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    pub callable_has_definition_body: bool,
343}
344
345#[derive(Clone, Copy)]
346pub struct ActivatedCallableArity {
347    pub activation_byte: usize,
348    pub arity: CallableArity,
349}
350
351#[derive(Debug, PartialEq, Eq, Hash)]
352pub struct TypeScanKey {
353    target: LogicalSymbolKey,
354    member_name: String,
355}
356
357#[derive(Clone, Debug, PartialEq, Eq, Hash)]
358struct LogicalSymbolKey {
359    kind: CodeUnitType,
360    fq_name: String,
361    signature: Option<String>,
362}
363
364struct ResolvedTypeOwner {
365    unit: CodeUnit,
366    is_forward_declaration: bool,
367}
368
369#[derive(Clone, Copy, PartialEq, Eq)]
370pub enum EnumOwnerKind {
371    Scoped,
372    Unscoped,
373    NonEnum,
374}
375
376impl TargetSpec {
377    pub fn type_scan_key(&self) -> Option<TypeScanKey> {
378        (self.kind == TargetKind::Type).then(|| TypeScanKey {
379            target: logical_symbol_key(&self.target),
380            member_name: self.member_name.clone(),
381        })
382    }
383
384    pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
385        if target.is_class() {
386            return Some(Self::new(
387                target.clone(),
388                TargetKind::Type,
389                Some(target.clone()),
390                target.identifier().to_string(),
391                None,
392                None,
393            ));
394        }
395
396        if target.is_field() {
397            // A namespace (module) is not a receiver: a namespace-scoped constant such as
398            // `example::DefaultPrefix` is referenced unqualified from inside the namespace and
399            // qualified from outside, exactly like a global. Treating a module owner as a
400            // member-field owner makes the receiver/owner-context match reject every valid
401            // reference, so resolve it as a global field instead.
402            let owner = type_owner_of(analyzer, target);
403            let kind = if owner.is_some() {
404                TargetKind::MemberField
405            } else {
406                TargetKind::GlobalField
407            };
408            let enum_owner_kind = owner
409                .as_ref()
410                .map(|owner| classify_enum_owner(analyzer, owner))
411                .unwrap_or(EnumOwnerKind::NonEnum);
412            let mut spec = Self::new(
413                target.clone(),
414                kind,
415                owner,
416                target.identifier().to_string(),
417                None,
418                None,
419            );
420            spec.enum_owner_kind = enum_owner_kind;
421            return Some(spec);
422        }
423
424        if target.is_function() {
425            // Free functions declared inside a namespace have a module owner; that namespace is
426            // not a call receiver, so resolve them as free functions rather than methods.
427            let owner_resolution = target_type_owner_resolution(analyzer, target);
428            let owner_is_forward_declaration = owner_resolution
429                .as_ref()
430                .is_some_and(|owner| owner.is_forward_declaration);
431            let owner = owner_resolution.map(|owner| owner.unit);
432            let kind = if owner.as_ref().is_some_and(|owner| {
433                target.identifier() == owner.identifier()
434                    || analyzer
435                        .cpp
436                        .and_then(|cpp| cpp.template_metadata(owner))
437                        .is_some_and(|metadata| metadata.primary_name == target.identifier())
438            }) {
439                TargetKind::Constructor
440            } else if owner.is_some() {
441                TargetKind::Method
442            } else {
443                TargetKind::FreeFunction
444            };
445            let mut spec = Self::new(
446                target.clone(),
447                kind,
448                owner,
449                target.identifier().to_string(),
450                Some(cpp_callable_arity(analyzer, target)),
451                cpp_callable_parameter_types(analyzer, target),
452            );
453            spec.owner_is_forward_declaration = owner_is_forward_declaration;
454            spec.callable_has_definition_body =
455                callable_target_has_definition_body(analyzer, target);
456            return Some(spec);
457        }
458
459        if target.is_macro() {
460            return Some(Self::new(
461                target.clone(),
462                TargetKind::Macro,
463                None,
464                target.identifier().to_string(),
465                None,
466                None,
467            ));
468        }
469
470        None
471    }
472
473    pub fn with_visible_callable_arities<'a>(
474        &'a self,
475        analyzer: &CppGraphSource<'_>,
476        cpp: &dyn CppSource,
477        visibility: &VisibilityIndex<'_>,
478        file: &ProjectFile,
479        prepared: &PreparedSyntaxTree,
480    ) -> Cow<'a, Self> {
481        let macro_parameter_arity =
482            visibility.callable_parameter_macro_arity(&self.target, self.target.signature());
483        let activated_callable_arities =
484            visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
485        if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
486            return Cow::Borrowed(self);
487        }
488        let mut effective = self.clone();
489        if let Some(macro_parameter_arity) = macro_parameter_arity {
490            effective.callable_arity = Some(macro_parameter_arity);
491        }
492        effective.activated_callable_arities = activated_callable_arities;
493        Cow::Owned(effective)
494    }
495
496    pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
497        let base = self.callable_arity?;
498        Some(
499            self.activated_callable_arities
500                .iter()
501                .filter(|candidate| candidate.activation_byte <= byte)
502                .fold(base, |arity, candidate| {
503                    merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
504                }),
505        )
506    }
507
508    pub fn new(
509        target: CodeUnit,
510        kind: TargetKind,
511        owner: Option<CodeUnit>,
512        member_name: String,
513        callable_arity: Option<CallableArity>,
514        param_types: Option<Vec<String>>,
515    ) -> Self {
516        Self {
517            target,
518            kind,
519            owner,
520            member_name,
521            callable_arity,
522            activated_callable_arities: Vec::new(),
523            param_types,
524            enum_owner_kind: EnumOwnerKind::NonEnum,
525            owner_is_forward_declaration: false,
526            callable_has_definition_body: false,
527        }
528    }
529}
530
531fn callable_target_has_definition_body(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> bool {
532    let Some(cpp) = analyzer.cpp else {
533        return false;
534    };
535    let Some(prepared) = cpp.prepared_syntax(analyzer.token, target.source()) else {
536        return false;
537    };
538    analyzer.ranges(target).into_iter().any(|range| {
539        let end = range
540            .start_byte
541            .saturating_add(1)
542            .min(prepared.source().len());
543        let mut current = prepared
544            .tree()
545            .root_node()
546            .descendant_for_byte_range(range.start_byte, end);
547        while let Some(node) = current {
548            match node.kind() {
549                "function_definition" => return true,
550                "declaration" => return false,
551                _ => current = node.parent(),
552            }
553        }
554        false
555    })
556}
557
558fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
559    LogicalSymbolKey {
560        kind: unit.kind(),
561        fq_name: unit.fq_name(),
562        signature: unit.signature().map(str::to_string),
563    }
564}
565
566fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
567    let classify = |source: &str| {
568        let source = source.trim_start();
569        if source.starts_with("enum class ") || source.starts_with("enum struct ") {
570            Some(EnumOwnerKind::Scoped)
571        } else if source.starts_with("enum ") {
572            Some(EnumOwnerKind::Unscoped)
573        } else {
574            None
575        }
576    };
577    owner
578        .signature()
579        .and_then(classify)
580        .or_else(|| {
581            analyzer
582                .get_source(owner, false)
583                .as_deref()
584                .and_then(classify)
585        })
586        .unwrap_or(EnumOwnerKind::NonEnum)
587}
588
589#[derive(Clone, PartialEq, Eq, Hash)]
590pub struct CppScanBinding {
591    pub unit: Option<CodeUnit>,
592    pub type_name: Option<String>,
593    pub indirection: i32,
594}
595
596impl CppScanBinding {
597    pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
598        Self {
599            type_name: Some(cpp_name_for(&unit)),
600            unit: Some(unit),
601            indirection,
602        }
603    }
604
605    pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
606        Self {
607            type_name: Some(type_name),
608            unit,
609            indirection,
610        }
611    }
612
613    pub fn as_arg_type(&self) -> Option<CppArgType> {
614        let name = self
615            .type_name
616            .clone()
617            .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
618        Some(CppArgType {
619            name,
620            unit: self.unit.clone(),
621            indirection: self.indirection,
622            pointee_const: false,
623        })
624    }
625}
626
627type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
628type VisibleParserAliasTargetNamesCell = Arc<OnceLock<HashMap<String, HashSet<String>>>>;
629pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
630pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
631type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
632pub type MacroEnvironmentCursorCell = Arc<Mutex<MacroEnvironmentCursor>>;
633type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
634type MacroLocalBindingTemplateCache =
635    HashMap<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>;
636
637#[derive(Clone, Default)]
638pub struct MacroEnvironment {
639    bindings: HashMap<String, MacroBinding>,
640    known_undefined_names: HashSet<String>,
641    /// Names the translation unit's compile command proves defined (#2011):
642    /// the `-D`s that survive command ordering, intersected across every
643    /// configuration naming the TU. Seeded once at TU start. An explicit
644    /// `#undef` seen later lands in `known_undefined_names` and wins.
645    build_proven_defines: HashSet<String>,
646    unknown_names: bool,
647    applied_pragma_once_files: HashSet<ProjectFile>,
648    maybe_applied_pragma_once_files: HashSet<ProjectFile>,
649}
650
651#[derive(Default)]
652pub struct MacroEnvironmentCursor {
653    frontier: usize,
654    environment: Arc<MacroEnvironment>,
655}
656
657impl MacroEnvironment {
658    fn binding(&self, name: &str) -> Option<&MacroBinding> {
659        self.bindings.get(name)
660    }
661
662    fn may_bind(&self, name: &str) -> bool {
663        self.bindings.contains_key(name) || self.unknown_names
664    }
665
666    fn insert(&mut self, name: String, binding: MacroBinding) {
667        self.known_undefined_names.remove(&name);
668        self.bindings.insert(name, binding);
669    }
670
671    fn remove(&mut self, name: &str) {
672        self.bindings.remove(name);
673        self.known_undefined_names.insert(name.to_string());
674    }
675
676    fn remove_known_undefined(&mut self, name: &str) {
677        self.known_undefined_names.remove(name);
678    }
679
680    fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
681        for binding in self.bindings.values_mut() {
682            *binding = MacroBinding::uncertain_from(binding, source, byte);
683        }
684        self.known_undefined_names.clear();
685        // An untracked include could `#undef` a command-line define, so the
686        // may-hold filter must stop treating the build facts as decisive from
687        // here on. The additive proof path keeps its facts: they still hold at
688        // the include chain's activation point.
689        self.build_proven_defines.clear();
690        self.unknown_names = true;
691    }
692
693    fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
694        guards.iter().all(|guard| self.guard_may_hold(guard))
695    }
696
697    fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
698        let Some(expression) = guard.as_boolean_expression() else {
699            return true;
700        };
701        self.boolean_guard_may_hold(&expression)
702    }
703
704    fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
705        match expression {
706            BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
707            BooleanGuardExpression::Undefined(name) => {
708                self.bindings
709                    .get(name)
710                    .is_none_or(|binding| !binding.is_exact())
711                    && (!self.build_proven_defines.contains(name)
712                        || self.known_undefined_names.contains(name))
713            }
714            BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
715            BooleanGuardExpression::Opaque(_)
716            | BooleanGuardExpression::NegatedOpaque(_)
717            | BooleanGuardExpression::Constant(true) => true,
718            BooleanGuardExpression::Constant(false) => false,
719            BooleanGuardExpression::All(expressions) => expressions
720                .iter()
721                .all(|expression| self.boolean_guard_may_hold(expression)),
722            BooleanGuardExpression::Any(expressions) => expressions
723                .iter()
724                .any(|expression| self.boolean_guard_may_hold(expression)),
725        }
726    }
727}
728
729#[derive(Clone)]
730pub enum EffectiveUsingTarget {
731    Ordinary {
732        name: String,
733        target_components: Vec<String>,
734        global: bool,
735    },
736    Namespace {
737        namespace_components: Vec<String>,
738        global: bool,
739    },
740}
741
742#[derive(Clone)]
743pub struct OrdinaryTypeImport {
744    pub target: EffectiveUsingTarget,
745    pub source: ProjectFile,
746    pub declaration_byte: usize,
747    pub scope_start: usize,
748    pub scope_end: usize,
749    pub scope_depth: usize,
750    pub block_scope: bool,
751    pub lexical_depth: usize,
752    pub declaration_namespace: Vec<String>,
753    pub namespace_scope: Option<Vec<String>>,
754    pub resolved_target_components: Option<Vec<String>>,
755    pub required_guards: HashSet<PreprocessorGuard>,
756}
757
758#[derive(Clone)]
759pub struct ConditionalIncludeProjection {
760    pub activation_byte: usize,
761    pub required_guards: HashSet<PreprocessorGuard>,
762}
763
764#[derive(Default)]
765pub struct SourceUsingIndex {
766    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
767    pub directives: Vec<OrdinaryTypeImport>,
768}
769
770#[derive(Default)]
771pub struct ProjectUsingIndex {
772    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
773    pub directives: Vec<OrdinaryTypeImport>,
774}
775
776type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
777
778pub struct EffectiveUsingIndex {
779    projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
780}
781
782impl EffectiveUsingIndex {
783    fn new(_root: ProjectFile) -> Self {
784        Self {
785            projected_by_name: Mutex::new(HashMap::default()),
786        }
787    }
788
789    pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
790        self.projected_by_name
791            .lock()
792            .expect("C++ effective-using projection cache poisoned")
793            .entry(name.to_string())
794            .or_default()
795            .clone()
796    }
797}
798
799pub enum OrdinaryTypeImportResolution {
800    Resolved {
801        target: CodeUnit,
802        target_components: Vec<String>,
803        lexical_depth: usize,
804        is_direct: bool,
805    },
806    Ambiguous {
807        lexical_depth: usize,
808    },
809    Missing,
810}
811
812type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
813type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
814type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
815type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
816type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
817type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
818type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
819
820/// One callable declaration's inputs to [`VisibilityIndex::same_logical_callable`],
821/// read from its declaration syntax rather than from its persisted signature
822/// string: the comparable shape of each parameter, and the trailing identity
823/// suffix that shape does not carry.
824struct ExtractedComparable {
825    shapes: Vec<CppComparableSlot>,
826    suffix: String,
827}
828
829/// How many alias hops [`VisibilityIndex::same_logical_callable`] follows
830/// before giving up on a written type name. A visited set already stops a
831/// cycle; this stops an adversarially long chain from costing a lookup per hop.
832const MAX_COMPARABLE_ALIAS_HOPS: usize = 32;
833
834/// Per-query C++ visibility facts.
835///
836/// The analyzer is *borrowed*, never cloned: `TreeSitterAnalyzer::clone` gives
837/// the clone a fresh, empty `QueryReadCache` on purpose (clones cross
838/// generations and overlays, where another generation's hydrated states would
839/// be wrong). An index that owned a clone would therefore see an inactive read
840/// cache for every `prepared_syntax` call it makes, re-reading and re-parsing
841/// the same source from the store once per candidate instead of once per query
842/// — the #1175 blow-up, where one scan re-parsed a 4.8 MB generated header
843/// tens of thousands of times.
844pub struct VisibilityIndex<'a> {
845    cpp: &'a dyn CppSource,
846    /// Proof that the request scope the index was built under is still open.
847    /// The index is a per-query object whose lifetime is inside the scope's,
848    /// so carrying the token here instead of on ninety method signatures is
849    /// the same guarantee for far less plumbing (issue #2414 step 3).
850    token: QueryToken<'a>,
851    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
852    visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
853    global_field_internal_linkage: HashMap<CodeUnit, bool>,
854    visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
855    alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
856    visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
857    visible_parser_alias_target_names:
858        Mutex<HashMap<ProjectFile, VisibleParserAliasTargetNamesCell>>,
859    ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
860    project_using_index: OnceLock<ProjectUsingIndex>,
861    callable_reference_specs:
862        Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
863    include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
864    compile_proven_guard_cells: Mutex<HashMap<ProjectFile, Arc<HashSet<PreprocessorGuard>>>>,
865    conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
866    #[cfg(any(test, feature = "test-support"))]
867    conditional_include_projection_index_build_count: AtomicUsize,
868    #[cfg(any(test, feature = "test-support"))]
869    conditional_include_projection_state_count: AtomicUsize,
870    #[cfg(any(test, feature = "test-support"))]
871    include_activation_build_count: AtomicUsize,
872    #[cfg(any(test, feature = "test-support"))]
873    using_donor_activation_count: AtomicUsize,
874    #[cfg(any(test, feature = "test-support"))]
875    using_namespace_lookup_count: AtomicUsize,
876    #[cfg(any(test, feature = "test-support"))]
877    using_name_candidate_inspection_count: AtomicUsize,
878    #[cfg(any(test, feature = "test-support"))]
879    callable_reference_spec_build_count: AtomicUsize,
880    #[cfg(any(test, feature = "test-support"))]
881    alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
882    #[cfg(any(test, feature = "test-support"))]
883    visible_parser_alias_name_set_build_count: AtomicUsize,
884    #[cfg(any(test, feature = "test-support"))]
885    visible_parser_alias_target_names_build_count: AtomicUsize,
886    field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
887    structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
888    callable_comparables: Mutex<HashMap<CodeUnit, Option<Arc<ExtractedComparable>>>>,
889    indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
890    indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
891    precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
892    macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
893    pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
894    // A forward cursor is useful only while its caller visits one source in byte order. The
895    // authoritative differential shares this index across target workers, whose frontiers can
896    // interleave arbitrarily, so sharing one cursor per file would serialize the include replay
897    // and repeatedly reset it. Keep one bounded cursor per participating worker instead; the
898    // immutable event and parse caches above remain shared.
899    pub macro_environment_cursors:
900        Mutex<HashMap<(ProjectFile, ThreadId), MacroEnvironmentCursorCell>>,
901    macro_replacements: Mutex<MacroReplacementCache>,
902    macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
903    callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
904    #[cfg(any(test, feature = "test-support"))]
905    pub macro_replacement_parse_count: AtomicUsize,
906    #[cfg(any(test, feature = "test-support"))]
907    pub macro_event_application_count: AtomicUsize,
908    #[cfg(any(test, feature = "test-support"))]
909    pub macro_environment_copy_count: AtomicUsize,
910    cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
911    cpp_template_families: HashMap<String, Vec<CodeUnit>>,
912    #[cfg(any(test, feature = "test-support"))]
913    qualified_candidate_inspections: AtomicUsize,
914    #[cfg(any(test, feature = "test-support"))]
915    target_preserving_type_resolution_count: AtomicUsize,
916}
917
918#[derive(Clone, Debug, PartialEq, Eq, Hash)]
919pub enum PreprocessorGuard {
920    Defined(String),
921    Undefined(String),
922    Boolean(BooleanGuardExpression),
923    Expression(String),
924    NegatedExpression(String),
925    Constant(bool),
926}
927
928#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
929pub enum BooleanGuardExpression {
930    Defined(String),
931    Undefined(String),
932    Truthy(String),
933    Falsy(String),
934    Opaque(String),
935    NegatedOpaque(String),
936    All(Vec<BooleanGuardExpression>),
937    Any(Vec<BooleanGuardExpression>),
938    Constant(bool),
939}
940
941impl BooleanGuardExpression {
942    fn negated(&self) -> Self {
943        match self {
944            Self::Defined(name) => Self::Undefined(name.clone()),
945            Self::Undefined(name) => Self::Defined(name.clone()),
946            Self::Truthy(name) => Self::Falsy(name.clone()),
947            Self::Falsy(name) => Self::Truthy(name.clone()),
948            Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
949            Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
950            Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
951            Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
952            Self::Constant(value) => Self::Constant(!value),
953        }
954    }
955
956    fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
957        Self::normalized(expressions, true)
958    }
959
960    fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
961        Self::normalized(expressions, false)
962    }
963
964    fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
965        let mut normalized = Vec::new();
966        for expression in expressions {
967            match expression {
968                Self::All(nested) if conjunction => normalized.extend(nested),
969                Self::Any(nested) if !conjunction => normalized.extend(nested),
970                Self::Constant(value) if value == conjunction => {}
971                Self::Constant(value) => return Self::Constant(value),
972                expression => normalized.push(expression),
973            }
974        }
975        normalized.sort_unstable();
976        normalized.dedup();
977        match normalized.len() {
978            0 => Self::Constant(conjunction),
979            1 => normalized.pop().expect("one Boolean guard expression"),
980            _ if conjunction => Self::All(normalized),
981            _ => Self::Any(normalized),
982        }
983    }
984
985    fn implies(&self, required: &Self) -> bool {
986        if self == required
987            || matches!(self, Self::Constant(false))
988            || matches!(required, Self::Constant(true))
989        {
990            return true;
991        }
992        match self {
993            Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
994            Self::All(active) => match required {
995                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
996                _ => active.iter().any(|expression| expression.implies(required)),
997            },
998            _ => match required {
999                Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
1000                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1001                _ => false,
1002            },
1003        }
1004    }
1005
1006    pub fn heap_size(&self) -> usize {
1007        match self {
1008            Self::Defined(value)
1009            | Self::Undefined(value)
1010            | Self::Truthy(value)
1011            | Self::Falsy(value)
1012            | Self::Opaque(value)
1013            | Self::NegatedOpaque(value) => value.len(),
1014            Self::All(expressions) | Self::Any(expressions) => {
1015                expressions
1016                    .iter()
1017                    .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
1018                        size.saturating_add(std::mem::size_of::<Self>())
1019                            .saturating_add(expression.heap_size())
1020                    })
1021            }
1022            Self::Constant(_) => 0,
1023        }
1024    }
1025}
1026
1027impl PreprocessorGuard {
1028    fn as_boolean_expression(&self) -> Option<BooleanGuardExpression> {
1029        match self {
1030            Self::Defined(name) => Some(BooleanGuardExpression::Defined(name.clone())),
1031            Self::Undefined(name) => Some(BooleanGuardExpression::Undefined(name.clone())),
1032            Self::Boolean(expression) => Some(expression.clone()),
1033            Self::Constant(value) => Some(BooleanGuardExpression::Constant(*value)),
1034            Self::Expression(_) | Self::NegatedExpression(_) => None,
1035        }
1036    }
1037
1038    fn negated(&self) -> Self {
1039        match self {
1040            Self::Defined(name) => Self::Undefined(name.clone()),
1041            Self::Undefined(name) => Self::Defined(name.clone()),
1042            Self::Boolean(expression) => Self::Boolean(expression.negated()),
1043            Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1044            Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1045            Self::Constant(value) => Self::Constant(!value),
1046        }
1047    }
1048
1049    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1050        match self {
1051            Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1052            // The expression has already been isolated structurally by
1053            // tree-sitter, but its full preprocessor semantics are outside the
1054            // analyzer's guard model. Any macro mutation can therefore change
1055            // its truth value.
1056            Self::Boolean(_) | Self::Expression(_) | Self::NegatedExpression(_) => true,
1057            Self::Constant(_) => false,
1058        }
1059    }
1060}
1061
1062#[derive(Clone, PartialEq, Eq)]
1063pub enum MacroDefinition {
1064    Object {
1065        replacement: String,
1066    },
1067    Function {
1068        parameters: Vec<String>,
1069        replacement: String,
1070    },
1071    Unsupported,
1072}
1073
1074#[derive(Clone, Debug, PartialEq, Eq)]
1075pub enum MacroIncludeProtection {
1076    MacroGuard(String),
1077    PragmaOnce,
1078    None,
1079}
1080
1081enum ParsedMacroReplacement {
1082    Parsed { source: String, tree: Tree },
1083    Unsupported,
1084}
1085
1086#[derive(Clone)]
1087enum MacroLocalBindingTypeTemplate {
1088    Parameter(usize),
1089    Fixed(String),
1090}
1091
1092#[derive(Clone)]
1093struct MacroLocalBindingTemplate {
1094    name: String,
1095    declared_type: MacroLocalBindingTypeTemplate,
1096    pointer_depth: i32,
1097}
1098
1099/// A local declaration contributed by one structurally known function-like macro.
1100///
1101/// `type_node` points into the invocation syntax when the replacement's type
1102/// is one of the macro parameters. Consumers can therefore use their normal
1103/// lexical type resolver without parsing replacement text themselves.
1104pub struct MacroLocalBinding<'tree> {
1105    pub name: String,
1106    pub type_name: String,
1107    pub type_node: Option<Node<'tree>>,
1108    pub pointer_depth: i32,
1109}
1110
1111/// Recover GLib's `g_autoptr(T) name = value` declaration from the CST shape
1112/// produced by tree-sitter-cpp for C source. The grammar retains the macro
1113/// invocation as the assignment's left operand and the declared name as one
1114/// adjacent `ERROR(identifier)` node, so no macro text splitting is needed.
1115fn recognized_c_macro_declarator_binding<'tree>(
1116    statement: Node<'tree>,
1117    source: &str,
1118) -> Option<MacroLocalBinding<'tree>> {
1119    let assignment = match statement.kind() {
1120        "assignment_expression" => statement,
1121        "expression_statement" if statement.named_child_count() == 1 => statement.named_child(0)?,
1122        _ => return None,
1123    };
1124    if assignment.kind() != "assignment_expression" {
1125        return None;
1126    }
1127    let call = assignment.child_by_field_name("left")?;
1128    if call.kind() != "call_expression" {
1129        return None;
1130    }
1131    let function = call.child_by_field_name("function")?;
1132    if function.kind() != "identifier" || node_text(function, source) != "g_autoptr" {
1133        return None;
1134    }
1135    let arguments = call.child_by_field_name("arguments")?;
1136    let mut actuals = argument_children(arguments);
1137    let type_node = actuals.next()?;
1138    if actuals.next().is_some()
1139        || !matches!(
1140            type_node.kind(),
1141            "identifier"
1142                | "type_identifier"
1143                | "qualified_identifier"
1144                | "scoped_type_identifier"
1145                | "template_type"
1146        )
1147    {
1148        return None;
1149    }
1150    let name_node = (0..assignment.named_child_count())
1151        .filter_map(|index| assignment.named_child(index))
1152        .filter(|child| child.kind() == "ERROR")
1153        .filter_map(|error| {
1154            (error.named_child_count() == 1)
1155                .then(|| error.named_child(0))
1156                .flatten()
1157        })
1158        .find(|node| node.kind() == "identifier")?;
1159    let name = node_text(name_node, source).trim();
1160    let type_name = node_text(type_node, source).trim();
1161    if name.is_empty() || type_name.is_empty() {
1162        return None;
1163    }
1164    Some(MacroLocalBinding {
1165        name: name.to_string(),
1166        type_name: type_name.to_string(),
1167        type_node: Some(type_node),
1168        pointer_depth: 1,
1169    })
1170}
1171
1172#[derive(Clone, PartialEq, Eq)]
1173pub struct MacroBinding {
1174    source: ProjectFile,
1175    declaration_byte: usize,
1176    definition: MacroDefinition,
1177    exact: bool,
1178}
1179
1180impl MacroBinding {
1181    fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1182        Self {
1183            source: source.clone(),
1184            declaration_byte,
1185            definition: MacroDefinition::Unsupported,
1186            exact: false,
1187        }
1188    }
1189
1190    fn is_exact(&self) -> bool {
1191        self.exact
1192    }
1193
1194    fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1195        Self {
1196            source: source.clone(),
1197            declaration_byte,
1198            definition: current.definition.clone(),
1199            exact: false,
1200        }
1201    }
1202}
1203
1204#[derive(Clone)]
1205pub enum MacroEvent {
1206    Define {
1207        name: String,
1208        binding: MacroBinding,
1209        byte: usize,
1210        conditional: bool,
1211    },
1212    Undef {
1213        name: String,
1214        byte: usize,
1215        conditional: bool,
1216    },
1217    Include {
1218        targets: Vec<ProjectFile>,
1219        byte: usize,
1220        conditional: bool,
1221    },
1222    Invalidate {
1223        byte: usize,
1224    },
1225}
1226
1227impl MacroEvent {
1228    pub fn byte(&self) -> usize {
1229        match self {
1230            Self::Define { byte, .. }
1231            | Self::Undef { byte, .. }
1232            | Self::Include { byte, .. }
1233            | Self::Invalidate { byte } => *byte,
1234        }
1235    }
1236}
1237
1238#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1239pub enum CallArityEvidence {
1240    Exact(usize),
1241    Unknown,
1242}
1243
1244impl CallArityEvidence {
1245    pub fn exact(self) -> Option<usize> {
1246        match self {
1247            Self::Exact(arity) => Some(arity),
1248            Self::Unknown => None,
1249        }
1250    }
1251
1252    pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1253        self.exact().map(|arity| expected.accepts(arity))
1254    }
1255}
1256
1257#[derive(Clone)]
1258struct DeclaredFieldTypeFact {
1259    type_text: String,
1260    indirection: i32,
1261    template_arguments: Option<Vec<CppTemplateExpression>>,
1262}
1263
1264#[derive(Clone, PartialEq, Eq)]
1265enum StructuredAliasTarget {
1266    Builtin,
1267    Named {
1268        components: Vec<String>,
1269        global: bool,
1270        arguments: Option<Vec<CppTemplateExpression>>,
1271    },
1272}
1273
1274struct CppAlias {
1275    name: String,
1276    target: String,
1277    namespace: Option<String>,
1278}
1279
1280type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1281
1282/// Why template-argument resolution failed. Definition diagnostics render
1283/// each mode differently; graph scans only care that the resolution is
1284/// unproven and match `Err(_)`.
1285#[derive(Debug, Clone, PartialEq, Eq)]
1286pub enum CppTemplateResolutionError {
1287    /// A template alias expansion revisited `alias`.
1288    AliasCycle { alias: CodeUnit },
1289    /// The explicit arguments do not bind to the declared template parameters.
1290    ArgumentBinding,
1291    /// Bound arguments do not substitute into the alias target's arguments.
1292    Substitution,
1293    /// No visible primary template declaration could be selected and
1294    /// reconciled for the specialization family.
1295    PrimarySelection,
1296    /// More than one applicable specialization remains and none is strictly
1297    /// more specialized than every other candidate.
1298    AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1299}
1300
1301/// The ambiguity candidates, deduplicated to one representative per visible
1302/// symbol so a diagnostic lists each contender once.
1303fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1304    let mut distinct: Vec<CodeUnit> = Vec::new();
1305    for unit in units {
1306        if !distinct
1307            .iter()
1308            .any(|existing| same_visible_symbol(existing, unit))
1309        {
1310            distinct.push(unit.clone());
1311        }
1312    }
1313    distinct
1314}
1315
1316impl<'a> VisibilityIndex<'a> {
1317    pub fn cpp(&self) -> &'a dyn CppSource {
1318        self.cpp
1319    }
1320
1321    /// The request-scope proof this index was built with (issue #2414 step 3).
1322    pub fn token(&self) -> QueryToken<'a> {
1323        self.token
1324    }
1325
1326    /// A [`VisibilityIndex`] over a caller-supplied visible-declaration map,
1327    /// bypassing the include-closure walk [`Self::build`] performs.
1328    ///
1329    /// The resolver's own unit tests drive the type-resolution paths against a
1330    /// hand-written visibility table; they live in `brokk-bifrost-analysis`
1331    /// because they need a real `CppAnalyzer`, so the struct literal they used
1332    /// to write inline is here instead of thirty-three public fields.
1333    #[cfg(any(test, feature = "test-support"))]
1334    pub fn from_visible_files_for_test(
1335        cpp: &'a dyn CppSource,
1336        token: QueryToken<'a>,
1337        visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1338    ) -> Self {
1339        let visible_source_files_by_root = visible_by_file
1340            .iter()
1341            .map(|(file, visible)| {
1342                (
1343                    file.clone(),
1344                    visible
1345                        .iter()
1346                        .map(|unit| unit.source().clone())
1347                        .chain(std::iter::once(file.clone()))
1348                        .collect(),
1349                )
1350            })
1351            .collect();
1352        let mut global_field_internal_linkage = HashMap::default();
1353        Self {
1354            cpp,
1355            token,
1356            visible_by_identifier: build_visible_identifier_index(
1357                &CppGraphSource::from_source(cpp, token),
1358                &visible_by_file,
1359                &visible_source_files_by_root,
1360                &mut global_field_internal_linkage,
1361            ),
1362            global_field_internal_linkage,
1363            visible_by_file,
1364            visible_source_files_by_root,
1365            alias_cells: Mutex::new(HashMap::default()),
1366            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1367            visible_parser_alias_target_names: Mutex::new(HashMap::default()),
1368            ordinary_type_import_cells: Mutex::new(HashMap::default()),
1369            project_using_index: OnceLock::new(),
1370            callable_reference_specs: Mutex::new(HashMap::default()),
1371            include_activation_cells: Mutex::new(HashMap::default()),
1372            compile_proven_guard_cells: Mutex::new(HashMap::default()),
1373            conditional_include_projection_cells: Mutex::new(HashMap::default()),
1374            conditional_include_projection_index_build_count: AtomicUsize::new(0),
1375            conditional_include_projection_state_count: AtomicUsize::new(0),
1376            include_activation_build_count: AtomicUsize::new(0),
1377            using_donor_activation_count: AtomicUsize::new(0),
1378            using_namespace_lookup_count: AtomicUsize::new(0),
1379            using_name_candidate_inspection_count: AtomicUsize::new(0),
1380            callable_reference_spec_build_count: AtomicUsize::new(0),
1381            alias_source_parse_counts: Mutex::new(HashMap::default()),
1382            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1383            visible_parser_alias_target_names_build_count: AtomicUsize::new(0),
1384            field_type_facts: Mutex::new(HashMap::default()),
1385            structured_alias_targets: Mutex::new(HashMap::default()),
1386            callable_comparables: Mutex::new(HashMap::default()),
1387            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1388            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1389            precise_parent_cache: Mutex::new(HashMap::default()),
1390            macro_event_cells: Mutex::new(HashMap::default()),
1391            macro_include_protection_cells: Mutex::new(HashMap::default()),
1392            macro_environment_cursors: Mutex::new(HashMap::default()),
1393            macro_replacements: Mutex::new(HashMap::default()),
1394            macro_local_binding_templates: Mutex::new(HashMap::default()),
1395            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1396            macro_replacement_parse_count: AtomicUsize::new(0),
1397            macro_event_application_count: AtomicUsize::new(0),
1398            macro_environment_copy_count: AtomicUsize::new(0),
1399            cpp_template_metadata: HashMap::default(),
1400            cpp_template_families: HashMap::default(),
1401            qualified_candidate_inspections: AtomicUsize::new(0),
1402            target_preserving_type_resolution_count: AtomicUsize::new(0),
1403        }
1404    }
1405
1406    /// The index's own C++ source, in the dispatching-analyzer shape.
1407    ///
1408    /// Four resolution paths reach the workspace through the C++ analyzer they
1409    /// already hold rather than through the analyzer the query was issued
1410    /// against; before the move they passed `&CppAnalyzer` straight into a
1411    /// `&dyn IAnalyzer` parameter. See [`CppGraphSource::from_source`].
1412    fn cpp_source(&self) -> CppGraphSource<'a> {
1413        CppGraphSource::from_source(self.cpp, self.token)
1414    }
1415
1416    pub fn build(
1417        cpp: &'a dyn CppSource,
1418        token: QueryToken<'a>,
1419        analyzer: &CppGraphSource<'_>,
1420        roots: &HashSet<ProjectFile>,
1421    ) -> Self {
1422        Self::build_with_cancellation(cpp, token, analyzer, roots, None)
1423    }
1424
1425    pub fn build_with_cancellation(
1426        cpp: &'a dyn CppSource,
1427        token: QueryToken<'a>,
1428        analyzer: &CppGraphSource<'_>,
1429        roots: &HashSet<ProjectFile>,
1430        cancellation: Option<&CancellationToken>,
1431    ) -> Self {
1432        let include_targets = cpp.include_target_index();
1433        let VisibilityData {
1434            mut visible_by_file,
1435            visible_source_files_by_root,
1436        } = build_visibility_data(
1437            roots,
1438            cancellation,
1439            |file| {
1440                let imports = analyzer.import_statements(file);
1441                cpp_include_paths(&imports)
1442                    .into_iter()
1443                    .flat_map(|include| {
1444                        resolve_include_targets_with_index(file, &include, include_targets)
1445                    })
1446                    .collect()
1447            },
1448            |root| analyzer.reference_uses_c_semantics(root),
1449            |file, c_semantics| analyzer.declarations_in_reading(file, c_semantics),
1450        );
1451        extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
1452        let mut global_field_internal_linkage = HashMap::default();
1453        let visible_by_identifier = build_visible_identifier_index(
1454            analyzer,
1455            &visible_by_file,
1456            &visible_source_files_by_root,
1457            &mut global_field_internal_linkage,
1458        );
1459        let mut cpp_template_metadata = HashMap::default();
1460        for unit in visible_by_file
1461            .values()
1462            .flatten()
1463            .filter(|unit| unit.is_class())
1464        {
1465            if cpp_template_metadata.contains_key(unit) {
1466                continue;
1467            }
1468            if let Some(metadata) = cpp.template_metadata(unit) {
1469                cpp_template_metadata.insert(unit.clone(), metadata);
1470            }
1471        }
1472        let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
1473        for (unit, metadata) in &cpp_template_metadata {
1474            cpp_template_families
1475                .entry(metadata.primary_fq_name.clone())
1476                .or_default()
1477                .push(unit.clone());
1478        }
1479        // `cpp_template_metadata` is hash-keyed on `CodeUnit`, so the push
1480        // order above is a function of those hashes. Two mirrored headers can
1481        // declare one specialization; `select_template_specialization` treats
1482        // them as interchangeable and returns the family's first entry, so an
1483        // unsorted family made the reported declaration depend on the
1484        // workspace's absolute path and on unrelated files (#1836). Order the
1485        // family exactly as `build_visible_identifier_index` orders its
1486        // per-identifier candidate lists.
1487        for family in cpp_template_families.values_mut() {
1488            sort_lookup_units(family);
1489        }
1490        Self {
1491            cpp,
1492            token,
1493            visible_by_file,
1494            visible_by_identifier,
1495            global_field_internal_linkage,
1496            visible_source_files_by_root,
1497            alias_cells: Mutex::new(HashMap::default()),
1498            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1499            visible_parser_alias_target_names: Mutex::new(HashMap::default()),
1500            ordinary_type_import_cells: Mutex::new(HashMap::default()),
1501            project_using_index: OnceLock::new(),
1502            callable_reference_specs: Mutex::new(HashMap::default()),
1503            include_activation_cells: Mutex::new(HashMap::default()),
1504            compile_proven_guard_cells: Mutex::new(HashMap::default()),
1505            conditional_include_projection_cells: Mutex::new(HashMap::default()),
1506            #[cfg(any(test, feature = "test-support"))]
1507            conditional_include_projection_index_build_count: AtomicUsize::new(0),
1508            #[cfg(any(test, feature = "test-support"))]
1509            conditional_include_projection_state_count: AtomicUsize::new(0),
1510            #[cfg(any(test, feature = "test-support"))]
1511            include_activation_build_count: AtomicUsize::new(0),
1512            #[cfg(any(test, feature = "test-support"))]
1513            using_donor_activation_count: AtomicUsize::new(0),
1514            #[cfg(any(test, feature = "test-support"))]
1515            using_namespace_lookup_count: AtomicUsize::new(0),
1516            #[cfg(any(test, feature = "test-support"))]
1517            using_name_candidate_inspection_count: AtomicUsize::new(0),
1518            #[cfg(any(test, feature = "test-support"))]
1519            callable_reference_spec_build_count: AtomicUsize::new(0),
1520            #[cfg(any(test, feature = "test-support"))]
1521            alias_source_parse_counts: Mutex::new(HashMap::default()),
1522            #[cfg(any(test, feature = "test-support"))]
1523            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1524            #[cfg(any(test, feature = "test-support"))]
1525            visible_parser_alias_target_names_build_count: AtomicUsize::new(0),
1526            field_type_facts: Mutex::new(HashMap::default()),
1527            structured_alias_targets: Mutex::new(HashMap::default()),
1528            callable_comparables: Mutex::new(HashMap::default()),
1529            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1530            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1531            precise_parent_cache: Mutex::new(HashMap::default()),
1532            macro_event_cells: Mutex::new(HashMap::default()),
1533            macro_include_protection_cells: Mutex::new(HashMap::default()),
1534            macro_environment_cursors: Mutex::new(HashMap::default()),
1535            macro_replacements: Mutex::new(HashMap::default()),
1536            macro_local_binding_templates: Mutex::new(HashMap::default()),
1537            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1538            #[cfg(any(test, feature = "test-support"))]
1539            macro_replacement_parse_count: AtomicUsize::new(0),
1540            #[cfg(any(test, feature = "test-support"))]
1541            macro_event_application_count: AtomicUsize::new(0),
1542            #[cfg(any(test, feature = "test-support"))]
1543            macro_environment_copy_count: AtomicUsize::new(0),
1544            cpp_template_metadata,
1545            cpp_template_families,
1546            #[cfg(any(test, feature = "test-support"))]
1547            qualified_candidate_inspections: AtomicUsize::new(0),
1548            #[cfg(any(test, feature = "test-support"))]
1549            target_preserving_type_resolution_count: AtomicUsize::new(0),
1550        }
1551    }
1552
1553    pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
1554        if file == target.source() {
1555            return true;
1556        }
1557        if self.global_field_has_internal_linkage(target) {
1558            return self
1559                .visible_source_files_by_root
1560                .get(file)
1561                .is_some_and(|sources| sources.contains(target.source()));
1562        }
1563        self.visible_by_file
1564            .get(file)
1565            .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
1566    }
1567
1568    fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
1569        self.global_field_internal_linkage
1570            .get(unit)
1571            .copied()
1572            .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
1573    }
1574
1575    pub fn call_arity_evidence(
1576        &self,
1577        file: &ProjectFile,
1578        call: Node<'_>,
1579        source: &str,
1580    ) -> CallArityEvidence {
1581        let Some(arguments) = call
1582            .child_by_field_name("arguments")
1583            .or_else(|| call.child_by_field_name("parameters"))
1584            .or_else(|| call.child_by_field_name("value"))
1585            .or_else(|| first_named_child_of_kind(call, "argument_list"))
1586            .or_else(|| first_named_child_of_kind(call, "initializer_list"))
1587        else {
1588            return CallArityEvidence::Exact(0);
1589        };
1590        let recovered_c_keyword_arguments =
1591            recovered_c_keyword_argument_count(file, call, arguments, source);
1592        let arguments = argument_children(arguments).collect::<Vec<_>>();
1593        if arguments
1594            .iter()
1595            .all(|argument| !argument_shape_may_change_arity(*argument))
1596        {
1597            return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
1598        }
1599        let environment = self.macro_environment(file, call.start_byte());
1600        let mut stack = Vec::new();
1601        let mut total = recovered_c_keyword_arguments;
1602        for argument in arguments {
1603            if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
1604                return CallArityEvidence::Unknown;
1605            }
1606            let CallArityEvidence::Exact(spread) =
1607                self.argument_arity_evidence(argument, source, &environment, &mut stack)
1608            else {
1609                return CallArityEvidence::Unknown;
1610            };
1611            total += spread;
1612        }
1613        CallArityEvidence::Exact(total)
1614    }
1615
1616    fn argument_arity_evidence(
1617        &self,
1618        argument: Node<'_>,
1619        source: &str,
1620        environment: &MacroEnvironment,
1621        stack: &mut Vec<(ProjectFile, usize)>,
1622    ) -> CallArityEvidence {
1623        let (name, invocation_arguments, function_like) = match argument.kind() {
1624            "identifier" => (node_text(argument, source), None, false),
1625            "call_expression" => {
1626                let Some(function) = argument.child_by_field_name("function") else {
1627                    return CallArityEvidence::Exact(1);
1628                };
1629                if function.kind() != "identifier" {
1630                    return CallArityEvidence::Exact(1);
1631                }
1632                let Some(arguments) = argument.child_by_field_name("arguments") else {
1633                    return CallArityEvidence::Exact(1);
1634                };
1635                (node_text(function, source), Some(arguments), true)
1636            }
1637            _ => return CallArityEvidence::Exact(1),
1638        };
1639        let Some(binding) = environment.binding(name) else {
1640            return if environment.unknown_names {
1641                CallArityEvidence::Unknown
1642            } else {
1643                CallArityEvidence::Exact(1)
1644            };
1645        };
1646        if !binding.is_exact() {
1647            return CallArityEvidence::Unknown;
1648        }
1649        match (&binding.definition, invocation_arguments, function_like) {
1650            (MacroDefinition::Object { replacement }, None, false) => self
1651                .replacement_arity_evidence(
1652                    replacement,
1653                    &[],
1654                    &[],
1655                    source,
1656                    environment,
1657                    stack,
1658                    binding,
1659                ),
1660            (
1661                MacroDefinition::Function {
1662                    parameters,
1663                    replacement,
1664                },
1665                Some(arguments),
1666                true,
1667            ) => {
1668                let actuals = argument_children(arguments).collect::<Vec<_>>();
1669                if actuals.len() != parameters.len() {
1670                    CallArityEvidence::Unknown
1671                } else {
1672                    self.replacement_arity_evidence(
1673                        replacement,
1674                        parameters,
1675                        &actuals,
1676                        source,
1677                        environment,
1678                        stack,
1679                        binding,
1680                    )
1681                }
1682            }
1683            (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
1684            _ => CallArityEvidence::Unknown,
1685        }
1686    }
1687
1688    #[allow(clippy::too_many_arguments)]
1689    fn replacement_arity_evidence(
1690        &self,
1691        replacement: &str,
1692        parameters: &[String],
1693        actuals: &[Node<'_>],
1694        actual_source: &str,
1695        environment: &MacroEnvironment,
1696        stack: &mut Vec<(ProjectFile, usize)>,
1697        binding: &MacroBinding,
1698    ) -> CallArityEvidence {
1699        let identity = (binding.source.clone(), binding.declaration_byte);
1700        if stack.contains(&identity) || replacement.trim().is_empty() {
1701            return CallArityEvidence::Unknown;
1702        }
1703        stack.push(identity);
1704        let parsed = self.parsed_macro_replacement(binding, replacement);
1705        let evidence = (|| {
1706            let ParsedMacroReplacement::Parsed {
1707                source: sentinel,
1708                tree,
1709            } = parsed.as_ref()
1710            else {
1711                return None;
1712            };
1713            let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
1714            let arguments = call.child_by_field_name("arguments")?;
1715            let mut total = 0usize;
1716            for argument in argument_children(arguments) {
1717                if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
1718                    return None;
1719                }
1720                if argument.kind() == "identifier"
1721                    && let Some(parameter_index) = parameters
1722                        .iter()
1723                        .position(|parameter| parameter == node_text(argument, sentinel))
1724                {
1725                    if !macro_expansion_shape_is_safe(
1726                        actuals[parameter_index],
1727                        actual_source,
1728                        &[],
1729                        environment,
1730                    ) {
1731                        return None;
1732                    }
1733                    let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
1734                        actuals[parameter_index],
1735                        actual_source,
1736                        environment,
1737                        stack,
1738                    ) else {
1739                        return None;
1740                    };
1741                    total += spread;
1742                    continue;
1743                }
1744                let CallArityEvidence::Exact(spread) =
1745                    self.argument_arity_evidence(argument, sentinel, environment, stack)
1746                else {
1747                    return None;
1748                };
1749                total += spread;
1750            }
1751            Some(CallArityEvidence::Exact(total))
1752        })()
1753        .unwrap_or(CallArityEvidence::Unknown);
1754        stack.pop();
1755        evidence
1756    }
1757
1758    fn parsed_macro_replacement(
1759        &self,
1760        binding: &MacroBinding,
1761        replacement: &str,
1762    ) -> Arc<ParsedMacroReplacement> {
1763        let key = (binding.source.clone(), binding.declaration_byte);
1764        let mut cache = self
1765            .macro_replacements
1766            .lock()
1767            .expect("C++ macro replacement cache poisoned");
1768        if let Some(parsed) = cache.get(&key) {
1769            return Arc::clone(parsed);
1770        }
1771        #[cfg(any(test, feature = "test-support"))]
1772        self.macro_replacement_parse_count
1773            .fetch_add(1, Ordering::Relaxed);
1774        let source =
1775            format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
1776        let mut parser = Parser::new();
1777        let parsed = parser
1778            .set_language(&tree_sitter_cpp::LANGUAGE.into())
1779            .ok()
1780            .and_then(|()| parser.parse(&source, None))
1781            .filter(|tree| !tree.root_node().has_error())
1782            .map_or(ParsedMacroReplacement::Unsupported, |tree| {
1783                ParsedMacroReplacement::Parsed { source, tree }
1784            });
1785        let parsed = Arc::new(parsed);
1786        cache.insert(key, Arc::clone(&parsed));
1787        parsed
1788    }
1789
1790    /// Recover a typed local declared by an active C function-like macro.
1791    ///
1792    /// This is intentionally narrower than macro expansion. The replacement
1793    /// must parse as one declaration, and the invocation must bind every
1794    /// formal parameter to one structured argument. That is sufficient for
1795    /// declaration macros such as `THIS(StorageAzure)`. An unavailable include
1796    /// can make the binding provisional without erasing its last known
1797    /// definition; an explicit conflicting definition still replaces it with
1798    /// Unsupported. Malformed and statement-producing macros also fail closed.
1799    pub fn function_macro_local_binding<'tree>(
1800        &self,
1801        file: &ProjectFile,
1802        statement: Node<'tree>,
1803        source: &str,
1804    ) -> Option<MacroLocalBinding<'tree>> {
1805        if !is_c_source_file(file) {
1806            return None;
1807        }
1808        if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
1809            return Some(binding);
1810        }
1811        let call = match statement.kind() {
1812            "call_expression" => statement,
1813            "expression_statement" if statement.named_child_count() == 1 => {
1814                statement.named_child(0)?
1815            }
1816            _ => return None,
1817        };
1818        if call.kind() != "call_expression" {
1819            return None;
1820        }
1821        let function = call.child_by_field_name("function")?;
1822        if function.kind() != "identifier" {
1823            return None;
1824        }
1825        let arguments = call.child_by_field_name("arguments")?;
1826        let actuals = argument_children(arguments).collect::<Vec<_>>();
1827        let environment = self.macro_environment(file, call.start_byte());
1828        let function_name = node_text(function, source);
1829        let binding = environment.binding(function_name)?;
1830        let MacroDefinition::Function {
1831            parameters,
1832            replacement,
1833        } = &binding.definition
1834        else {
1835            return None;
1836        };
1837        if actuals.len() != parameters.len() {
1838            return None;
1839        }
1840        let template = self.macro_local_binding_template(binding, parameters, replacement)?;
1841        let (type_name, type_node) = match &template.declared_type {
1842            MacroLocalBindingTypeTemplate::Parameter(index) => {
1843                let actual = *actuals.get(*index)?;
1844                if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
1845                    return None;
1846                }
1847                (node_text(actual, source).trim().to_string(), Some(actual))
1848            }
1849            MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
1850        };
1851        if type_name.is_empty() {
1852            return None;
1853        }
1854        Some(MacroLocalBinding {
1855            name: template.name.clone(),
1856            type_name,
1857            type_node,
1858            pointer_depth: template.pointer_depth,
1859        })
1860    }
1861
1862    fn macro_local_binding_template(
1863        &self,
1864        binding: &MacroBinding,
1865        parameters: &[String],
1866        replacement: &str,
1867    ) -> Option<Arc<MacroLocalBindingTemplate>> {
1868        let key = (binding.source.clone(), binding.declaration_byte);
1869        let mut cache = self
1870            .macro_local_binding_templates
1871            .lock()
1872            .expect("C++ macro local-binding cache poisoned");
1873        if let Some(template) = cache.get(&key) {
1874            return template.clone();
1875        }
1876        let sentinel = format!("void __bifrost_macro_local() {{ {replacement}; }}");
1877        let template = (|| {
1878            let mut parser = Parser::new();
1879            parser
1880                .set_language(&tree_sitter_cpp::LANGUAGE.into())
1881                .ok()?;
1882            let tree = parser.parse(&sentinel, None)?;
1883            if tree.root_node().has_error() {
1884                return None;
1885            }
1886            let function = first_descendant_of_kind(tree.root_node(), "function_definition")?;
1887            let body = function.child_by_field_name("body")?;
1888            if body.named_child_count() != 1 {
1889                return None;
1890            }
1891            let declaration = body.named_child(0)?;
1892            if declaration.kind() != "declaration" {
1893                return None;
1894            }
1895            let type_node = declaration
1896                .child_by_field_name("type")
1897                .or_else(|| first_type_child(declaration))?;
1898            let declarator = declaration.child_by_field_name("declarator").or_else(|| {
1899                let mut cursor = declaration.walk();
1900                declaration.named_children(&mut cursor).find_map(|child| {
1901                    if child.kind() == "init_declarator" {
1902                        child.child_by_field_name("declarator")
1903                    } else {
1904                        is_declarator_node(child).then_some(child)
1905                    }
1906                })
1907            })?;
1908            let name = extract_variable_name(declarator, &sentinel)?;
1909            let pointer_depth =
1910                declared_name_indirection(declaration, type_node, &name, &sentinel)?;
1911            let type_text = node_text(type_node, &sentinel).trim();
1912            let declared_type = parameters
1913                .iter()
1914                .position(|parameter| parameter == type_text)
1915                .map(MacroLocalBindingTypeTemplate::Parameter)
1916                .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
1917            Some(Arc::new(MacroLocalBindingTemplate {
1918                name,
1919                declared_type,
1920                pointer_depth,
1921            }))
1922        })();
1923        cache.insert(key, template.clone());
1924        template
1925    }
1926
1927    fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
1928        let Some(value) = node.child_by_field_name("value") else {
1929            return MacroDefinition::Unsupported;
1930        };
1931        let replacement = node_text(value, source).to_string();
1932        if node.kind() == "preproc_def" {
1933            return MacroDefinition::Object { replacement };
1934        }
1935        let Some(parameters) = node.child_by_field_name("parameters") else {
1936            return MacroDefinition::Unsupported;
1937        };
1938        if (0..parameters.child_count()).any(|index| {
1939            parameters
1940                .child(index)
1941                .is_some_and(|child| child.kind() == "...")
1942        }) {
1943            return MacroDefinition::Unsupported;
1944        }
1945        let parameters = (0..parameters.named_child_count())
1946            .filter_map(|index| parameters.named_child(index))
1947            .map(|parameter| node_text(parameter, source).to_string())
1948            .collect();
1949        MacroDefinition::Function {
1950            parameters,
1951            replacement,
1952        }
1953    }
1954
1955    pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
1956        self.macro_event_cells
1957            .lock()
1958            .expect("C++ macro event cache poisoned")
1959            .entry(file.clone())
1960            .or_default()
1961            .clone()
1962    }
1963
1964    pub fn macro_environment_cursor_cell(&self, file: &ProjectFile) -> MacroEnvironmentCursorCell {
1965        let key = (file.clone(), std::thread::current().id());
1966        self.macro_environment_cursors
1967            .lock()
1968            .expect("C++ macro environment cursor cache poisoned")
1969            .entry(key)
1970            .or_default()
1971            .clone()
1972    }
1973
1974    pub fn macro_environment(
1975        &self,
1976        file: &ProjectFile,
1977        before_byte: usize,
1978    ) -> Arc<MacroEnvironment> {
1979        let cell = self.macro_event_cell(file);
1980        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
1981        let frontier = events.partition_point(|event| event.byte() < before_byte);
1982        let cursor_cell = self.macro_environment_cursor_cell(file);
1983        let mut cursor = cursor_cell
1984            .lock()
1985            .expect("C++ macro environment cursor poisoned");
1986        if frontier < cursor.frontier {
1987            *cursor = MacroEnvironmentCursor::default();
1988        }
1989        // Seed the TU's build-proven defines once, before any event applies
1990        // (#2011). They are facts of the whole compile, so they hold from the
1991        // first byte; a later explicit #undef event still overrides them
1992        // through `known_undefined_names`.
1993        if cursor.frontier == 0 {
1994            let proven = self.compile_proven_guards(file);
1995            if !proven.is_empty() && cursor.environment.build_proven_defines.len() != proven.len() {
1996                Arc::make_mut(&mut cursor.environment).build_proven_defines = proven
1997                    .iter()
1998                    .filter_map(|guard| match guard {
1999                        PreprocessorGuard::Defined(name) => Some(name.clone()),
2000                        _ => None,
2001                    })
2002                    .collect();
2003            }
2004        }
2005        if frontier > cursor.frontier {
2006            #[cfg(any(test, feature = "test-support"))]
2007            if Arc::strong_count(&cursor.environment) > 1 {
2008                self.macro_environment_copy_count
2009                    .fetch_add(1, Ordering::Relaxed);
2010            }
2011            let start = cursor.frontier;
2012            let environment = Arc::make_mut(&mut cursor.environment);
2013            let mut include_stack = HashSet::from_iter([file.clone()]);
2014            for event in &events[start..frontier] {
2015                self.apply_macro_event(file, event, environment, &mut include_stack);
2016            }
2017            cursor.frontier = frontier;
2018        }
2019        Arc::clone(&cursor.environment)
2020    }
2021
2022    /// Whether `name` is bound as a macro at `before_byte` in `file`,
2023    /// including a binding this environment cannot pin to one replacement
2024    /// (a conditional `#define`, or a function-like macro).
2025    ///
2026    /// [`Self::object_macro_replacement_at`] collapses every such binding to
2027    /// `None`, which is indistinguishable from "not a macro at all". A caller
2028    /// that must not read a macro token as an ordinary type name needs the two
2029    /// apart: an unexpandable macro is an unknown, a plain identifier is not.
2030    pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
2031        self.macro_environment(file, before_byte)
2032            .binding(name)
2033            .is_some()
2034    }
2035
2036    pub fn macro_name_may_be_bound_at(
2037        &self,
2038        file: &ProjectFile,
2039        name: &str,
2040        before_byte: usize,
2041    ) -> bool {
2042        self.macro_environment(file, before_byte).may_bind(name)
2043    }
2044
2045    /// Whether the active macro binding at this reference is the requested
2046    /// indexed definition. Name equality alone is not enough because two
2047    /// headers can define the same macro for different translation units.
2048    pub fn macro_binding_matches_target_at(
2049        &self,
2050        analyzer: &CppGraphSource<'_>,
2051        file: &ProjectFile,
2052        name: &str,
2053        before_byte: usize,
2054        target: &CodeUnit,
2055    ) -> bool {
2056        let environment = self.macro_environment(file, before_byte);
2057        let Some(binding) = environment.binding(name) else {
2058            return false;
2059        };
2060        // A normal header guard makes the replacement text conditional, but
2061        // it does not erase the definition site's source and byte identity.
2062        // Keep that identity even when expansion details are not exact.
2063        if binding.source != *target.source() {
2064            return false;
2065        }
2066        let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
2067            return false;
2068        };
2069        analyzer.ranges(target).iter().any(|range| {
2070            let Some(mut node) = node_for_exact_range(prepared.tree().root_node(), range) else {
2071                return false;
2072            };
2073            while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
2074                let Some(parent) = node.parent() else {
2075                    return false;
2076                };
2077                node = parent;
2078            }
2079            node.start_byte() == binding.declaration_byte
2080        })
2081    }
2082
2083    /// Resolve an ordinary expression-position macro token at its exact byte.
2084    ///
2085    /// Calls and preprocessor-condition tokens have separate resolution
2086    /// surfaces. Declaration names, macro parameters, and labels are not
2087    /// references. Keeping that role policy here makes forward and both
2088    /// inverse graph builders consume the same activation verdict (#2093).
2089    pub fn resolve_ordinary_macro_reference(
2090        &self,
2091        analyzer: &CppGraphSource<'_>,
2092        file: &ProjectFile,
2093        node: Node<'_>,
2094        source: &str,
2095    ) -> OrdinaryMacroReferenceResolution {
2096        if !is_ordinary_macro_reference_node(node) {
2097            return OrdinaryMacroReferenceResolution::Missing;
2098        }
2099        let name = node_text(node, source);
2100        if name.is_empty() {
2101            return OrdinaryMacroReferenceResolution::Missing;
2102        }
2103        let visible = self
2104            .visible_identifier_candidates(file, name)
2105            .filter(|candidate| candidate.is_macro())
2106            .cloned()
2107            .collect::<Vec<_>>();
2108        let mut exact = Vec::new();
2109        for candidate in &visible {
2110            if self.macro_binding_matches_target_at(
2111                analyzer,
2112                file,
2113                name,
2114                node.start_byte(),
2115                candidate,
2116            ) && !exact
2117                .iter()
2118                .any(|existing| same_visible_symbol(existing, candidate))
2119            {
2120                exact.push(candidate.clone());
2121            }
2122        }
2123        match exact.len() {
2124            1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
2125            2.. => OrdinaryMacroReferenceResolution::Ambiguous,
2126            0 if !visible.is_empty()
2127                && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
2128            {
2129                OrdinaryMacroReferenceResolution::Ambiguous
2130            }
2131            0 => OrdinaryMacroReferenceResolution::Missing,
2132        }
2133    }
2134
2135    /// Collect reference-capable C tokens beneath tree-sitter recovery nodes.
2136    ///
2137    /// The ordinary census deliberately skips every `ERROR` subtree. This
2138    /// separate, precision-only frontier admits only roles that retain enough
2139    /// structure for the C usage graph to interpret independently (#2089).
2140    /// Macro evidence comes from this visibility index at the exact byte; no
2141    /// source-text parsing or terminal-name fallback is used.
2142    pub fn recovered_c_reference_ranges(
2143        &self,
2144        file: &ProjectFile,
2145        root: Node<'_>,
2146        source: &str,
2147        limit: usize,
2148    ) -> RecoveredCReferenceRanges {
2149        if !is_c_source_file(file) {
2150            return RecoveredCReferenceRanges::Complete(Vec::new());
2151        }
2152        let mut ranges = Vec::new();
2153        let mut seen = HashSet::default();
2154        let mut stack = vec![(root, root.is_error())];
2155        while let Some((node, inside_error)) = stack.pop() {
2156            let inside_error = inside_error || node.is_error();
2157            if inside_error
2158                && recovered_c_reference_node(self, file, node, source)
2159                && seen.insert((node.start_byte(), node.end_byte()))
2160            {
2161                if ranges.len() == limit {
2162                    return RecoveredCReferenceRanges::LimitExceeded;
2163                }
2164                ranges.push(Range {
2165                    start_byte: node.start_byte(),
2166                    end_byte: node.end_byte(),
2167                    start_line: node.start_position().row,
2168                    end_line: node.end_position().row,
2169                });
2170            }
2171            let mut cursor = node.walk();
2172            for child in node.named_children(&mut cursor) {
2173                stack.push((child, inside_error));
2174            }
2175        }
2176        ranges.sort_unstable();
2177        RecoveredCReferenceRanges::Complete(ranges)
2178    }
2179
2180    /// Whether this target is an indexed macro visible from this file.
2181    ///
2182    /// An unresolved conditional can make more than one same-name macro a
2183    /// possible active binding. Each possible target can keep the site as an
2184    /// unproven hit. A macro in an unrelated translation unit stays excluded.
2185    pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2186        self.visible_identifier_candidates(file, target.identifier())
2187            .filter(|candidate| candidate.is_macro())
2188            .any(|candidate| {
2189                candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
2190            })
2191    }
2192
2193    pub fn object_macro_replacement_at(
2194        &self,
2195        file: &ProjectFile,
2196        name: &str,
2197        before_byte: usize,
2198    ) -> Option<String> {
2199        let environment = self.macro_environment(file, before_byte);
2200        let binding = environment.binding(name)?;
2201        if !binding.exact {
2202            return None;
2203        }
2204        match &binding.definition {
2205            MacroDefinition::Object { replacement } => Some(replacement.clone()),
2206            MacroDefinition::Function { .. } | MacroDefinition::Unsupported => None,
2207        }
2208    }
2209
2210    fn apply_macro_events(
2211        &self,
2212        file: &ProjectFile,
2213        before_byte: Option<usize>,
2214        environment: &mut MacroEnvironment,
2215        include_stack: &mut HashSet<ProjectFile>,
2216    ) {
2217        if !include_stack.insert(file.clone()) {
2218            return;
2219        }
2220        if self.cpp.prepared_syntax(self.token, file).is_none() {
2221            environment.mark_unknown_names(file, before_byte.unwrap_or_default());
2222            include_stack.remove(file);
2223            return;
2224        }
2225        match self.macro_include_protection(file) {
2226            MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
2227                Some(binding) if binding.is_exact() => {
2228                    include_stack.remove(file);
2229                    return;
2230                }
2231                Some(_) | None if environment.unknown_names => {
2232                    let mut ambiguous_seen = HashSet::default();
2233                    self.mark_macro_events_ambiguous(
2234                        file,
2235                        environment,
2236                        &mut ambiguous_seen,
2237                        file,
2238                        before_byte.unwrap_or_default(),
2239                    );
2240                    include_stack.remove(file);
2241                    return;
2242                }
2243                Some(_) => {
2244                    let mut ambiguous_seen = HashSet::default();
2245                    self.mark_macro_events_ambiguous(
2246                        file,
2247                        environment,
2248                        &mut ambiguous_seen,
2249                        file,
2250                        before_byte.unwrap_or_default(),
2251                    );
2252                    include_stack.remove(file);
2253                    return;
2254                }
2255                None => {}
2256            },
2257            MacroIncludeProtection::PragmaOnce => {
2258                if !environment.applied_pragma_once_files.insert(file.clone()) {
2259                    include_stack.remove(file);
2260                    return;
2261                }
2262                if environment.maybe_applied_pragma_once_files.remove(file) {
2263                    // A prior conditional include may already have consumed the pragma-once
2264                    // header. This unconditional include guarantees it is consumed now, but
2265                    // cannot prove whether its events occur before or after intervening local
2266                    // macro changes, so preserve the union as ambiguous.
2267                    let mut ambiguous_seen = HashSet::default();
2268                    environment.applied_pragma_once_files.remove(file);
2269                    self.mark_macro_events_ambiguous(
2270                        file,
2271                        environment,
2272                        &mut ambiguous_seen,
2273                        file,
2274                        before_byte.unwrap_or_default(),
2275                    );
2276                    environment.maybe_applied_pragma_once_files.remove(file);
2277                    environment.applied_pragma_once_files.insert(file.clone());
2278                    include_stack.remove(file);
2279                    return;
2280                }
2281            }
2282            MacroIncludeProtection::None => {}
2283        }
2284        let cell = self.macro_event_cell(file);
2285        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2286        for event in events {
2287            if before_byte.is_some_and(|limit| event.byte() >= limit) {
2288                break;
2289            }
2290            self.apply_macro_event(file, event, environment, include_stack);
2291        }
2292        include_stack.remove(file);
2293    }
2294
2295    fn apply_macro_event(
2296        &self,
2297        file: &ProjectFile,
2298        event: &MacroEvent,
2299        environment: &mut MacroEnvironment,
2300        include_stack: &mut HashSet<ProjectFile>,
2301    ) {
2302        #[cfg(any(test, feature = "test-support"))]
2303        self.macro_event_application_count
2304            .fetch_add(1, Ordering::Relaxed);
2305        match event {
2306            MacroEvent::Define {
2307                name,
2308                binding,
2309                conditional,
2310                byte,
2311            } => {
2312                if *conditional {
2313                    Self::merge_conditional_macro_definition(
2314                        environment,
2315                        name,
2316                        binding,
2317                        file,
2318                        *byte,
2319                    );
2320                } else {
2321                    environment.insert(name.clone(), binding.clone());
2322                }
2323            }
2324            MacroEvent::Undef {
2325                name,
2326                conditional,
2327                byte,
2328            } => {
2329                if *conditional {
2330                    if environment.binding(name).is_some() {
2331                        environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
2332                    }
2333                } else {
2334                    environment.remove(name);
2335                }
2336            }
2337            MacroEvent::Include {
2338                targets,
2339                conditional,
2340                byte,
2341            } => {
2342                if targets.is_empty() {
2343                    environment.mark_unknown_names(file, *byte);
2344                    return;
2345                }
2346                if *conditional || targets.len() > 1 {
2347                    let mut ambiguous_seen = HashSet::default();
2348                    for target in targets {
2349                        self.mark_macro_events_ambiguous(
2350                            target,
2351                            environment,
2352                            &mut ambiguous_seen,
2353                            file,
2354                            *byte,
2355                        );
2356                    }
2357                } else if let Some(target) = targets.first() {
2358                    self.apply_macro_events(target, None, environment, include_stack);
2359                }
2360            }
2361            MacroEvent::Invalidate { byte } => {
2362                for binding in environment.bindings.values_mut() {
2363                    *binding = MacroBinding::uncertain_from(binding, file, *byte);
2364                }
2365            }
2366        }
2367    }
2368
2369    fn mark_macro_events_ambiguous(
2370        &self,
2371        file: &ProjectFile,
2372        environment: &mut MacroEnvironment,
2373        include_stack: &mut HashSet<ProjectFile>,
2374        conditional_file: &ProjectFile,
2375        conditional_byte: usize,
2376    ) {
2377        if !include_stack.insert(file.clone()) {
2378            return;
2379        }
2380        if self.cpp.prepared_syntax(self.token, file).is_none() {
2381            environment.mark_unknown_names(conditional_file, conditional_byte);
2382            return;
2383        }
2384        match self.macro_include_protection(file) {
2385            MacroIncludeProtection::MacroGuard(guard) => {
2386                if environment
2387                    .binding(&guard)
2388                    .is_some_and(MacroBinding::is_exact)
2389                {
2390                    return;
2391                }
2392            }
2393            MacroIncludeProtection::PragmaOnce => {
2394                if environment.applied_pragma_once_files.contains(file) {
2395                    return;
2396                }
2397                environment
2398                    .maybe_applied_pragma_once_files
2399                    .insert(file.clone());
2400            }
2401            MacroIncludeProtection::None => {}
2402        }
2403        let cell = self.macro_event_cell(file);
2404        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2405        for event in events {
2406            #[cfg(any(test, feature = "test-support"))]
2407            self.macro_event_application_count
2408                .fetch_add(1, Ordering::Relaxed);
2409            match event {
2410                MacroEvent::Define { name, binding, .. } => {
2411                    Self::merge_conditional_macro_definition(
2412                        environment,
2413                        name,
2414                        binding,
2415                        conditional_file,
2416                        conditional_byte,
2417                    );
2418                }
2419                MacroEvent::Undef { name, .. } => {
2420                    if environment.binding(name).is_some() {
2421                        environment.insert(
2422                            name.clone(),
2423                            MacroBinding::ambiguous(conditional_file, conditional_byte),
2424                        );
2425                    } else {
2426                        environment.remove_known_undefined(name);
2427                    }
2428                }
2429                MacroEvent::Include { targets, .. } => {
2430                    if targets.is_empty() {
2431                        environment.mark_unknown_names(conditional_file, conditional_byte);
2432                        continue;
2433                    }
2434                    for target in targets {
2435                        self.mark_macro_events_ambiguous(
2436                            target,
2437                            environment,
2438                            include_stack,
2439                            conditional_file,
2440                            conditional_byte,
2441                        );
2442                    }
2443                }
2444                MacroEvent::Invalidate { .. } => {
2445                    for binding in environment.bindings.values_mut() {
2446                        *binding = MacroBinding::uncertain_from(
2447                            binding,
2448                            conditional_file,
2449                            conditional_byte,
2450                        );
2451                    }
2452                }
2453            }
2454        }
2455    }
2456
2457    fn merge_conditional_macro_definition(
2458        environment: &mut MacroEnvironment,
2459        name: &str,
2460        possible_binding: &MacroBinding,
2461        conditional_file: &ProjectFile,
2462        conditional_byte: usize,
2463    ) {
2464        // A conditional include can revisit an already-active guarded header.
2465        // If the possible branch defines the exact same macro, both outcomes
2466        // leave the binding unchanged; degrading it to Unknown would discard
2467        // proof because of an unrelated unresolved macro name (#2092).
2468        if environment.binding(name).is_some_and(|current| {
2469            current.definition != MacroDefinition::Unsupported
2470                && current.definition == possible_binding.definition
2471        }) {
2472            return;
2473        }
2474        environment.insert(
2475            name.to_string(),
2476            MacroBinding::ambiguous(conditional_file, conditional_byte),
2477        );
2478    }
2479
2480    pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
2481        let cell = self
2482            .macro_include_protection_cells
2483            .lock()
2484            .expect("C++ include protection cache poisoned")
2485            .entry(file.clone())
2486            .or_default()
2487            .clone();
2488        cell.get_or_init(|| {
2489            self.cpp.prepared_syntax(self.token, file).map_or(
2490                MacroIncludeProtection::None,
2491                |prepared| {
2492                    top_level_macro_include_protection(
2493                        prepared.tree().root_node(),
2494                        prepared.source(),
2495                    )
2496                },
2497            )
2498        })
2499        .clone()
2500    }
2501
2502    fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
2503        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
2504            return Vec::new();
2505        };
2506        let source = prepared.source();
2507        let mut events = Vec::new();
2508        let mut stack = vec![prepared.tree().root_node()];
2509        while let Some(node) = stack.pop() {
2510            let conditional = has_preprocessor_conditional_ancestor(node, source);
2511            match node.kind() {
2512                "preproc_def" | "preproc_function_def" => {
2513                    let Some(name) = node.child_by_field_name("name") else {
2514                        continue;
2515                    };
2516                    let name = node_text(name, source).to_string();
2517                    events.push(MacroEvent::Define {
2518                        name,
2519                        binding: MacroBinding {
2520                            source: file.clone(),
2521                            declaration_byte: node.start_byte(),
2522                            definition: Self::decode_macro_definition(node, source),
2523                            exact: true,
2524                        },
2525                        byte: node.start_byte(),
2526                        conditional,
2527                    });
2528                    continue;
2529                }
2530                "preproc_include" => {
2531                    let Some(path) = node.child_by_field_name("path") else {
2532                        events.push(MacroEvent::Include {
2533                            targets: Vec::new(),
2534                            byte: node.start_byte(),
2535                            conditional,
2536                        });
2537                        continue;
2538                    };
2539                    let targets =
2540                        structured_include_path(path, source).map_or_else(Vec::new, |path| {
2541                            resolve_include_targets_with_index(
2542                                file,
2543                                path,
2544                                self.cpp.include_target_index(),
2545                            )
2546                        });
2547                    // An unresolved angle-bracket include crosses into an external system
2548                    // boundary that is absent from the source index. It must not poison all
2549                    // later local macro evidence. Quoted/project-local and computed includes,
2550                    // by contrast, may hide indexed macro state and therefore fail closed.
2551                    if targets.is_empty() && path.kind() == "system_lib_string" {
2552                        continue;
2553                    }
2554                    events.push(MacroEvent::Include {
2555                        targets,
2556                        byte: node.start_byte(),
2557                        conditional,
2558                    });
2559                    continue;
2560                }
2561                "preproc_call" => {
2562                    let Some(directive) = node.child_by_field_name("directive") else {
2563                        continue;
2564                    };
2565                    if node_text(directive, source) != "#undef" {
2566                        continue;
2567                    }
2568                    let name = node
2569                        .child_by_field_name("argument")
2570                        .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
2571                    if let Some(name) = name {
2572                        events.push(MacroEvent::Undef {
2573                            name,
2574                            byte: node.start_byte(),
2575                            conditional,
2576                        });
2577                    } else {
2578                        events.push(MacroEvent::Invalidate {
2579                            byte: node.start_byte(),
2580                        });
2581                    }
2582                    continue;
2583                }
2584                _ => {}
2585            }
2586            for index in (0..node.named_child_count()).rev() {
2587                if let Some(child) = node.named_child(index) {
2588                    stack.push(child);
2589                }
2590            }
2591        }
2592        events.sort_by_key(MacroEvent::byte);
2593        events
2594    }
2595
2596    pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
2597        self.ordinary_type_import_cells
2598            .lock()
2599            .expect("C++ ordinary type import cache poisoned")
2600            .entry(file.clone())
2601            .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
2602            .clone()
2603    }
2604
2605    pub fn project_using_index(
2606        &self,
2607        build: impl FnOnce() -> ProjectUsingIndex,
2608    ) -> &ProjectUsingIndex {
2609        self.project_using_index.get_or_init(build)
2610    }
2611
2612    pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
2613        let mut files = self
2614            .visible_source_files_by_root
2615            .values()
2616            .flatten()
2617            .cloned()
2618            .collect::<HashSet<_>>()
2619            .into_iter()
2620            .collect::<Vec<_>>();
2621        files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
2622        files
2623    }
2624
2625    pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
2626        self.visible_source_files_by_root
2627            .get(root)
2628            .is_some_and(|files| files.contains(source))
2629    }
2630
2631    fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
2632        let cached = self
2633            .visible_parser_alias_name_sets
2634            .read()
2635            .expect("visible parser alias-name cache poisoned")
2636            .get(file)
2637            .cloned();
2638        let cell = if let Some(cached) = cached {
2639            cached
2640        } else {
2641            let mut cells = self
2642                .visible_parser_alias_name_sets
2643                .write()
2644                .expect("visible parser alias-name cache poisoned");
2645            Arc::clone(
2646                cells
2647                    .entry(file.clone())
2648                    .or_insert_with(|| Arc::new(OnceLock::new())),
2649            )
2650        };
2651        cell.get_or_init(|| {
2652            #[cfg(any(test, feature = "test-support"))]
2653            self.visible_parser_alias_name_set_build_count
2654                .fetch_add(1, Ordering::Relaxed);
2655            let mut names = HashSet::default();
2656            let visible_files = self
2657                .visible_source_files_by_root
2658                .get(file)
2659                .cloned()
2660                .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
2661            for visible_file in visible_files {
2662                let aliases = {
2663                    let mut cells = self.alias_cells.lock().expect("alias cell map lock");
2664                    Arc::clone(
2665                        cells
2666                            .entry(visible_file.clone())
2667                            .or_insert_with(|| Arc::new(OnceLock::new())),
2668                    )
2669                };
2670                for alias in aliases
2671                    .get_or_init(|| {
2672                        #[cfg(any(test, feature = "test-support"))]
2673                        {
2674                            *self
2675                                .alias_source_parse_counts
2676                                .lock()
2677                                .expect("alias source parse count lock")
2678                                .entry(visible_file.clone())
2679                                .or_default() += 1;
2680                        }
2681                        aliases_from_prepared_source(self.cpp, self.token, &visible_file)
2682                            .into_boxed_slice()
2683                    })
2684                    .iter()
2685                {
2686                    names.insert(alias.name.clone());
2687                }
2688            }
2689            names
2690        })
2691        .contains(name)
2692    }
2693
2694    fn visible_parser_alias_names_for_target(
2695        &self,
2696        file: &ProjectFile,
2697        target: &CodeUnit,
2698    ) -> HashSet<String> {
2699        let cell = {
2700            let mut cells = self
2701                .visible_parser_alias_target_names
2702                .lock()
2703                .expect("visible parser alias-target cache poisoned");
2704            Arc::clone(
2705                cells
2706                    .entry(file.clone())
2707                    .or_insert_with(|| Arc::new(OnceLock::new())),
2708            )
2709        };
2710        let target_name = cpp_name_for(target);
2711        cell.get_or_init(|| {
2712            #[cfg(any(test, feature = "test-support"))]
2713            self.visible_parser_alias_target_names_build_count
2714                .fetch_add(1, Ordering::Relaxed);
2715            let visible_files = self
2716                .visible_source_files_by_root
2717                .get(file)
2718                .cloned()
2719                .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
2720            let mut names_by_target = HashMap::<String, HashSet<String>>::default();
2721            for visible_file in visible_files {
2722                let aliases = {
2723                    let mut cells = self.alias_cells.lock().expect("alias cell map lock");
2724                    Arc::clone(
2725                        cells
2726                            .entry(visible_file.clone())
2727                            .or_insert_with(|| Arc::new(OnceLock::new())),
2728                    )
2729                };
2730                for alias in aliases
2731                    .get_or_init(|| {
2732                        #[cfg(any(test, feature = "test-support"))]
2733                        {
2734                            *self
2735                                .alias_source_parse_counts
2736                                .lock()
2737                                .expect("alias source parse count lock")
2738                                .entry(visible_file.clone())
2739                                .or_default() += 1;
2740                        }
2741                        aliases_from_prepared_source(self.cpp, self.token, &visible_file)
2742                            .into_boxed_slice()
2743                    })
2744                    .iter()
2745                {
2746                    for target_name in parser_alias_target_names(alias) {
2747                        names_by_target
2748                            .entry(target_name)
2749                            .or_default()
2750                            .insert(alias.name.clone());
2751                    }
2752                }
2753            }
2754            names_by_target
2755        })
2756        .get(&target_name)
2757        .cloned()
2758        .unwrap_or_default()
2759    }
2760
2761    fn callable_arities_for_target(
2762        &self,
2763        analyzer: &CppGraphSource<'_>,
2764        cpp: &dyn CppSource,
2765        file: &ProjectFile,
2766        prepared: &PreparedSyntaxTree,
2767        spec: &TargetSpec,
2768    ) -> Vec<ActivatedCallableArity> {
2769        let Some(signature) = spec.target.signature() else {
2770            return Vec::new();
2771        };
2772        let Some(candidates) = self
2773            .visible_by_identifier
2774            .get(file)
2775            .and_then(|by_name| by_name.get(&spec.member_name))
2776        else {
2777            return Vec::new();
2778        };
2779        let differing_candidates = candidates
2780            .iter()
2781            .filter(|candidate| {
2782                candidate.is_function()
2783                    && candidate.fq_name() == spec.target.fq_name()
2784                    && candidate.signature() == Some(signature)
2785            })
2786            .filter_map(|candidate| {
2787                analyzer
2788                    .signature_metadata(candidate)
2789                    .into_iter()
2790                    .find_map(|metadata| metadata.callable_arity())
2791                    .filter(|arity| Some(*arity) != spec.callable_arity)
2792                    .map(|arity| (candidate, arity))
2793            })
2794            .collect::<Vec<_>>();
2795        if differing_candidates.is_empty() {
2796            return Vec::new();
2797        }
2798        let mut arities = Vec::with_capacity(differing_candidates.len());
2799        // The activation ranges here describe the whole file rather than one
2800        // reference, so there is no reference guard environment to consult.
2801        let reference = CallableReferenceContext {
2802            file,
2803            position: None,
2804        };
2805        for (candidate, candidate_arity) in differing_candidates {
2806            let declaration_activation = if candidate.source() == file {
2807                callable_declaration_activation_in_file(analyzer, prepared, candidate, &reference)
2808            } else {
2809                cpp.prepared_syntax(self.token, candidate.source())
2810                    .and_then(|syntax| {
2811                        callable_declaration_activation_in_file(
2812                            analyzer,
2813                            syntax.as_ref(),
2814                            candidate,
2815                            &reference,
2816                        )
2817                    })
2818            };
2819            let Some(declaration_activation) = declaration_activation else {
2820                continue;
2821            };
2822            let activation_byte = if candidate.source() == file {
2823                Some(declaration_activation)
2824            } else {
2825                self.include_activation_for_source(cpp, file, prepared, candidate.source())
2826            };
2827            if let Some(activation_byte) = activation_byte {
2828                arities.push(ActivatedCallableArity {
2829                    activation_byte,
2830                    arity: candidate_arity,
2831                });
2832            }
2833        }
2834        arities
2835    }
2836
2837    fn callable_parameter_macro_arity(
2838        &self,
2839        target: &CodeUnit,
2840        signature: Option<&str>,
2841    ) -> Option<CallableArity> {
2842        let parameter_types = cpp_signature_param_types(signature?)?;
2843        let [macro_name] = parameter_types.as_slice() else {
2844            return None;
2845        };
2846        if macro_name.is_empty()
2847            || !macro_name
2848                .chars()
2849                .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
2850        {
2851            return None;
2852        }
2853        let cache_key = (target.source().clone(), macro_name.clone());
2854        if let Some(cached) = self
2855            .callable_parameter_macro_arities
2856            .lock()
2857            .expect("C++ callable parameter-macro arity cache poisoned")
2858            .get(&cache_key)
2859            .copied()
2860        {
2861            return cached;
2862        }
2863        let mut visible_files = HashSet::default();
2864        collect_include_closure(
2865            &self.cpp_source(),
2866            self.cpp.include_target_index(),
2867            target.source(),
2868            &mut visible_files,
2869            None,
2870        );
2871        let mut arities = Vec::new();
2872        for visible_file in visible_files {
2873            let cell = self.macro_event_cell(&visible_file);
2874            for event in
2875                cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
2876            {
2877                let MacroEvent::Define { name, binding, .. } = event else {
2878                    continue;
2879                };
2880                if name != macro_name {
2881                    continue;
2882                }
2883                let MacroDefinition::Object { replacement } = &binding.definition else {
2884                    continue;
2885                };
2886                let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
2887                    continue;
2888                };
2889                if !arities.contains(&arity) {
2890                    arities.push(arity);
2891                }
2892            }
2893        }
2894        let resolved = (|| {
2895            let required = arities
2896                .iter()
2897                .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
2898                .min()?;
2899            let total = arities.iter().map(|arity| arity.total()).max()?;
2900            let repeated = arities
2901                .iter()
2902                .any(|arity| arity.accepts(arity.total().saturating_add(1)));
2903            // Preprocessor conditions can leave more than one object-like parameter
2904            // bundle active in the target header's include closure. Preserve their
2905            // conservative callable envelope instead of choosing whichever definition
2906            // happened to be visited first.
2907            Some(CallableArity::new(required, total, repeated))
2908        })();
2909        self.callable_parameter_macro_arities
2910            .lock()
2911            .expect("C++ callable parameter-macro arity cache poisoned")
2912            .insert(cache_key, resolved);
2913        resolved
2914    }
2915
2916    pub fn include_activation_for_source(
2917        &self,
2918        cpp: &dyn CppSource,
2919        file: &ProjectFile,
2920        prepared: &PreparedSyntaxTree,
2921        donor_source: &ProjectFile,
2922    ) -> Option<usize> {
2923        let key = (file.clone(), donor_source.clone());
2924        if let Some(cached) = self
2925            .include_activation_cells
2926            .lock()
2927            .expect("C++ include activation cache poisoned")
2928            .get(&key)
2929            .copied()
2930        {
2931            return cached;
2932        }
2933        #[cfg(any(test, feature = "test-support"))]
2934        self.include_activation_build_count
2935            .fetch_add(1, Ordering::Relaxed);
2936        let activation = find_include_activation(cpp, self.token, file, prepared, donor_source);
2937        let mut cells = self
2938            .include_activation_cells
2939            .lock()
2940            .expect("C++ include activation cache poisoned");
2941        *cells.entry(key).or_insert(activation)
2942    }
2943
2944    pub fn conditional_include_projections_for_source(
2945        &self,
2946        file: &ProjectFile,
2947        prepared: &PreparedSyntaxTree,
2948        donor_source: &ProjectFile,
2949    ) -> Arc<[ConditionalIncludeProjection]> {
2950        static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
2951        let cell = self
2952            .conditional_include_projection_cells
2953            .lock()
2954            .expect("C++ conditional include projection cache poisoned")
2955            .entry(file.clone())
2956            .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
2957            .clone();
2958        let index = cell.get_or_build_pool_independent(|| {
2959            #[cfg(any(test, feature = "test-support"))]
2960            self.conditional_include_projection_index_build_count
2961                .fetch_add(1, Ordering::Relaxed);
2962            find_conditional_include_projection_index(self.cpp, self.token, file, prepared, &|| {
2963                #[cfg(any(test, feature = "test-support"))]
2964                self.conditional_include_projection_state_count
2965                    .fetch_add(1, Ordering::Relaxed);
2966            })
2967        });
2968        index
2969            .get(donor_source)
2970            .cloned()
2971            .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
2972    }
2973
2974    #[cfg(any(test, feature = "test-support"))]
2975    pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
2976        (
2977            self.conditional_include_projection_index_build_count
2978                .load(Ordering::Relaxed),
2979            self.conditional_include_projection_state_count
2980                .load(Ordering::Relaxed),
2981        )
2982    }
2983
2984    #[cfg(any(test, feature = "test-support"))]
2985    pub fn include_activation_build_count_for_test(&self) -> usize {
2986        self.include_activation_build_count.load(Ordering::Relaxed)
2987    }
2988
2989    #[cfg(any(test, feature = "test-support"))]
2990    pub fn note_using_donor_activation_for_test(&self) {
2991        self.using_donor_activation_count
2992            .fetch_add(1, Ordering::Relaxed);
2993    }
2994
2995    #[cfg(not(any(test, feature = "test-support")))]
2996    pub fn note_using_donor_activation_for_test(&self) {}
2997
2998    #[cfg(any(test, feature = "test-support"))]
2999    pub fn note_using_namespace_lookup_for_test(&self) {
3000        self.using_namespace_lookup_count
3001            .fetch_add(1, Ordering::Relaxed);
3002    }
3003
3004    #[cfg(not(any(test, feature = "test-support")))]
3005    pub fn note_using_namespace_lookup_for_test(&self) {}
3006
3007    #[cfg(any(test, feature = "test-support"))]
3008    pub fn note_using_name_candidate_inspection_for_test(&self) {
3009        self.using_name_candidate_inspection_count
3010            .fetch_add(1, Ordering::Relaxed);
3011    }
3012
3013    #[cfg(not(any(test, feature = "test-support")))]
3014    pub fn note_using_name_candidate_inspection_for_test(&self) {}
3015
3016    #[cfg(any(test, feature = "test-support"))]
3017    pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
3018        (
3019            self.using_donor_activation_count.load(Ordering::Relaxed),
3020            self.using_namespace_lookup_count.load(Ordering::Relaxed),
3021            self.callable_reference_spec_build_count
3022                .load(Ordering::Relaxed),
3023            self.using_name_candidate_inspection_count
3024                .load(Ordering::Relaxed),
3025        )
3026    }
3027
3028    pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
3029        file == target.source()
3030            || self
3031                .visible_by_file
3032                .get(file)
3033                .is_some_and(|visible| visible.contains(target))
3034    }
3035
3036    /// Whether some declaration of `declaration`'s logical symbol is visible at
3037    /// `reference_byte` in `file`.
3038    ///
3039    /// The question is asked of the *logical* symbol, not of the physical unit:
3040    /// an out-of-line body in a `.cpp` nobody includes is never itself visible,
3041    /// and it does not have to be - what makes the call legal is the header
3042    /// declaration that the reference file does include. Reading that relation
3043    /// through `same_logical_callable` rather than through signature strings is
3044    /// the same #2010 correction the gates make, and it matters here because
3045    /// the body and the declaration are exactly the pair that spells one
3046    /// parameter type two ways.
3047    pub fn declaration_visible_at(
3048        &self,
3049        analyzer: &CppGraphSource<'_>,
3050        file: &ProjectFile,
3051        declaration: &CodeUnit,
3052        reference_byte: usize,
3053    ) -> bool {
3054        let reference_guards = OnceCell::new();
3055        self.visible_identifier_candidates(file, declaration.identifier())
3056            .filter(|candidate| {
3057                self.same_logical_callable(analyzer, candidate, declaration)
3058                    || flattened_macro_namespace_declaration_matches(
3059                        analyzer,
3060                        self.cpp,
3061                        file,
3062                        candidate,
3063                        declaration,
3064                        reference_byte,
3065                    )
3066            })
3067            .any(|candidate| {
3068                self.physical_declaration_visible_at(
3069                    analyzer,
3070                    file,
3071                    candidate,
3072                    reference_byte,
3073                    &reference_guards,
3074                )
3075            })
3076    }
3077
3078    pub fn callable_arity_at_reference(
3079        &self,
3080        analyzer: &CppGraphSource<'_>,
3081        file: &ProjectFile,
3082        candidate: &CodeUnit,
3083        reference_byte: usize,
3084    ) -> Option<CallableArity> {
3085        let key = (file.clone(), logical_symbol_key(candidate));
3086        let cell = self
3087            .callable_reference_specs
3088            .lock()
3089            .expect("C++ callable reference-spec cache poisoned")
3090            .entry(key)
3091            .or_default()
3092            .clone();
3093        let spec = cell.get_or_init(|| {
3094            let prepared = self.cpp.prepared_syntax(self.token, file)?;
3095            let spec = TargetSpec::from_target(analyzer, candidate)?;
3096            let spec = spec
3097                .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
3098                .into_owned();
3099            #[cfg(any(test, feature = "test-support"))]
3100            self.callable_reference_spec_build_count
3101                .fetch_add(1, Ordering::Relaxed);
3102            Some(spec)
3103        });
3104        spec.as_ref()?.callable_arity_at(reference_byte)
3105    }
3106
3107    fn physical_declaration_visible_at(
3108        &self,
3109        analyzer: &CppGraphSource<'_>,
3110        file: &ProjectFile,
3111        declaration: &CodeUnit,
3112        reference_byte: usize,
3113        reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
3114    ) -> bool {
3115        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3116            return false;
3117        };
3118        let reference = CallableReferenceContext {
3119            file,
3120            position: Some(CallableReferencePosition {
3121                prepared: prepared.as_ref(),
3122                byte: reference_byte,
3123                guards: reference_guards,
3124            }),
3125        };
3126        if declaration.source() == file {
3127            return callable_declaration_activation_in_file(
3128                analyzer,
3129                prepared.as_ref(),
3130                declaration,
3131                &reference,
3132            )
3133            .or_else(|| {
3134                self.exhaustive_guard_family_activation(
3135                    analyzer,
3136                    prepared.as_ref(),
3137                    declaration,
3138                    &reference,
3139                )
3140            })
3141            .is_some_and(|activation| activation < reference_byte);
3142        }
3143        let Some(donor_syntax) = self.cpp.prepared_syntax(self.token, declaration.source()) else {
3144            return false;
3145        };
3146        if callable_declaration_activation_in_file(
3147            analyzer,
3148            donor_syntax.as_ref(),
3149            declaration,
3150            &reference,
3151        )
3152        .or_else(|| {
3153            self.exhaustive_guard_family_activation(
3154                analyzer,
3155                donor_syntax.as_ref(),
3156                declaration,
3157                &reference,
3158            )
3159        })
3160        .is_none()
3161        {
3162            return false;
3163        }
3164        declaration_guard_requirements(analyzer, self.cpp, declaration)
3165            .into_iter()
3166            .any(|(_, declaration_guards)| {
3167                self.foreign_declaration_reachable_at_reference(
3168                    file,
3169                    prepared.as_ref(),
3170                    declaration.source(),
3171                    &declaration_guards,
3172                    reference.guards(),
3173                    reference_byte,
3174                )
3175            })
3176    }
3177
3178    pub fn external_type_candidate_visible_at(
3179        &self,
3180        file: &ProjectFile,
3181        candidate: &CodeUnit,
3182        reference_byte: usize,
3183    ) -> bool {
3184        if candidate.source() == file {
3185            return true;
3186        }
3187        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3188            return false;
3189        };
3190        self.visible_identifier_candidates(file, candidate.identifier())
3191            .filter(|peer| same_logical_symbol(candidate, peer))
3192            .any(|peer| {
3193                peer.source() == file
3194                    || self
3195                        .include_activation_for_source(
3196                            self.cpp,
3197                            file,
3198                            prepared.as_ref(),
3199                            peer.source(),
3200                        )
3201                        .is_some_and(|activation| activation <= reference_byte)
3202            })
3203    }
3204
3205    pub fn external_type_declaration_visible_at(
3206        &self,
3207        file: &ProjectFile,
3208        candidate: &CodeUnit,
3209        reference_byte: usize,
3210    ) -> bool {
3211        if candidate.source() == file {
3212            return true;
3213        }
3214        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3215            return false;
3216        };
3217        self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
3218            .is_some_and(|activation| activation <= reference_byte)
3219    }
3220
3221    /// The preprocessor facts the build proves for a reference sited in
3222    /// `file` (#2011).
3223    ///
3224    /// Every `-D` that survives its command's `-D`/`-U` ordering is a positive
3225    /// `Defined` fact, and a fact holds only when every compile configuration
3226    /// that governs the file agrees on it (intersection). The facts are
3227    /// strictly additive to the reference's active guard set: they can prove a
3228    /// required guard, but the guard check itself is never weakened and no
3229    /// implication is ever inferred from source text.
3230    ///
3231    /// A file with its own database entry answers from that entry alone
3232    /// (phase 1). A header takes its context from the translation units whose
3233    /// include closure reaches it, intersected across all of them (phase 2):
3234    /// the header is compiled once per including TU, so a fact holds for a
3235    /// header-sited reference only when every one of those compilations
3236    /// proves it. A reaching TU the database does not cover proves nothing,
3237    /// which empties the intersection. A file nothing covers or reaches has
3238    /// no facts and every check runs on source structure alone.
3239    pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
3240        if let Some(cached) = self
3241            .compile_proven_guard_cells
3242            .lock()
3243            .expect("C++ compile-proven guard cache poisoned")
3244            .get(file)
3245        {
3246            return Arc::clone(cached);
3247        }
3248        let names = match context_fact_names(self.cpp.compile_contexts_for(file)) {
3249            Some(names) => names,
3250            None => {
3251                let mut translation_units = self.cpp.reaching_translation_units(file).into_iter();
3252                let seed = translation_units.next().and_then(|translation_unit| {
3253                    context_fact_names(self.cpp.compile_contexts_for(&translation_unit))
3254                });
3255                match seed {
3256                    None => HashSet::default(),
3257                    Some(mut names) => {
3258                        for translation_unit in translation_units {
3259                            let Some(reached) = context_fact_names(
3260                                self.cpp.compile_contexts_for(&translation_unit),
3261                            ) else {
3262                                names.clear();
3263                                break;
3264                            };
3265                            names.retain(|name| reached.contains(name));
3266                            if names.is_empty() {
3267                                break;
3268                            }
3269                        }
3270                        names
3271                    }
3272                }
3273            }
3274        };
3275        let proven = Arc::new(
3276            names
3277                .into_iter()
3278                .map(PreprocessorGuard::Defined)
3279                .collect::<HashSet<_>>(),
3280        );
3281        self.compile_proven_guard_cells
3282            .lock()
3283            .expect("C++ compile-proven guard cache poisoned")
3284            .insert(file.clone(), Arc::clone(&proven));
3285        proven
3286    }
3287
3288    /// Whether no compile data covers the compilations of `file`: it has no
3289    /// database entry of its own, and either nothing reaches it or some
3290    /// translation unit that reaches it has no entry. This is the state a
3291    /// regenerated `compile_commands.json` could decide; data that is present
3292    /// for every governing compilation but does not prove a guard is a
3293    /// decided conservative miss, not this state.
3294    fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
3295        if !self.cpp.compile_contexts_for(file).is_empty() {
3296            return false;
3297        }
3298        let translation_units = self.cpp.reaching_translation_units(file);
3299        translation_units.is_empty()
3300            || translation_units
3301                .iter()
3302                .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
3303    }
3304
3305    /// Whether a lookup miss for `identifier` in `file` is explainable by
3306    /// missing compile context (#2011): some same-name declaration is
3307    /// reachable through a conditional include whose required guards neither
3308    /// contradict the reference's active guards nor follow from them, and the
3309    /// translation unit has no compile-commands entry that could decide the
3310    /// question. Callers surface this as an explicit "requires compile
3311    /// context" incompleteness instead of an indistinguishable miss.
3312    ///
3313    /// A structurally disproven declaration (contradicting guards) and a TU
3314    /// whose compile context exists but does not prove the guard both answer
3315    /// `false`: those misses are decided, not incomplete.
3316    pub fn miss_requires_compile_context(
3317        &self,
3318        file: &ProjectFile,
3319        identifier: &str,
3320        reference: Node<'_>,
3321    ) -> bool {
3322        if !self.compile_context_is_absent(file) {
3323            return false;
3324        }
3325        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3326            return false;
3327        };
3328        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
3329        let reference_byte = reference.start_byte();
3330        let mut sources = self
3331            .visible_identifier_candidates(file, identifier)
3332            .map(CodeUnit::source)
3333            .filter(|source| *source != file)
3334            .collect::<Vec<_>>();
3335        sources.sort();
3336        sources.dedup();
3337        sources.into_iter().any(|declaration_source| {
3338            self.conditional_include_projections_for_source(
3339                file,
3340                prepared.as_ref(),
3341                declaration_source,
3342            )
3343            .iter()
3344            .any(|projection| {
3345                projection.activation_byte <= reference_byte
3346                    && !guard_requirements_hold_at_reference(
3347                        &projection.required_guards,
3348                        reference_guards.as_ref(),
3349                    )
3350                    && guards_compatible_at_reference(
3351                        &projection.required_guards,
3352                        reference_guards.as_ref(),
3353                    )
3354            })
3355        })
3356    }
3357
3358    /// Decide whether a declaration that lives in another file reaches a
3359    /// reference in `file`.
3360    ///
3361    /// An external header selects its declaration branch before the reference
3362    /// file is parsed. Require compatible reference guards, but do not test
3363    /// the header's guard expression for stability in the reference file: a
3364    /// `.c` translation unit can never satisfy the `#ifdef __cplusplus` that
3365    /// wraps every declaration of a portable C header, and demanding it would
3366    /// hide the whole header. Guards that the reference file imposes on its
3367    /// own `#include` still have to hold, and still have to be stable.
3368    fn foreign_declaration_reachable_at_reference(
3369        &self,
3370        file: &ProjectFile,
3371        prepared: &PreparedSyntaxTree,
3372        declaration_source: &ProjectFile,
3373        declaration_guards: &HashSet<PreprocessorGuard>,
3374        reference_guards: Option<&HashSet<PreprocessorGuard>>,
3375        reference_byte: usize,
3376    ) -> bool {
3377        // The translation unit's build-proven defines join the reference's
3378        // active guard set (#2011): a conditional include like the nng
3379        // `NNG_PLATFORM_POSIX` chain is provable only by the compile command.
3380        // A reference whose own environment is unknown stays unknown -- the
3381        // facts extend an environment, they never invent one.
3382        let proven = self.compile_proven_guards(file);
3383        let augmented;
3384        let reference_guards = match reference_guards {
3385            Some(active) if !proven.is_empty() => {
3386                augmented = active.union(&proven).cloned().collect();
3387                Some(&augmented)
3388            }
3389            other => other,
3390        };
3391        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
3392            return false;
3393        }
3394        if self
3395            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
3396            .is_some_and(|activation| activation <= reference_byte)
3397        {
3398            return true;
3399        }
3400        self.conditional_include_projections_for_source(file, prepared, declaration_source)
3401            .iter()
3402            .any(|projection| {
3403                projection.activation_byte <= reference_byte
3404                    && guard_requirements_hold_at_reference(
3405                        &projection.required_guards,
3406                        reference_guards,
3407                    )
3408                    && self.preprocessor_guards_stable_between(
3409                        file,
3410                        projection.activation_byte,
3411                        reference_byte,
3412                        &projection.required_guards,
3413                    )
3414            })
3415    }
3416
3417    pub fn external_type_candidate_visible_in_context(
3418        &self,
3419        analyzer: &CppGraphSource<'_>,
3420        file: &ProjectFile,
3421        candidate: &CodeUnit,
3422        reference: Node<'_>,
3423    ) -> bool {
3424        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3425            return false;
3426        };
3427        let macro_environment = self.macro_environment(file, reference.start_byte());
3428        let reference_guards = preprocessor_guard_environment(reference, prepared.source())
3429            .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
3430
3431        let directly_visible = self
3432            .visible_identifier_candidates(file, candidate.identifier())
3433            .filter(|peer| same_logical_symbol(candidate, peer))
3434            .any(|peer| {
3435                declaration_guard_requirements(analyzer, self.cpp, peer)
3436                    .into_iter()
3437                    .any(|(declaration_byte, declaration_guards)| {
3438                        if peer.source() == file {
3439                            return declaration_byte < reference.start_byte()
3440                                && guard_requirements_hold_at_reference(
3441                                    &declaration_guards,
3442                                    reference_guards.as_ref(),
3443                                )
3444                                && self.preprocessor_guards_stable_between(
3445                                    file,
3446                                    declaration_byte,
3447                                    reference.start_byte(),
3448                                    &declaration_guards,
3449                                );
3450                        }
3451                        self.foreign_declaration_reachable_at_reference(
3452                            file,
3453                            prepared.as_ref(),
3454                            peer.source(),
3455                            &declaration_guards,
3456                            reference_guards.as_ref(),
3457                            reference.start_byte(),
3458                        )
3459                    })
3460            });
3461        let complementary = self
3462            .visible_identifier_candidates(file, candidate.identifier())
3463            .filter(|peer| {
3464                peer.kind() == candidate.kind()
3465                    && peer.fq_name() == candidate.fq_name()
3466                    && peer.source() == candidate.source()
3467            })
3468            .collect::<Vec<_>>();
3469        // A completed #if/#else family declares the shared source-level name
3470        // before this reference. A later macro mutation cannot revoke that
3471        // declaration. The family gate below rejects declarations split across
3472        // separate conditional blocks, where mutation can change coverage.
3473        let candidate_branch_compatible = reference_guards.as_ref().is_some_and(|active| {
3474            declaration_guard_requirements(analyzer, self.cpp, candidate)
3475                .iter()
3476                .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
3477        });
3478        let complementary_visible = candidate_branch_compatible
3479            && self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate)
3480            && if candidate.source() == file {
3481                declaration_guard_requirements(analyzer, self.cpp, candidate)
3482                    .iter()
3483                    .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
3484            } else {
3485                self.include_activation_for_source(
3486                    self.cpp,
3487                    file,
3488                    prepared.as_ref(),
3489                    candidate.source(),
3490                )
3491                .is_some_and(|activation| activation <= reference.start_byte())
3492            };
3493        directly_visible || complementary_visible
3494    }
3495
3496    pub fn is_exhaustive_same_fqn_type_declaration_family(
3497        &self,
3498        analyzer: &CppGraphSource<'_>,
3499        file: &ProjectFile,
3500        candidate: &CodeUnit,
3501    ) -> bool {
3502        let candidates = self
3503            .visible_identifier_candidates(file, candidate.identifier())
3504            .filter(|peer| {
3505                peer.kind() == candidate.kind()
3506                    && peer.fq_name() == candidate.fq_name()
3507                    && peer.source() == candidate.source()
3508            })
3509            .collect::<Vec<_>>();
3510        self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
3511    }
3512
3513    /// Prove a nested type alias used as a dependent member-pointer owner when
3514    /// its owning class has mutually-exclusive declarations.  A common C++11
3515    /// compatibility shape provides the owning class in one preprocessor
3516    /// branch and aliases it to a standard-library type in the other branch;
3517    /// the nested fallback alias is therefore not itself active in every
3518    /// branch even though the qualified owner API is.
3519    ///
3520    /// This is deliberately narrower than ordinary type visibility.  The
3521    /// caller has already recovered a member-pointer owner path from the CST;
3522    /// this helper additionally requires the target's structured parent to
3523    /// match that path, physical source visibility, and exact preprocessor
3524    /// guard agreement with the parent declaration.  Only then may the
3525    /// parent's direct/complementary same-FQN visibility stand in for the
3526    /// nested terminal's active-branch check.
3527    pub fn dependent_member_pointer_alias_visible_in_context(
3528        &self,
3529        analyzer: &CppGraphSource<'_>,
3530        file: &ProjectFile,
3531        candidate: &CodeUnit,
3532        owner_components: &[String],
3533        reference: Node<'_>,
3534    ) -> bool {
3535        if !analyzer
3536            .type_alias_provider()
3537            .is_some_and(|provider| provider.is_type_alias(candidate))
3538        {
3539            return false;
3540        }
3541        let Some((terminal, owner_prefix)) = owner_components.split_last() else {
3542            return false;
3543        };
3544        if terminal != candidate.identifier()
3545            || canonical_cpp_scope_components(candidate) != owner_components
3546        {
3547            return false;
3548        }
3549        let Some(expected_parent_fq_name) =
3550            brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
3551        else {
3552            return false;
3553        };
3554        let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
3555            return false;
3556        };
3557        if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
3558            || parent_anchor.source() != candidate.source()
3559            || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
3560        {
3561            return false;
3562        }
3563
3564        // The ordinary path already handles unguarded aliases (and preserves
3565        // same-file declaration ordering).  This fallback is only for a
3566        // physically visible declaration whose guard is the owning branch's
3567        // guard, so reject a same-file declaration that appears after the
3568        // reference before considering guard compatibility.
3569        if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
3570            || candidate.source() == file
3571                && !analyzer
3572                    .ranges(candidate)
3573                    .iter()
3574                    .any(|range| range.start_byte < reference.start_byte())
3575        {
3576            return false;
3577        }
3578
3579        let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
3580        if candidate_guards.is_empty() {
3581            return false;
3582        }
3583        let same_guard_sets =
3584            |left: &[(usize, HashSet<PreprocessorGuard>)],
3585             right: &[(usize, HashSet<PreprocessorGuard>)]| {
3586                left.iter().all(|(_, left_guards)| {
3587                    right
3588                        .iter()
3589                        .any(|(_, right_guards)| left_guards == right_guards)
3590                })
3591            };
3592        let parent_candidates = self
3593            .visible_identifier_candidates(file, parent_anchor.identifier())
3594            .filter(|peer| {
3595                peer.kind() == parent_anchor.kind()
3596                    && peer.fq_name() == expected_parent_fq_name.as_str()
3597                    && peer.source() == parent_anchor.source()
3598                    && canonical_cpp_scope_components(peer) == owner_prefix
3599            })
3600            .filter_map(|peer| {
3601                let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
3602                (candidate_guards.len() == parent_guards.len()
3603                    && same_guard_sets(&candidate_guards, &parent_guards)
3604                    && same_guard_sets(&parent_guards, &candidate_guards))
3605                .then(|| (peer.clone(), parent_guards))
3606            })
3607            .collect::<Vec<_>>();
3608        let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
3609            return false;
3610        };
3611
3612        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3613            return false;
3614        };
3615        let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
3616        else {
3617            return false;
3618        };
3619        // An external header selects its declaration branch before the
3620        // reference file is parsed. Require compatible reference guards, but
3621        // do not test the header's guard expression for stability in the
3622        // reference file. Same-file aliases still require that stability.
3623        if !candidate_guards.iter().any(|(_, target_guards)| {
3624            guards_compatible_at_reference(target_guards, Some(&reference_guards))
3625                && (candidate.source() != file
3626                    || self.preprocessor_guards_stable_between(
3627                        file,
3628                        0,
3629                        reference.start_byte(),
3630                        target_guards,
3631                    ))
3632        }) {
3633            return false;
3634        }
3635
3636        self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
3637    }
3638
3639    /// Check a type candidate's preprocessor/import context without imposing
3640    /// ordinary declaration-before-reference ordering for same-file peers.
3641    ///
3642    /// C++ class scope makes member names visible throughout the complete
3643    /// class, including a trailing return type that appears before the member
3644    /// alias declaration in source order. Callers must first prove that the
3645    /// reference is inside the candidate's indexed class owner; this helper
3646    /// only relaxes the byte-order predicate while retaining guard and include
3647    /// activation checks.
3648    pub fn external_type_candidate_guard_compatible_in_context(
3649        &self,
3650        analyzer: &CppGraphSource<'_>,
3651        file: &ProjectFile,
3652        candidate: &CodeUnit,
3653        reference: Node<'_>,
3654    ) -> bool {
3655        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3656            return false;
3657        };
3658        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
3659
3660        self.visible_identifier_candidates(file, candidate.identifier())
3661            .filter(|peer| same_logical_symbol(candidate, peer))
3662            .any(|peer| {
3663                declaration_guard_requirements(analyzer, self.cpp, peer)
3664                    .into_iter()
3665                    .any(|(declaration_byte, declaration_guards)| {
3666                        if peer.source() == file {
3667                            let (start, end) = if declaration_byte <= reference.start_byte() {
3668                                (declaration_byte, reference.start_byte())
3669                            } else {
3670                                (reference.start_byte(), declaration_byte)
3671                            };
3672                            return guard_requirements_hold_at_reference(
3673                                &declaration_guards,
3674                                reference_guards.as_ref(),
3675                            ) && self.preprocessor_guards_stable_between(
3676                                file,
3677                                start,
3678                                end,
3679                                &declaration_guards,
3680                            );
3681                        }
3682                        self.foreign_declaration_reachable_at_reference(
3683                            file,
3684                            prepared.as_ref(),
3685                            peer.source(),
3686                            &declaration_guards,
3687                            reference_guards.as_ref(),
3688                            reference.start_byte(),
3689                        )
3690                    })
3691            })
3692    }
3693
3694    /// Whether a same-file callable declaration is nameable from `reference`
3695    /// after deliberately relaxing declaration-before-reference ordering.
3696    ///
3697    /// Ordinary lookup still requires an earlier declaration. Definition
3698    /// navigation for incomplete C translation units may recover a later
3699    /// definition, but only when it is at file scope and its preprocessor
3700    /// requirements hold at the call (#2404).
3701    pub fn same_file_callable_guard_compatible_ignoring_order(
3702        &self,
3703        analyzer: &CppGraphSource<'_>,
3704        file: &ProjectFile,
3705        candidate: &CodeUnit,
3706        reference: Node<'_>,
3707    ) -> bool {
3708        if candidate.source() != file || !candidate.is_callable() {
3709            return false;
3710        }
3711        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3712            return false;
3713        };
3714        let guards = OnceCell::new();
3715        let context = CallableReferenceContext {
3716            file,
3717            position: Some(CallableReferencePosition {
3718                prepared: prepared.as_ref(),
3719                byte: reference.start_byte(),
3720                guards: &guards,
3721            }),
3722        };
3723        nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
3724            .into_iter()
3725            .any(|declaration| {
3726                callable_preprocessor_context_is_visible_for_reference(
3727                    declaration,
3728                    prepared.source(),
3729                    &context,
3730                )
3731            })
3732    }
3733
3734    pub fn type_candidate_may_be_visible_before_reference(
3735        &self,
3736        analyzer: &CppGraphSource<'_>,
3737        file: &ProjectFile,
3738        candidate: &CodeUnit,
3739        reference_byte: usize,
3740    ) -> bool {
3741        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3742            return false;
3743        };
3744        let root = prepared.tree().root_node();
3745        let end_byte = reference_byte
3746            .saturating_add(1)
3747            .min(prepared.source().len());
3748        let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
3749            return false;
3750        };
3751        self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
3752    }
3753
3754    pub fn preprocessor_guards_stable_between(
3755        &self,
3756        file: &ProjectFile,
3757        start_byte: usize,
3758        end_byte: usize,
3759        guards: &HashSet<PreprocessorGuard>,
3760    ) -> bool {
3761        if guards.is_empty() || start_byte >= end_byte {
3762            return true;
3763        }
3764        let cell = self.macro_event_cell(file);
3765        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3766        let mut visited = HashSet::from_iter([file.clone()]);
3767        !events.iter().any(|event| {
3768            event.byte() >= start_byte
3769                && event.byte() < end_byte
3770                && self.macro_event_may_mutate_guards(event, guards, &mut visited)
3771        })
3772    }
3773
3774    fn macro_event_may_mutate_guards(
3775        &self,
3776        event: &MacroEvent,
3777        guards: &HashSet<PreprocessorGuard>,
3778        visited: &mut HashSet<ProjectFile>,
3779    ) -> bool {
3780        match event {
3781            MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
3782                guards.iter().any(|guard| guard.may_depend_on_macro(name))
3783            }
3784            MacroEvent::Include { targets, .. } => {
3785                targets.is_empty()
3786                    || targets
3787                        .iter()
3788                        .any(|target| self.source_may_mutate_guards(target, guards, visited))
3789            }
3790            MacroEvent::Invalidate { .. } => true,
3791        }
3792    }
3793
3794    fn source_may_mutate_guards(
3795        &self,
3796        file: &ProjectFile,
3797        guards: &HashSet<PreprocessorGuard>,
3798        visited: &mut HashSet<ProjectFile>,
3799    ) -> bool {
3800        if !visited.insert(file.clone()) {
3801            return false;
3802        }
3803        let cell = self.macro_event_cell(file);
3804        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3805        events
3806            .iter()
3807            .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
3808    }
3809
3810    pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
3811        let normalized = normalize_reference_name(raw_name)?;
3812        self.type_candidates(file, &normalized)
3813            .into_iter()
3814            .next()
3815            .cloned()
3816    }
3817
3818    /// Mirror forward navigation's visible-name fallback for a bare parameter
3819    /// type after lexical owner and inheritance lookup is exhausted.
3820    ///
3821    /// Generated or otherwise unindexed base classes can hide the alias that
3822    /// makes a parameter type valid C++. Accept the fallback only when every
3823    /// include-visible class or alias with that spelling canonicalizes to one
3824    /// logical type. A shadowing local type resolves lexically before this
3825    /// path, while distinct visible types keep the result ambiguous.
3826    pub fn unique_visible_parameter_type_fallback(
3827        &self,
3828        analyzer: &CppGraphSource<'_>,
3829        file: &ProjectFile,
3830        node: Node<'_>,
3831        source: &str,
3832    ) -> Option<CodeUnit> {
3833        if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
3834            return None;
3835        }
3836        let name = node_text(node, source);
3837        let candidates = self
3838            .visible_identifier_candidates(file, name)
3839            .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
3840            .filter(|candidate| {
3841                self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
3842            })
3843            .collect::<Vec<_>>();
3844        self.unique_canonical_type_candidate(analyzer, file, &candidates)
3845    }
3846
3847    pub fn resolve_type_node_result(
3848        &self,
3849        file: &ProjectFile,
3850        node: Node<'_>,
3851        source: &str,
3852    ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
3853        let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
3854            return Ok(None);
3855        };
3856        let Some(arguments) = cpp_template_reference_arguments(node, source) else {
3857            return Ok(Some(primary));
3858        };
3859        self.resolve_template_arguments(file, primary, &arguments)
3860            .map(Some)
3861    }
3862
3863    pub fn resolve_type_node_primary(
3864        &self,
3865        file: &ProjectFile,
3866        node: Node<'_>,
3867        source: &str,
3868    ) -> Option<CodeUnit> {
3869        let components = cpp_type_name_components(node, source)?;
3870        self.resolve_type(file, &components.join("::"))
3871    }
3872
3873    pub fn resolve_template_arguments(
3874        &self,
3875        file: &ProjectFile,
3876        primary: CodeUnit,
3877        arguments: &[CppTemplateExpression],
3878    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
3879        self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
3880    }
3881
3882    fn resolve_template_arguments_inner(
3883        &self,
3884        file: &ProjectFile,
3885        primary: CodeUnit,
3886        arguments: &[CppTemplateExpression],
3887        seen_aliases: &mut HashSet<CodeUnit>,
3888    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
3889        if let Some(metadata) = self.cpp_template_metadata.get(&primary)
3890            && let Some(alias_target) = &metadata.alias_target
3891        {
3892            if !seen_aliases.insert(primary.clone()) {
3893                return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
3894            }
3895            let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
3896                .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
3897            let target_name = alias_target.components.join("::");
3898            let target_primary = if alias_target.global {
3899                unique_logical_type_candidate(self.type_candidates(file, &target_name))
3900            } else {
3901                self.resolve_unique_type_for_declaration(file, &primary, &target_name)
3902            };
3903            let Some(target_primary) = target_primary else {
3904                // A dependent or external RHS cannot be canonicalized from the
3905                // indexed graph. Preserve the alias's direct identity instead
3906                // of inventing a target from its source spelling.
3907                return Ok(primary);
3908            };
3909            let Some(target_arguments) = &alias_target.arguments else {
3910                return Ok(target_primary);
3911            };
3912            let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
3913                .ok_or(CppTemplateResolutionError::Substitution)?;
3914            return self.resolve_template_arguments_inner(
3915                file,
3916                target_primary,
3917                &target_arguments,
3918                seen_aliases,
3919            );
3920        }
3921
3922        let primary_fq_name = self
3923            .cpp_template_metadata
3924            .get(&primary)
3925            .map(|metadata| metadata.primary_fq_name.clone())
3926            .unwrap_or_else(|| primary.fq_name());
3927        let has_specialization_metadata = self
3928            .cpp_template_families
3929            .get(&primary_fq_name)
3930            .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
3931        if !has_specialization_metadata {
3932            return Ok(primary);
3933        }
3934        self.select_template_specialization(file, &primary, arguments)
3935    }
3936
3937    fn select_template_specialization(
3938        &self,
3939        file: &ProjectFile,
3940        resolved: &CodeUnit,
3941        explicit_arguments: &[CppTemplateExpression],
3942    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
3943        let primary_fq_name = self
3944            .cpp_template_metadata
3945            .get(resolved)
3946            .map(|metadata| metadata.primary_fq_name.clone())
3947            .unwrap_or_else(|| resolved.fq_name());
3948        let family = self
3949            .cpp_template_families
3950            .get(&primary_fq_name)
3951            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
3952        let primary_candidates = family
3953            .iter()
3954            .filter_map(|unit| {
3955                let metadata = self.cpp_template_metadata.get(unit)?;
3956                (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
3957            })
3958            .collect::<Vec<_>>();
3959        let primary_unit = primary_candidates
3960            .iter()
3961            .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
3962            .or_else(|| {
3963                primary_candidates
3964                    .iter()
3965                    .map(|(unit, _)| *unit)
3966                    .min_by_key(|unit| {
3967                        (
3968                            unit.source().to_string(),
3969                            unit.signature().unwrap_or_default(),
3970                        )
3971                    })
3972            })
3973            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
3974        let primary_parameters =
3975            cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
3976                .ok_or(CppTemplateResolutionError::PrimarySelection)?;
3977        let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
3978            .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
3979
3980        let mut applicable = Vec::new();
3981        for unit in family {
3982            let Some(metadata) = self.cpp_template_metadata.get(unit) else {
3983                continue;
3984            };
3985            if metadata.is_primary() || !self.is_visible(file, unit) {
3986                continue;
3987            }
3988            if !cpp_specialization_matches(metadata, &expanded) {
3989                continue;
3990            }
3991            applicable.push((unit, metadata));
3992        }
3993        if applicable.is_empty() {
3994            return Ok(primary_unit.clone());
3995        }
3996
3997        // A scalar constraint count cannot represent C++ partial ordering:
3998        // e.g. `<T*, U>` and `<T, int>` are incomparable for `<int*, int>`.
3999        // Select only a logical candidate whose structural pattern is strictly
4000        // more specialized than every other distinct applicable candidate.
4001        let winners = applicable
4002            .iter()
4003            .filter(|(candidate, candidate_metadata)| {
4004                applicable.iter().all(|(other, other_metadata)| {
4005                    same_visible_symbol(candidate, other)
4006                        || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
4007                })
4008            })
4009            .copied()
4010            .collect::<Vec<_>>();
4011        let Some((selected, _)) = winners.first() else {
4012            // Mutually incomparable applicable candidates: every one of them
4013            // is a live contender.
4014            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
4015                candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
4016            });
4017        };
4018        if winners
4019            .iter()
4020            .any(|(unit, _)| !same_visible_symbol(unit, selected))
4021        {
4022            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
4023                candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
4024            });
4025        }
4026        Ok((*selected).clone())
4027    }
4028
4029    pub fn resolve_type_components_lexically(
4030        &self,
4031        analyzer: &CppGraphSource<'_>,
4032        file: &ProjectFile,
4033        components: &[String],
4034        global: bool,
4035        lexical_scope: &[String],
4036    ) -> LexicalTypeResolution {
4037        self.resolve_type_components_lexically_inner(
4038            analyzer,
4039            file,
4040            components,
4041            global,
4042            lexical_scope,
4043            TypeCandidateResolution::Canonical,
4044        )
4045    }
4046
4047    pub fn resolve_type_components_lexically_for_forward(
4048        &self,
4049        analyzer: &CppGraphSource<'_>,
4050        file: &ProjectFile,
4051        components: &[String],
4052        global: bool,
4053        lexical_scope: &[String],
4054    ) -> LexicalTypeResolution {
4055        self.resolve_type_components_lexically_inner(
4056            analyzer,
4057            file,
4058            components,
4059            global,
4060            lexical_scope,
4061            TypeCandidateResolution::PreserveAlias,
4062        )
4063    }
4064
4065    pub fn resolve_type_components_lexically_for_target(
4066        &self,
4067        analyzer: &CppGraphSource<'_>,
4068        file: &ProjectFile,
4069        components: &[String],
4070        global: bool,
4071        lexical_scope: &[String],
4072        target: &CodeUnit,
4073    ) -> LexicalTypeResolution {
4074        #[cfg(any(test, feature = "test-support"))]
4075        self.target_preserving_type_resolution_count
4076            .fetch_add(1, Ordering::Relaxed);
4077        self.resolve_type_components_lexically_inner(
4078            analyzer,
4079            file,
4080            components,
4081            global,
4082            lexical_scope,
4083            TypeCandidateResolution::PreserveTarget(target),
4084        )
4085    }
4086
4087    pub fn coarse_unqualified_type_reference_may_resolve(
4088        &self,
4089        file: &ProjectFile,
4090        name: &str,
4091    ) -> bool {
4092        if name.is_empty() {
4093            return true;
4094        }
4095        self.visible_identifier_candidates(file, name)
4096            .any(|candidate| candidate.kind() == CodeUnitType::Class || is_type_alias(candidate))
4097            || self.visible_parser_alias_name_is_visible(file, name)
4098    }
4099
4100    #[allow(clippy::too_many_arguments)]
4101    pub fn structured_type_reference_may_resolve_to_target(
4102        &self,
4103        analyzer: &CppGraphSource<'_>,
4104        file: &ProjectFile,
4105        components: &[String],
4106        global: bool,
4107        lexical_scope: &[String],
4108        target: &CodeUnit,
4109    ) -> bool {
4110        if components.is_empty() {
4111            return true;
4112        }
4113        let Some(terminal) = components.last() else {
4114            return true;
4115        };
4116        let parser_alias_visible = self.visible_parser_alias_name_is_visible(file, terminal);
4117        if parser_alias_visible
4118            && self.parser_alias_resolves_to_type(analyzer, file, terminal, target)
4119        {
4120            return true;
4121        }
4122        let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
4123            .map(|qualified| qualified.join("::"))
4124            .collect::<Vec<_>>();
4125        let target_name = cpp_name_for(target);
4126        if qualified_tiers
4127            .iter()
4128            .any(|qualified| qualified == &target_name)
4129        {
4130            return true;
4131        }
4132
4133        let mut saw_shape_candidate = parser_alias_visible;
4134        for candidate in self.visible_identifier_candidates(file, terminal) {
4135            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
4136            {
4137                continue;
4138            }
4139            let candidate_name = cpp_name_for(candidate);
4140            let shape_matches = if global || components.len() > 1 {
4141                qualified_tiers
4142                    .iter()
4143                    .any(|qualified| qualified == &candidate_name)
4144            } else {
4145                true
4146            };
4147            if !shape_matches {
4148                continue;
4149            }
4150            saw_shape_candidate = true;
4151            if same_visible_symbol(candidate, target)
4152                || self.compatible_primary_template_redeclarations(candidate, target)
4153                || (declared_type_alias(analyzer, candidate)
4154                    && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
4155            {
4156                return true;
4157            }
4158        }
4159
4160        !saw_shape_candidate
4161    }
4162
4163    pub fn target_preserving_reference_namespace(
4164        &self,
4165        analyzer: &CppGraphSource<'_>,
4166        file: &ProjectFile,
4167        identifier: &str,
4168        target: &CodeUnit,
4169    ) -> Option<Vec<String>> {
4170        let mut namespace = None;
4171        for candidate in self.visible_identifier_candidates(file, identifier) {
4172            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
4173            {
4174                continue;
4175            }
4176            if !(same_visible_symbol(candidate, target)
4177                || self.compatible_primary_template_redeclarations(candidate, target)
4178                || declared_type_alias(analyzer, candidate)
4179                    && self.structured_alias_primary_preserves_target(
4180                        analyzer, file, candidate, target,
4181                    ))
4182            {
4183                continue;
4184            }
4185            if namespace
4186                .as_ref()
4187                .is_some_and(|existing| existing != candidate.package_name())
4188            {
4189                return None;
4190            }
4191            namespace = Some(candidate.package_name().to_string());
4192        }
4193        let namespace = namespace?;
4194        Some(
4195            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4196                brokk_bifrost_core::analyzer::Language::Cpp,
4197                &namespace,
4198            ),
4199        )
4200    }
4201
4202    pub fn resolve_imported_type_candidate(
4203        &self,
4204        analyzer: &CppGraphSource<'_>,
4205        file: &ProjectFile,
4206        target: &CodeUnit,
4207        target_components: &[String],
4208        direct_target: Option<&CodeUnit>,
4209        preserve_alias: bool,
4210    ) -> LexicalTypeResolution {
4211        let candidates = [target];
4212        let resolution = if preserve_alias {
4213            TypeCandidateResolution::PreserveAlias
4214        } else {
4215            direct_target.map_or(
4216                TypeCandidateResolution::Canonical,
4217                TypeCandidateResolution::PreserveTarget,
4218            )
4219        };
4220        // One candidate goes in, so a failure here is never "choose one of
4221        // these": it is the alias chain leaving the index, which must answer
4222        // missing rather than ambiguous (#1828).
4223        match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
4224            Ok(unit) => LexicalTypeResolution::Resolved {
4225                unit,
4226                components: target_components.to_vec(),
4227                candidates: vec![target.clone()],
4228            },
4229            Err(failure) => failure.lexical_resolution(),
4230        }
4231    }
4232
4233    fn resolve_type_components_lexically_inner(
4234        &self,
4235        analyzer: &CppGraphSource<'_>,
4236        file: &ProjectFile,
4237        components: &[String],
4238        global: bool,
4239        lexical_scope: &[String],
4240        resolution: TypeCandidateResolution<'_>,
4241    ) -> LexicalTypeResolution {
4242        if components.is_empty() {
4243            return LexicalTypeResolution::Missing;
4244        }
4245        // A C++ class injects its own name into the class scope.  The indexed
4246        // FqName for that declaration is the class path itself (for example,
4247        // `n::raw_hash_set`), not a synthetic child named
4248        // `n::raw_hash_set::raw_hash_set`.  Ordinary lexical tiers append the
4249        // requested identifier to every scope component, so they cannot
4250        // represent that injected binding when the enclosing class is the
4251        // closest scope.  Recover the binding from the structured class path
4252        // before allowing lookup to fall through to an outer same-spelled
4253        // declaration.
4254        let mut injected = self.resolve_injected_class_name(
4255            analyzer,
4256            file,
4257            components,
4258            global,
4259            lexical_scope,
4260            resolution,
4261        );
4262        for qualified in lexical_component_tiers(components, global, lexical_scope) {
4263            let prefix_len = qualified.len().saturating_sub(components.len());
4264            if injected
4265                .as_ref()
4266                .is_some_and(|(owner_len, _)| prefix_len < *owner_len)
4267            {
4268                return injected
4269                    .take()
4270                    .expect("injected class resolution was just present")
4271                    .1;
4272            }
4273            let qualified_name = qualified.join("::");
4274            let candidates = self
4275                .type_candidates(file, &qualified_name)
4276                .into_iter()
4277                .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
4278                .collect::<Vec<_>>();
4279            if candidates.is_empty() {
4280                if !global && components.len() == 1 {
4281                    match self.resolve_inherited_type_for_lexical_scope(
4282                        analyzer,
4283                        file,
4284                        &qualified[..prefix_len],
4285                        &components[0],
4286                        resolution,
4287                    ) {
4288                        LexicalTypeResolution::Missing => {}
4289                        inherited => return inherited,
4290                    }
4291                }
4292                continue;
4293            }
4294            let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
4295                Ok(unit) => unit,
4296                Err(failure) => return failure.lexical_resolution(),
4297            };
4298            return LexicalTypeResolution::Resolved {
4299                unit,
4300                components: qualified,
4301                candidates: candidates.into_iter().cloned().collect(),
4302            };
4303        }
4304        LexicalTypeResolution::Missing
4305    }
4306
4307    fn resolve_injected_class_name(
4308        &self,
4309        analyzer: &CppGraphSource<'_>,
4310        file: &ProjectFile,
4311        components: &[String],
4312        global: bool,
4313        lexical_scope: &[String],
4314        resolution: TypeCandidateResolution<'_>,
4315    ) -> Option<(usize, LexicalTypeResolution)> {
4316        if global
4317            || components.len() != 1
4318            || file.rel_path().extension().is_some_and(|ext| ext == "c")
4319            || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
4320        {
4321            return None;
4322        }
4323        let name = components.first()?;
4324        let mut matches: Vec<&CodeUnit> = Vec::new();
4325        let mut owner_len = 0;
4326        for candidate in self.visible_identifier_candidates(file, name) {
4327            if !candidate.is_class()
4328                || declared_type_alias(analyzer, candidate)
4329                || candidate.identifier() != name
4330            {
4331                continue;
4332            }
4333            let candidate_scope = canonical_cpp_scope_components(candidate);
4334            if candidate_scope.len() > lexical_scope.len()
4335                || !lexical_scope.starts_with(&candidate_scope)
4336                || candidate_scope.last().is_none_or(|last| last != name)
4337            {
4338                continue;
4339            }
4340            if candidate_scope.len() > owner_len {
4341                owner_len = candidate_scope.len();
4342                matches.clear();
4343            }
4344            if candidate_scope.len() == owner_len
4345                && !matches
4346                    .iter()
4347                    .any(|existing| same_logical_symbol(existing, candidate))
4348            {
4349                matches.push(candidate);
4350            }
4351        }
4352        if matches.is_empty() {
4353            return None;
4354        }
4355        // A same-named class at the current lexical boundary is already
4356        // represented by the ordinary namespace/class tier.  The injected
4357        // recovery is only needed when lookup is occurring inside a nested
4358        // class, where the enclosing class name is injected across that
4359        // additional class boundary.  Keeping this boundary strict avoids
4360        // treating qualified receiver/static-qualifier context as an
4361        // injected-name reference.
4362        if owner_len >= lexical_scope.len() {
4363            return None;
4364        }
4365        let owner_components = lexical_scope[..owner_len].to_vec();
4366        let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
4367            Ok(unit) => LexicalTypeResolution::Resolved {
4368                unit,
4369                components: owner_components,
4370                candidates: matches.into_iter().cloned().collect(),
4371            },
4372            Err(failure) => failure.lexical_resolution(),
4373        };
4374        Some((owner_len, resolution))
4375    }
4376
4377    fn resolve_inherited_type_for_lexical_scope(
4378        &self,
4379        analyzer: &CppGraphSource<'_>,
4380        file: &ProjectFile,
4381        lexical_scope: &[String],
4382        name: &str,
4383        resolution: TypeCandidateResolution<'_>,
4384    ) -> LexicalTypeResolution {
4385        let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
4386            return LexicalTypeResolution::Missing;
4387        };
4388        let lexical_owner_name = lexical_scope.join("::");
4389        if lexical_owner_name.is_empty() {
4390            return LexicalTypeResolution::Missing;
4391        }
4392        let owner_candidates = self
4393            .type_candidates(file, &lexical_owner_name)
4394            .into_iter()
4395            .filter(|candidate| {
4396                canonical_cpp_name_matches(candidate, &lexical_owner_name)
4397                    && !declared_type_alias(analyzer, candidate)
4398            })
4399            .collect::<Vec<_>>();
4400        if owner_candidates.is_empty() {
4401            return LexicalTypeResolution::Missing;
4402        }
4403        // A visible forward declaration and the physical class definition share
4404        // one FQN, but only the definition owns hierarchy facts. When lookup is
4405        // physically inside that definition, do not let an earlier header
4406        // forward declaration erase its base edges (#2240).
4407        let physical_owner_candidates = owner_candidates
4408            .iter()
4409            .copied()
4410            .filter(|candidate| candidate.source() == file)
4411            .collect::<Vec<_>>();
4412        let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
4413            owner_candidates
4414        } else {
4415            physical_owner_candidates
4416        };
4417        let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
4418            return LexicalTypeResolution::Ambiguous;
4419        };
4420
4421        let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
4422        let mut visited_owners = HashSet::default();
4423        while !frontier.is_empty() {
4424            let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
4425            let mut next_frontier = Vec::new();
4426            for owner in frontier {
4427                if !visited_owners.insert(owner.fq_name()) {
4428                    continue;
4429                }
4430                let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
4431                let candidates = self
4432                    .type_candidates(file, &qualified_name)
4433                    .into_iter()
4434                    .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
4435                    .collect::<Vec<_>>();
4436                if candidates.is_empty() {
4437                    for ancestor in hierarchy.get_direct_ancestors(&owner) {
4438                        if !next_frontier
4439                            .iter()
4440                            .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
4441                        {
4442                            next_frontier.push(ancestor);
4443                        }
4444                    }
4445                    continue;
4446                }
4447                let unit =
4448                    match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
4449                        Ok(unit) => unit,
4450                        Err(failure) => return failure.lexical_resolution(),
4451                    };
4452                level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
4453            }
4454            if let Some((unit, candidates)) = level_matches.first().cloned() {
4455                let Some(first_declaration) = candidates.first() else {
4456                    return LexicalTypeResolution::Ambiguous;
4457                };
4458                if !level_matches.iter().all(|(_, declarations)| {
4459                    declarations
4460                        .iter()
4461                        .all(|declaration| same_logical_symbol(first_declaration, declaration))
4462                }) {
4463                    return LexicalTypeResolution::Ambiguous;
4464                }
4465                let mut components = lexical_scope.to_vec();
4466                components.push(name.to_string());
4467                return LexicalTypeResolution::Resolved {
4468                    unit,
4469                    components,
4470                    candidates,
4471                };
4472            }
4473            frontier = next_frontier;
4474        }
4475        LexicalTypeResolution::Missing
4476    }
4477
4478    /// Resolve a base class through its injected class name at the nearest
4479    /// inheritance tier. Distinct same-named bases at that tier are ambiguous.
4480    pub fn inherited_injected_class_owner(
4481        &self,
4482        analyzer: &CppGraphSource<'_>,
4483        file: &ProjectFile,
4484        enclosing_owner: &CodeUnit,
4485        injected_name: &str,
4486    ) -> Option<CodeUnit> {
4487        let hierarchy = analyzer.type_hierarchy_provider()?;
4488        let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
4489        let mut visited = HashSet::default();
4490        while !frontier.is_empty() {
4491            let mut level_matches = Vec::new();
4492            let mut next_frontier = Vec::new();
4493            for raw_owner in frontier {
4494                let owner = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)?;
4495                if !visited.insert(owner.clone()) {
4496                    continue;
4497                }
4498                if owner.identifier() == injected_name
4499                    && !level_matches
4500                        .iter()
4501                        .any(|existing| same_logical_symbol(existing, &owner))
4502                {
4503                    level_matches.push(owner.clone());
4504                }
4505                next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
4506            }
4507            if let Some(first) = level_matches.first() {
4508                return level_matches
4509                    .iter()
4510                    .all(|candidate| same_logical_symbol(candidate, first))
4511                    .then(|| first.clone());
4512            }
4513            frontier = next_frontier;
4514        }
4515        None
4516    }
4517
4518    /// The one type the candidates name under `resolution`, or why they do not
4519    /// name one. The two preserving modes only ever reject candidates that
4520    /// disagree with each other, which is ambiguity; canonicalization can also
4521    /// fail because the alias chain leaves the index (#1828).
4522    fn resolve_type_candidates(
4523        &self,
4524        analyzer: &CppGraphSource<'_>,
4525        file: &ProjectFile,
4526        candidates: &[&CodeUnit],
4527        resolution: TypeCandidateResolution<'_>,
4528    ) -> Result<CodeUnit, TypeCandidateFailure> {
4529        match resolution {
4530            TypeCandidateResolution::Canonical => {
4531                self.canonical_type_candidate_resolution(analyzer, file, candidates)
4532            }
4533            TypeCandidateResolution::PreserveAlias => {
4534                // A generated index can retain identical alias spellings from
4535                // mutually exclusive headers. When the reference file
4536                // physically reaches exactly one of those source declarations,
4537                // include closure is the structured evidence that selects it;
4538                // treating the two source spellings as an overload set makes a
4539                // reachable alias appear ambiguous (#1844).
4540                let same_fqn_alias_family = candidates.len() > 1
4541                    && candidates.iter().all(|candidate| {
4542                        declared_type_alias(analyzer, candidate)
4543                            && same_logical_symbol(candidates[0], candidate)
4544                    })
4545                    && candidates
4546                        .iter()
4547                        .any(|candidate| candidate.source() != candidates[0].source());
4548                if same_fqn_alias_family {
4549                    let physically_visible = candidates
4550                        .iter()
4551                        .copied()
4552                        .filter(|candidate| self.is_physically_visible(file, candidate))
4553                        .collect::<Vec<_>>();
4554                    // The family is one logical declaration only when the
4555                    // reachable spellings agree. Two same-FQN aliases whose
4556                    // written targets differ (`using Choice = Canonical;` in
4557                    // one header, `using Choice = ::Canonical;` in another)
4558                    // are a genuine conflict, and choosing the first indexed
4559                    // one silently binds the reference to an arbitrary owner
4560                    // (#2398). Collapse only a single reachable declaration
4561                    // or reachable declarations with one structured target;
4562                    // everything else stays ambiguous below.
4563                    let one_structured_target = physically_visible.len() > 1
4564                        && physically_visible.iter().skip(1).all(|candidate| {
4565                            let target = self.structured_alias_target(analyzer, candidate);
4566                            target.is_some()
4567                                && target
4568                                    == self.structured_alias_target(analyzer, physically_visible[0])
4569                        });
4570                    if physically_visible.len() == 1 || one_structured_target {
4571                        return Ok(physically_visible[0].clone());
4572                    }
4573                }
4574                unique_type_candidate_preserving_alias(analyzer, candidates)
4575                    .ok_or(TypeCandidateFailure::Ambiguous)
4576            }
4577            TypeCandidateResolution::PreserveTarget(target) => self
4578                .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
4579                .ok_or(TypeCandidateFailure::Ambiguous),
4580        }
4581    }
4582
4583    pub fn resolve_callable_value_components_lexically(
4584        &self,
4585        analyzer: &CppGraphSource<'_>,
4586        file: &ProjectFile,
4587        owner_components: &[String],
4588        member_name: &str,
4589        global: bool,
4590        lexical_scope: &[String],
4591    ) -> LexicalCallableValueResolution {
4592        if owner_components.is_empty() || member_name.is_empty() {
4593            return LexicalCallableValueResolution::Missing;
4594        }
4595        for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
4596            let owner_name = qualified_owner.join("::");
4597            let type_candidates = self
4598                .type_candidates(file, &owner_name)
4599                .into_iter()
4600                .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
4601                .collect::<Vec<_>>();
4602            let resolved_type = if type_candidates.is_empty() {
4603                None
4604            } else {
4605                let Some(unit) =
4606                    self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
4607                else {
4608                    return LexicalCallableValueResolution::Ambiguous;
4609                };
4610                Some(unit)
4611            };
4612
4613            let mut qualified_callable = qualified_owner;
4614            qualified_callable.push(member_name.to_string());
4615            let callable_name = qualified_callable.join("::");
4616            let free_function = self
4617                .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
4618                .into_iter()
4619                .find(|candidate| {
4620                    canonical_cpp_name_matches(candidate, &callable_name)
4621                        && type_owner_of(analyzer, candidate).is_none()
4622                })
4623                .cloned();
4624
4625            match (resolved_type, free_function) {
4626                (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
4627                (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
4628                (None, Some(function)) => {
4629                    return LexicalCallableValueResolution::FreeFunction(function);
4630                }
4631                (None, None) => {}
4632            }
4633        }
4634        LexicalCallableValueResolution::Missing
4635    }
4636
4637    fn resolve_type_for_declaration(
4638        &self,
4639        visible_from: &ProjectFile,
4640        declaration: &CodeUnit,
4641        raw_name: &str,
4642    ) -> Option<CodeUnit> {
4643        let normalized = normalize_reference_name(raw_name)?;
4644        if !normalized.contains("::")
4645            && let Some(namespace) = cpp_namespace_for(declaration)
4646        {
4647            for prefix in namespace_prefixes(&namespace) {
4648                let qualified = format!("{prefix}::{normalized}");
4649                if let Some(unit) = self
4650                    .type_candidates(visible_from, &qualified)
4651                    .into_iter()
4652                    .next()
4653                {
4654                    return Some(unit.clone());
4655                }
4656            }
4657        }
4658        self.resolve_type(visible_from, raw_name)
4659    }
4660
4661    fn resolve_unique_canonical_type_for_declaration(
4662        &self,
4663        analyzer: &CppGraphSource<'_>,
4664        visible_from: &ProjectFile,
4665        declaration: &CodeUnit,
4666        raw_name: &str,
4667    ) -> Option<CodeUnit> {
4668        let mut current =
4669            self.resolve_unique_type_for_declaration(visible_from, declaration, raw_name)?;
4670        let mut seen_aliases = HashSet::default();
4671        loop {
4672            let Some(target) = self.structured_alias_target(analyzer, &current) else {
4673                return current.is_class().then_some(current);
4674            };
4675            if matches!(target, StructuredAliasTarget::Builtin) {
4676                return current.is_class().then_some(current);
4677            }
4678            if !seen_aliases.insert(current.clone()) {
4679                return None;
4680            }
4681            current = self.resolve_structured_alias_target(visible_from, &current, &target)?;
4682        }
4683    }
4684
4685    pub fn canonical_type_unit(
4686        &self,
4687        analyzer: &CppGraphSource<'_>,
4688        visible_from: &ProjectFile,
4689        unit: &CodeUnit,
4690    ) -> Option<CodeUnit> {
4691        self.canonical_type_resolution(analyzer, visible_from, unit)
4692            .ok()
4693    }
4694
4695    /// Follow `unit`'s alias chain to the class it names, or report why the
4696    /// chain does not end at one indexed class.
4697    ///
4698    /// A chain that leaves the index - an alias to a template parameter, to a
4699    /// standard-library type, or to any other declaration the workspace does
4700    /// not hold - is `Unresolvable`, not `Ambiguous` (#1828). So is a cycle:
4701    /// there is still nothing to choose between.
4702    fn canonical_type_resolution(
4703        &self,
4704        analyzer: &CppGraphSource<'_>,
4705        visible_from: &ProjectFile,
4706        unit: &CodeUnit,
4707    ) -> Result<CodeUnit, TypeCandidateFailure> {
4708        let mut current = unit.clone();
4709        let mut seen_aliases = HashSet::default();
4710        loop {
4711            let Some(target) = self.structured_alias_target(analyzer, &current) else {
4712                return current
4713                    .is_class()
4714                    .then_some(current)
4715                    .ok_or(TypeCandidateFailure::Unresolvable);
4716            };
4717            if matches!(target, StructuredAliasTarget::Builtin) {
4718                return current
4719                    .is_class()
4720                    .then_some(current)
4721                    .ok_or(TypeCandidateFailure::Unresolvable);
4722            }
4723            if !seen_aliases.insert(current.clone()) {
4724                return Err(TypeCandidateFailure::Unresolvable);
4725            }
4726            current = self.structured_alias_target_resolution(visible_from, &current, &target)?;
4727        }
4728    }
4729
4730    pub fn canonical_visible_full_type_unit(
4731        &self,
4732        analyzer: &CppGraphSource<'_>,
4733        visible_from: &ProjectFile,
4734        unit: &CodeUnit,
4735    ) -> Option<CodeUnit> {
4736        let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
4737        if cpp_class_declaration_strength(analyzer, &canonical)
4738            != CppClassDeclarationStrength::Forward
4739        {
4740            return Some(canonical);
4741        }
4742        let mut full = Vec::new();
4743        for candidate in self
4744            .visible_identifier_candidates(visible_from, canonical.identifier())
4745            .filter(|candidate| {
4746                candidate.is_class()
4747                    && candidate.fq_name() == canonical.fq_name()
4748                    && cpp_class_declaration_strength(analyzer, candidate)
4749                        == CppClassDeclarationStrength::Full
4750            })
4751        {
4752            if !full.iter().any(|existing| same_symbol(existing, candidate)) {
4753                full.push(candidate.clone());
4754            }
4755        }
4756        match full.len() {
4757            0 => Some(canonical),
4758            1 => full.pop(),
4759            _ => None,
4760        }
4761    }
4762
4763    fn resolve_structured_alias_target(
4764        &self,
4765        visible_from: &ProjectFile,
4766        declaration: &CodeUnit,
4767        target: &StructuredAliasTarget,
4768    ) -> Option<CodeUnit> {
4769        self.structured_alias_target_resolution(visible_from, declaration, target)
4770            .ok()
4771    }
4772
4773    fn structured_alias_target_resolution(
4774        &self,
4775        visible_from: &ProjectFile,
4776        declaration: &CodeUnit,
4777        target: &StructuredAliasTarget,
4778    ) -> Result<CodeUnit, TypeCandidateFailure> {
4779        let primary =
4780            self.structured_alias_primary_resolution(visible_from, declaration, target)?;
4781        let StructuredAliasTarget::Named { arguments, .. } = target else {
4782            return Err(TypeCandidateFailure::Unresolvable);
4783        };
4784        match arguments {
4785            Some(arguments) => self
4786                .resolve_template_arguments(visible_from, primary, arguments)
4787                .map_err(|error| match error {
4788                    CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
4789                        TypeCandidateFailure::Ambiguous
4790                    }
4791                    _ => TypeCandidateFailure::Unresolvable,
4792                }),
4793            None => Ok(primary),
4794        }
4795    }
4796
4797    fn resolve_structured_alias_primary(
4798        &self,
4799        visible_from: &ProjectFile,
4800        declaration: &CodeUnit,
4801        target: &StructuredAliasTarget,
4802    ) -> Option<CodeUnit> {
4803        self.structured_alias_primary_resolution(visible_from, declaration, target)
4804            .ok()
4805    }
4806
4807    fn structured_alias_primary_resolution(
4808        &self,
4809        visible_from: &ProjectFile,
4810        declaration: &CodeUnit,
4811        target: &StructuredAliasTarget,
4812    ) -> Result<CodeUnit, TypeCandidateFailure> {
4813        let StructuredAliasTarget::Named {
4814            components, global, ..
4815        } = target
4816        else {
4817            return Err(TypeCandidateFailure::Unresolvable);
4818        };
4819        let qualified = components.join("::");
4820        let candidates = if *global {
4821            // `::A::B` anchors at the root scope, so a candidate whose
4822            // canonical path merely ends with the spelled components does not
4823            // qualify. Without this filter a global `::Canonical` target also
4824            // collects `alpha::Canonical`, the lookup reports a false
4825            // ambiguity, and the alias arm silently drops out of its
4826            // conflicting family instead of proving the conflict (#2398).
4827            let mut candidates = self.type_candidates(visible_from, &qualified);
4828            candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
4829            candidates
4830        } else {
4831            self.type_candidates_for_declaration(visible_from, declaration, &qualified)
4832        };
4833        logical_type_candidate(candidates)
4834    }
4835
4836    pub fn structured_alias_primary_preserves_target(
4837        &self,
4838        analyzer: &CppGraphSource<'_>,
4839        visible_from: &ProjectFile,
4840        candidate: &CodeUnit,
4841        target: &CodeUnit,
4842    ) -> bool {
4843        let mut current = candidate.clone();
4844        let mut seen = HashSet::default();
4845        let mut matched_target = false;
4846        loop {
4847            if same_visible_symbol(&current, target)
4848                || self.compatible_primary_template_redeclarations(&current, target)
4849            {
4850                matched_target = true;
4851            }
4852            if !seen.insert(current.clone()) {
4853                return false;
4854            }
4855            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
4856                return matched_target;
4857            };
4858            if matches!(alias_target, StructuredAliasTarget::Builtin) {
4859                return matched_target;
4860            };
4861            let Some(primary) =
4862                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
4863            else {
4864                // A dependent member target such as `Detector<T>::type`
4865                // cannot be reduced to an indexed primary, but a preceding
4866                // structured alias hop may already have proven the requested
4867                // alias identity. Cycles still resolve a primary and are
4868                // rejected by `seen` above.
4869                return matched_target;
4870            };
4871            current = primary;
4872        }
4873    }
4874
4875    pub fn structured_class_alias_resolves_to_target(
4876        &self,
4877        analyzer: &CppGraphSource<'_>,
4878        visible_from: &ProjectFile,
4879        alias: &CodeUnit,
4880        target: &CodeUnit,
4881    ) -> bool {
4882        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
4883            return false;
4884        };
4885        let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
4886            return false;
4887        };
4888        let StructuredAliasTarget::Named {
4889            components, global, ..
4890        } = &alias_target
4891        else {
4892            return false;
4893        };
4894        let lexical_scope = canonical_cpp_scope_components(&owner);
4895        match self.resolve_type_components_lexically_for_target(
4896            analyzer,
4897            visible_from,
4898            components,
4899            *global,
4900            &lexical_scope,
4901            target,
4902        ) {
4903            LexicalTypeResolution::Resolved {
4904                unit, candidates, ..
4905            } => {
4906                same_visible_symbol(&unit, target)
4907                    || self.same_template_member_identity(analyzer, &unit, target)
4908                    || candidates.iter().any(|candidate| {
4909                        same_visible_symbol(candidate, target)
4910                            || self.same_template_member_identity(analyzer, candidate, target)
4911                    })
4912            }
4913            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
4914                self.structured_alias_primary_preserves_target(
4915                    analyzer,
4916                    visible_from,
4917                    alias,
4918                    target,
4919                ) || self.flattened_macro_namespace_alias_target_matches(
4920                    analyzer,
4921                    visible_from,
4922                    alias,
4923                    &alias_target,
4924                    target,
4925                )
4926            }
4927        }
4928    }
4929
4930    /// Return true when a class-owned alias names the requested type as one
4931    /// structured qualifier in its target path.
4932    ///
4933    /// A dependent target such as `Primary<T>::Type` cannot resolve to one
4934    /// indexed class. Forward lookup can still retain `Primary` as its bounded
4935    /// canonical identity. Inverse lookup needs the same evidence when later
4936    /// references use only the alias spelling.
4937    pub fn structured_class_alias_path_preserves_target(
4938        &self,
4939        analyzer: &CppGraphSource<'_>,
4940        visible_from: &ProjectFile,
4941        alias: &CodeUnit,
4942        target: &CodeUnit,
4943    ) -> bool {
4944        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
4945            return false;
4946        };
4947        let Some(StructuredAliasTarget::Named {
4948            components, global, ..
4949        }) = self.structured_alias_target(analyzer, alias)
4950        else {
4951            return false;
4952        };
4953        let lexical_scope = canonical_cpp_scope_components(&owner);
4954        (1..components.len()).rev().any(|component_count| {
4955            matches!(
4956                self.resolve_type_components_lexically_for_target(
4957                    analyzer,
4958                    visible_from,
4959                    &components[..component_count],
4960                    global,
4961                    &lexical_scope,
4962                    target,
4963                ),
4964                LexicalTypeResolution::Resolved {
4965                    ref unit,
4966                    ref candidates,
4967                    ..
4968                } if same_visible_symbol(unit, target)
4969                    || self.same_template_member_identity(analyzer, unit, target)
4970                    || candidates.iter().any(|candidate| {
4971                        same_visible_symbol(candidate, target)
4972                            || self.same_template_member_identity(analyzer, candidate, target)
4973                    })
4974            )
4975        })
4976    }
4977
4978    fn flattened_macro_namespace_alias_target_matches(
4979        &self,
4980        analyzer: &CppGraphSource<'_>,
4981        visible_from: &ProjectFile,
4982        alias: &CodeUnit,
4983        alias_target: &StructuredAliasTarget,
4984        target: &CodeUnit,
4985    ) -> bool {
4986        let StructuredAliasTarget::Named {
4987            components,
4988            global: false,
4989            arguments: None,
4990        } = alias_target
4991        else {
4992            return false;
4993        };
4994        let Some((target_name, namespace_components)) = components.split_last() else {
4995            return false;
4996        };
4997        if namespace_components.is_empty()
4998            || target_name != target.identifier()
4999            || alias.source() != target.source()
5000            || alias.source() != visible_from
5001            || !target.is_class()
5002            || declared_type_alias(analyzer, target)
5003        {
5004            return false;
5005        }
5006        if self
5007            .resolve_structured_alias_target(visible_from, alias, alias_target)
5008            .is_some()
5009        {
5010            return false;
5011        }
5012
5013        let alias_ranges = analyzer.ranges(alias);
5014        let target_ranges = analyzer.ranges(target);
5015        if alias_ranges.is_empty() || target_ranges.is_empty() {
5016            return false;
5017        }
5018        let alias_start = alias_ranges
5019            .iter()
5020            .map(|range| range.start_byte)
5021            .min()
5022            .expect("non-empty alias ranges have a minimum");
5023        let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
5024            return false;
5025        };
5026        let root = prepared.tree().root_node();
5027        let has_matching_declaration = target_ranges
5028            .iter()
5029            .filter(|range| range.end_byte <= alias_start)
5030            .filter_map(|range| node_for_exact_range(root, range))
5031            .any(|node| {
5032                flattened_macro_namespace_components(node, prepared.source())
5033                    .is_some_and(|recovered| recovered == namespace_components)
5034            });
5035        if !has_matching_declaration {
5036            return false;
5037        }
5038
5039        let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
5040        let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
5041        guard_requirement_sets_match(&alias_guards, &target_guards)
5042    }
5043
5044    pub fn template_alias_arguments_preserve_target(
5045        &self,
5046        analyzer: &CppGraphSource<'_>,
5047        visible_from: &ProjectFile,
5048        alias: &CodeUnit,
5049        arguments: &[CppTemplateExpression],
5050        target: &CodeUnit,
5051    ) -> bool {
5052        let Some(metadata) = self.cpp_template_metadata.get(alias) else {
5053            return false;
5054        };
5055        if metadata.alias_target.is_none()
5056            || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
5057        {
5058            return false;
5059        }
5060        self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
5061    }
5062
5063    pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
5064        self.cpp_template_metadata
5065            .get(unit)
5066            .is_some_and(CppTemplateMetadata::is_primary)
5067    }
5068
5069    pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
5070        self.cpp_template_metadata
5071            .get(unit)
5072            .is_some_and(CppTemplateMetadata::is_specialization)
5073    }
5074
5075    pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
5076        same_visible_symbol(left, right)
5077            || self.compatible_primary_template_redeclarations(left, right)
5078    }
5079
5080    pub fn same_template_member_identity(
5081        &self,
5082        analyzer: &CppGraphSource<'_>,
5083        left: &CodeUnit,
5084        right: &CodeUnit,
5085    ) -> bool {
5086        if same_visible_symbol(left, right) {
5087            return true;
5088        }
5089        if left.kind() != right.kind()
5090            || left.identifier() != right.identifier()
5091            || left.signature() != right.signature()
5092        {
5093            return false;
5094        }
5095        let (Some(left_owner), Some(right_owner)) =
5096            (analyzer.parent_of(left), analyzer.parent_of(right))
5097        else {
5098            return false;
5099        };
5100        left_owner.is_class()
5101            && right_owner.is_class()
5102            && self.same_template_owner_identity(&left_owner, &right_owner)
5103    }
5104
5105    fn unique_canonical_type_candidate(
5106        &self,
5107        analyzer: &CppGraphSource<'_>,
5108        visible_from: &ProjectFile,
5109        candidates: &[&CodeUnit],
5110    ) -> Option<CodeUnit> {
5111        self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
5112            .ok()
5113    }
5114
5115    fn canonical_type_candidate_resolution(
5116        &self,
5117        analyzer: &CppGraphSource<'_>,
5118        visible_from: &ProjectFile,
5119        candidates: &[&CodeUnit],
5120    ) -> Result<CodeUnit, TypeCandidateFailure> {
5121        let mut canonical = Vec::new();
5122        for candidate in candidates {
5123            let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
5124            if canonical
5125                .iter()
5126                .any(|existing| same_visible_symbol(existing, &resolved))
5127            {
5128                continue;
5129            }
5130            if let Some(existing) = canonical.iter_mut().find(|existing| {
5131                self.compatible_primary_template_redeclarations(existing, &resolved)
5132            }) {
5133                // A forward declaration and its full primary-template
5134                // definition are one C++ type even when they live in
5135                // different headers and alpha-rename their parameters. The
5136                // target-preserving path already reconciles this family; do
5137                // the same for ordinary canonical lookup so an out-of-line
5138                // member's lexical owner is not made ambiguous by its own
5139                // forward declaration. Retain the strongest physical
5140                // declaration for later owner/range queries.
5141                if matches!(
5142                    (
5143                        cpp_class_declaration_strength(analyzer, existing),
5144                        cpp_class_declaration_strength(analyzer, &resolved),
5145                    ),
5146                    (
5147                        CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
5148                        CppClassDeclarationStrength::Full,
5149                    ) | (
5150                        CppClassDeclarationStrength::Unknown,
5151                        CppClassDeclarationStrength::Forward,
5152                    )
5153                ) {
5154                    *existing = resolved;
5155                }
5156                continue;
5157            }
5158            canonical.push(resolved);
5159            if canonical.len() > 1 {
5160                return Err(TypeCandidateFailure::Ambiguous);
5161            }
5162        }
5163        canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
5164    }
5165
5166    pub fn unique_type_candidate_preserving_target(
5167        &self,
5168        analyzer: &CppGraphSource<'_>,
5169        visible_from: &ProjectFile,
5170        candidates: &[&CodeUnit],
5171        target: &CodeUnit,
5172    ) -> Option<CodeUnit> {
5173        // C++ headers often expose one logical type through mutually exclusive
5174        // physical declarations, for example a class in the fallback branch
5175        // and a `using` alias to the standard-library type in the configured
5176        // branch. The index intentionally retains both declarations so forward
5177        // lookup can report each target. Preserve the requested target when
5178        // that is the only ambiguity: every candidate has the same type kind,
5179        // exact canonical FQN, and source file, and the requested declaration
5180        // itself is one of the physical candidates. Do not merge same-named
5181        // declarations from different files or namespaces; those remain
5182        // ambiguous and fail closed below.
5183        if self.alternate_same_fqn_type_declarations(analyzer, candidates, target) {
5184            return Some(target.clone());
5185        }
5186        let mut resolved_candidates = Vec::new();
5187        for candidate in candidates {
5188            // An ifdef branch that aliases an unindexed system type (for
5189            // example `typedef pthread_mutex_t k5_os_mutex`) cannot be
5190            // canonicalized. That branch does not name `target`. Dropping it
5191            // keeps the branch that does. Failing the whole family here would
5192            // deny every usage of the reachable spelling (#2368).
5193            let Some(resolved) =
5194                self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
5195            else {
5196                continue;
5197            };
5198            if resolved_candidates
5199                .iter()
5200                .any(|existing| same_visible_symbol(existing, &resolved))
5201            {
5202                continue;
5203            }
5204            resolved_candidates.push(resolved);
5205        }
5206        match resolved_candidates.as_slice() {
5207            [] => None,
5208            [single] => Some(single.clone()),
5209            // The branches disagree about what the name aliases. When they are
5210            // spellings of one entity (#1845) that disagreement is a build
5211            // configuration, not a choice between types, so it must not deny
5212            // the requested target its reference.
5213            _ => self
5214                .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
5215                .map(|_| target.clone()),
5216        }
5217    }
5218
5219    /// The declaration a same-file same-FQN family stands for when a reference
5220    /// names `target`, or `None` when the candidates are not one family or the
5221    /// family does not name `target`.
5222    ///
5223    /// A translation unit cannot hold two different types under one qualified
5224    /// name, so several same-kind declarations of one FQN in one file are
5225    /// alternate spellings of one entity - the configuration branches of an
5226    /// `#if` family, for example log4cxx's `logchar`, which aliases `char` in
5227    /// the UTF-8 branch and `UniChar` in the unichar branch. Their alias
5228    /// targets differ; canonicalizing each branch on its own and then demanding
5229    /// agreement reports an ambiguity that denies every declaration in the
5230    /// family its usages (#1845). The family names `target` when it declares
5231    /// it, or when one branch's alias chain reaches it.
5232    ///
5233    /// Declarations in different files or namespaces are distinct entities and
5234    /// are deliberately excluded: their disagreement is a real ambiguity.
5235    pub fn same_fqn_type_spelling_for_target<'b>(
5236        &self,
5237        analyzer: &CppGraphSource<'_>,
5238        visible_from: &ProjectFile,
5239        candidates: &[&'b CodeUnit],
5240        target: &CodeUnit,
5241    ) -> Option<&'b CodeUnit> {
5242        let [first, rest @ ..] = candidates else {
5243            return None;
5244        };
5245        if rest.is_empty()
5246            || !rest.iter().all(|candidate| {
5247                candidate.kind() == first.kind()
5248                    && candidate.fq_name() == first.fq_name()
5249                    && candidate.source() == first.source()
5250            })
5251        {
5252            return None;
5253        }
5254        candidates
5255            .iter()
5256            .copied()
5257            .find(|candidate| same_symbol(candidate, target))
5258            .or_else(|| {
5259                candidates.iter().copied().find(|candidate| {
5260                    self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
5261                        .is_some_and(|resolved| same_visible_symbol(&resolved, target))
5262                })
5263            })
5264    }
5265
5266    pub fn alternate_same_fqn_type_declarations(
5267        &self,
5268        analyzer: &CppGraphSource<'_>,
5269        candidates: &[&CodeUnit],
5270        target: &CodeUnit,
5271    ) -> bool {
5272        let Some(first) = candidates.first() else {
5273            return false;
5274        };
5275        let same_api = first.kind() == target.kind()
5276            && first.fq_name() == target.fq_name()
5277            && first.source() == target.source()
5278            && candidates.iter().all(|candidate| {
5279                candidate.kind() == target.kind()
5280                    && candidate.fq_name() == target.fq_name()
5281                    && candidate.source() == target.source()
5282            })
5283            && candidates
5284                .iter()
5285                .any(|candidate| same_symbol(candidate, target))
5286            && candidates
5287                .iter()
5288                .any(|candidate| !same_logical_symbol(candidate, target));
5289        if !same_api {
5290            return false;
5291        }
5292
5293        let requirements = candidates
5294            .iter()
5295            .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
5296            .collect::<Vec<_>>();
5297        requirements.len() > 1
5298            && requirements
5299                .iter()
5300                .all(|requirement| !requirement.is_empty())
5301            && requirements.iter().enumerate().all(|(index, left)| {
5302                requirements[index + 1..].iter().all(|right| {
5303                    left.iter().all(|(_, left_guards)| {
5304                        right.iter().all(|(_, right_guards)| {
5305                            merge_preprocessor_guards(left_guards, right_guards).is_none()
5306                        })
5307                    })
5308                })
5309            })
5310    }
5311
5312    fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
5313        let mut pending = vec![terms.to_vec()];
5314        while let Some(branch_terms) = pending.pop() {
5315            let mut normalized = Vec::new();
5316            let mut covers_branch = false;
5317            for term in branch_terms {
5318                if term.iter().any(|guard| term.contains(&guard.negated())) {
5319                    continue;
5320                }
5321                if term.is_empty() {
5322                    covers_branch = true;
5323                    break;
5324                }
5325                if !normalized.iter().any(|existing| existing == &term) {
5326                    normalized.push(term);
5327                }
5328            }
5329            if covers_branch {
5330                continue;
5331            }
5332            let Some(split_guard) = normalized
5333                .iter()
5334                .flat_map(|term| term.iter())
5335                .next()
5336                .cloned()
5337            else {
5338                return false;
5339            };
5340            let negated_guard = split_guard.negated();
5341            let mut when_defined = Vec::new();
5342            let mut when_undefined = Vec::new();
5343            for term in normalized {
5344                if term.contains(&negated_guard) {
5345                    // This term cannot hold when `split_guard` is true.
5346                } else if term.contains(&split_guard) {
5347                    let mut reduced = term.clone();
5348                    reduced.remove(&split_guard);
5349                    when_defined.push(reduced);
5350                } else {
5351                    when_defined.push(term.clone());
5352                }
5353                if term.contains(&split_guard) {
5354                    // This term cannot hold when `split_guard` is false.
5355                } else if term.contains(&negated_guard) {
5356                    let mut reduced = term;
5357                    reduced.remove(&negated_guard);
5358                    when_undefined.push(reduced);
5359                } else {
5360                    when_undefined.push(term);
5361                }
5362            }
5363            pending.push(when_defined);
5364            pending.push(when_undefined);
5365        }
5366        true
5367    }
5368
5369    /// The byte range of the one `#if` family with a terminal `#else` that holds
5370    /// every physical declaration of every candidate, or `None` when they do not
5371    /// share one such family.
5372    ///
5373    /// Guard terms alone cannot distinguish one `#if` family from separate blocks
5374    /// whose macros changed between declarations. Require every physical range to
5375    /// belong to one syntax-tree family with a terminal `#else` before the terms
5376    /// can prove branch coverage.
5377    fn declarations_share_exhaustive_conditional_family(
5378        &self,
5379        analyzer: &CppGraphSource<'_>,
5380        candidates: &[&CodeUnit],
5381    ) -> Option<(usize, usize)> {
5382        let mut family_range = None;
5383        for candidate in candidates {
5384            let prepared = self.cpp.prepared_syntax(self.token, candidate.source())?;
5385            let root = prepared.tree().root_node();
5386            let mut candidate_family = None;
5387            for range in analyzer.ranges(candidate) {
5388                let node = root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
5389                let family = preprocessor_conditional_family_for_declaration(node)?;
5390                let key = (family.start_byte(), family.end_byte());
5391                if candidate_family.is_some_and(|existing| existing != key) {
5392                    return None;
5393                }
5394                candidate_family = Some(key);
5395            }
5396            let candidate_family = candidate_family?;
5397            if family_range.is_some_and(|existing| existing != candidate_family) {
5398                return None;
5399            }
5400            family_range = Some(candidate_family);
5401        }
5402        family_range
5403    }
5404
5405    pub fn complementary_same_fqn_type_declarations(
5406        &self,
5407        analyzer: &CppGraphSource<'_>,
5408        candidates: &[&CodeUnit],
5409        target: &CodeUnit,
5410    ) -> bool {
5411        if candidates.len() < 2
5412            || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
5413            || self
5414                .declarations_share_exhaustive_conditional_family(analyzer, candidates)
5415                .is_none()
5416        {
5417            return false;
5418        }
5419        Self::preprocessor_guard_terms_cover_all_paths(
5420            &self.declaration_family_guard_terms(analyzer, candidates),
5421        )
5422    }
5423
5424    fn declaration_family_guard_terms(
5425        &self,
5426        analyzer: &CppGraphSource<'_>,
5427        candidates: &[&CodeUnit],
5428    ) -> Vec<HashSet<PreprocessorGuard>> {
5429        candidates
5430            .iter()
5431            .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
5432            .map(|(_, guards)| guards)
5433            .collect()
5434    }
5435
5436    /// A callable name declared on every branch of one completed `#if`/`#else`
5437    /// family is declared on every configuration path, so a reference below the
5438    /// whole family sees one of the branches whatever the preprocessor decides.
5439    /// Answer the family's end byte: only past `#endif` is every branch's
5440    /// declaration behind the reference.
5441    ///
5442    /// This is the callable analogue of `complementary_same_fqn_type_declarations`
5443    /// and shares both of its primitives. It does not require two distinct
5444    /// `CodeUnit`s: branches that declare the same signature can collapse into
5445    /// one unit carrying one physical range per branch.
5446    ///
5447    /// The branches are alternate spellings of one declaration, never competing
5448    /// declarations, so only the first branch stands for the family. Reporting
5449    /// every branch as visible would turn a name the source declares exactly
5450    /// once into an ambiguity between build configurations.
5451    fn exhaustive_guard_family_activation(
5452        &self,
5453        analyzer: &CppGraphSource<'_>,
5454        prepared: &PreparedSyntaxTree,
5455        candidate: &CodeUnit,
5456        reference: &CallableReferenceContext<'_>,
5457    ) -> Option<usize> {
5458        // Branch coverage says nothing about scope: a block-local declaration
5459        // stays invisible however many branches declare it.
5460        if nameable_callable_declaration_nodes(analyzer, prepared, candidate).is_empty() {
5461            return None;
5462        }
5463        let family = self
5464            .visible_identifier_candidates(candidate.source(), candidate.identifier())
5465            .filter(|peer| {
5466                peer.kind() == candidate.kind()
5467                    && peer.fq_name() == candidate.fq_name()
5468                    && peer.source() == candidate.source()
5469            })
5470            .collect::<Vec<_>>();
5471        let (_, family_end) =
5472            self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
5473        if !Self::preprocessor_guard_terms_cover_all_paths(
5474            &self.declaration_family_guard_terms(analyzer, &family),
5475        ) {
5476            return None;
5477        }
5478        // A reference whose own guards pick one branch already reaches that
5479        // branch through the ordinary same-guard path; the family must not
5480        // resurrect the branch the reference contradicts.
5481        if !declaration_guard_requirements(analyzer, self.cpp, candidate)
5482            .iter()
5483            .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
5484        {
5485            return None;
5486        }
5487        (first_declaration_byte(analyzer, candidate)?
5488            == family
5489                .iter()
5490                .filter_map(|peer| first_declaration_byte(analyzer, peer))
5491                .min()?)
5492        .then_some(family_end)
5493    }
5494
5495    fn type_candidate_preserving_target(
5496        &self,
5497        analyzer: &CppGraphSource<'_>,
5498        visible_from: &ProjectFile,
5499        candidate: &CodeUnit,
5500        target: &CodeUnit,
5501    ) -> Option<CodeUnit> {
5502        let mut current = candidate.clone();
5503        let mut matched_target = same_visible_symbol(&current, target)
5504            || self.compatible_primary_template_redeclarations(&current, target);
5505        let mut seen = HashSet::default();
5506        loop {
5507            if !seen.insert(current.clone()) {
5508                return None;
5509            }
5510            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
5511                return matched_target
5512                    .then(|| target.clone())
5513                    .or_else(|| current.is_class().then_some(current));
5514            };
5515            if self.flattened_macro_namespace_alias_target_matches(
5516                analyzer,
5517                visible_from,
5518                &current,
5519                &alias_target,
5520                target,
5521            ) {
5522                return Some(target.clone());
5523            }
5524            if matches!(alias_target, StructuredAliasTarget::Builtin) {
5525                return matched_target
5526                    .then(|| target.clone())
5527                    .or_else(|| current.is_class().then_some(current));
5528            }
5529            // A non-template alias can name a template alias with explicit
5530            // arguments (for example, `using Result = Expected<int>`).  When
5531            // the requested target is that alias's primary declaration, keep
5532            // the primary identity before expanding the RHS arguments.  The
5533            // expansion would otherwise canonicalize through the underlying
5534            // implementation type and lose the target spelling used by the
5535            // forward resolver.
5536            if !self.cpp_template_metadata.contains_key(&current)
5537                && let Some(primary) =
5538                    self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
5539                && (same_visible_symbol(&primary, target)
5540                    || self.compatible_primary_template_redeclarations(&primary, target))
5541            {
5542                return Some(target.clone());
5543            }
5544            if same_visible_symbol(&current, target) {
5545                return Some(target.clone());
5546            }
5547            if self.cpp_template_metadata.contains_key(&current) {
5548                return None;
5549            }
5550            let Some(next) =
5551                self.resolve_structured_alias_target(visible_from, &current, &alias_target)
5552            else {
5553                return matched_target.then(|| target.clone());
5554            };
5555            current = next;
5556            matched_target |= same_visible_symbol(&current, target)
5557                || self.compatible_primary_template_redeclarations(&current, target);
5558        }
5559    }
5560
5561    fn compatible_primary_template_redeclarations(
5562        &self,
5563        left: &CodeUnit,
5564        right: &CodeUnit,
5565    ) -> bool {
5566        let (Some(left_metadata), Some(right_metadata)) = (
5567            self.cpp_template_metadata.get(left),
5568            self.cpp_template_metadata.get(right),
5569        ) else {
5570            return false;
5571        };
5572        left_metadata.primary_fq_name == right_metadata.primary_fq_name
5573            && left_metadata.is_primary()
5574            && right_metadata.is_primary()
5575            && cpp_reconcile_primary_template_parameters(
5576                &[(left, left_metadata), (right, right_metadata)],
5577                right,
5578            )
5579            .is_some()
5580    }
5581
5582    fn alias_candidate_may_preserve_target(
5583        &self,
5584        analyzer: &CppGraphSource<'_>,
5585        visible_from: &ProjectFile,
5586        candidate: &CodeUnit,
5587        target: &CodeUnit,
5588    ) -> bool {
5589        let mut current = candidate.clone();
5590        let mut seen = HashSet::default();
5591        loop {
5592            if same_visible_symbol(&current, target)
5593                || self.compatible_primary_template_redeclarations(&current, target)
5594            {
5595                return true;
5596            }
5597            if self.cpp_template_metadata.contains_key(&current) {
5598                return true;
5599            }
5600            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
5601                return false;
5602            };
5603            let StructuredAliasTarget::Named {
5604                components,
5605                global,
5606                arguments,
5607            } = alias_target
5608            else {
5609                return false;
5610            };
5611            if arguments.is_some() || !seen.insert(current.clone()) {
5612                return true;
5613            }
5614            let qualified = components.join("::");
5615            let next = if global {
5616                unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
5617            } else {
5618                self.resolve_unique_type_for_declaration(visible_from, &current, &qualified)
5619            };
5620            let Some(next) = next else {
5621                return true;
5622            };
5623            current = next;
5624        }
5625    }
5626
5627    /// Every indexed type declaration `raw_name` names when it is written in
5628    /// `declaration`'s namespace: the innermost enclosing namespace that holds
5629    /// the name wins, otherwise the name is looked up unqualified.
5630    fn type_candidates_for_declaration<'b>(
5631        &'b self,
5632        visible_from: &ProjectFile,
5633        declaration: &CodeUnit,
5634        raw_name: &str,
5635    ) -> Vec<&'b CodeUnit> {
5636        let Some(normalized) = normalize_reference_name(raw_name) else {
5637            return Vec::new();
5638        };
5639        if let Some(namespace) = cpp_namespace_for(declaration) {
5640            for prefix in namespace_prefixes(&namespace) {
5641                let qualified = format!("{prefix}::{normalized}");
5642                let candidates = self.type_candidates(visible_from, &qualified);
5643                if !candidates.is_empty() {
5644                    return candidates;
5645                }
5646            }
5647        }
5648        self.type_candidates(visible_from, &normalized)
5649    }
5650
5651    fn resolve_unique_type_for_declaration(
5652        &self,
5653        visible_from: &ProjectFile,
5654        declaration: &CodeUnit,
5655        raw_name: &str,
5656    ) -> Option<CodeUnit> {
5657        unique_logical_type_candidate(self.type_candidates_for_declaration(
5658            visible_from,
5659            declaration,
5660            raw_name,
5661        ))
5662    }
5663
5664    pub fn resolves_to_type(
5665        &self,
5666        analyzer: &CppGraphSource<'_>,
5667        file: &ProjectFile,
5668        raw_name: &str,
5669        target: &CodeUnit,
5670    ) -> bool {
5671        let Some(normalized) = normalize_reference_name(raw_name) else {
5672            return false;
5673        };
5674        let candidates = self.type_candidates(file, &normalized);
5675        if candidates.is_empty() {
5676            return self.parser_alias_resolves_to_type(analyzer, file, raw_name, target);
5677        }
5678        let Some(resolved) =
5679            self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
5680        else {
5681            return false;
5682        };
5683        same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
5684    }
5685
5686    pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
5687        let raw_target = cpp_alias_declaration_target_text(alias.signature()?)?;
5688        let resolved = self.resolve_type_for_declaration(alias.source(), alias, &raw_target)?;
5689        match resolved.kind() {
5690            CodeUnitType::Class => Some(resolved),
5691            _ if is_type_alias(&resolved) => self.alias_target(&resolved),
5692            _ => None,
5693        }
5694    }
5695
5696    /// Whether two callable declarations declare one function.
5697    ///
5698    /// [`same_logical_symbol`] compares the persisted signature strings, which
5699    /// embed each parameter type exactly as it was spelled. A header
5700    /// declaration written inside `namespace zmq { class dist_t { ... } }` says
5701    /// `send_to_matching(msg_t *)` while its out-of-line body at file scope
5702    /// says `zmq::msg_t *`, so the string comparison reports two symbols where
5703    /// C++ ([basic.def], [dcl.fct]) sees one declaration and one definition.
5704    /// This resolves the written parameter names before comparing them and
5705    /// reports the same answer the language does for the cases it can prove.
5706    ///
5707    /// Everything it cannot prove stays two symbols: a template declaration, a
5708    /// parameter with no comparable shape, a name that resolves on one side
5709    /// only, and an alias chain it cannot follow safely (#2010).
5710    pub fn same_logical_callable(
5711        &self,
5712        analyzer: &CppGraphSource<'_>,
5713        left: &CodeUnit,
5714        right: &CodeUnit,
5715    ) -> bool {
5716        if same_logical_symbol(left, right) {
5717            return true;
5718        }
5719        if left.kind() != right.kind()
5720            || !left.is_callable()
5721            || !right.is_callable()
5722            || left.fq_name() != right.fq_name()
5723        {
5724            return false;
5725        }
5726        // A template declaration and its out-of-line body can also diverge
5727        // outside the parameter list - `template <class T>` against
5728        // `template <typename T>` - and the template head is part of the
5729        // persisted signature. Deciding template-head equivalence is a
5730        // separate question, so templates keep string identity.
5731        if self.callable_is_template_declaration(analyzer, left)
5732            || self.callable_is_template_declaration(analyzer, right)
5733        {
5734            return false;
5735        }
5736        let (Some(left_comparable), Some(right_comparable)) = (
5737            self.callable_comparable(analyzer, left),
5738            self.callable_comparable(analyzer, right),
5739        ) else {
5740            return false;
5741        };
5742        // The trailing member `const`, ref-qualifier, `noexcept`, trailing
5743        // return type and requires-clause are part of C++ callable identity and
5744        // an out-of-line definition repeats them verbatim, so they must agree
5745        // as written.
5746        if left_comparable.suffix != right_comparable.suffix
5747            || left_comparable.shapes.len() != right_comparable.shapes.len()
5748        {
5749            return false;
5750        }
5751        left_comparable
5752            .shapes
5753            .iter()
5754            .zip(right_comparable.shapes.iter())
5755            .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
5756                (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
5757                (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
5758                    self.comparable_shapes_agree(analyzer, left_shape, right_shape)
5759                }
5760                // An unstructured parameter records that the reduction failed,
5761                // not that the two spellings mean the same type, so it agrees
5762                // with nothing - including another unstructured parameter.
5763                _ => false,
5764            })
5765    }
5766
5767    /// Compare two parameter shapes node by node with an explicit paired stack.
5768    ///
5769    /// Shape variants and cv-qualifiers must agree exactly at every level; only
5770    /// the named leaves may be spelled differently, and they agree when they
5771    /// resolve to one type declaration.
5772    fn comparable_shapes_agree(
5773        &self,
5774        analyzer: &CppGraphSource<'_>,
5775        left: &CppComparableParameter,
5776        right: &CppComparableParameter,
5777    ) -> bool {
5778        let mut stack = vec![(left.root(), right.root())];
5779        while let Some((left_index, right_index)) = stack.pop() {
5780            match (left.node(left_index), right.node(right_index)) {
5781                (
5782                    CppComparableNode::Named {
5783                        name: left_name,
5784                        primitive: left_primitive,
5785                        konst: left_konst,
5786                        volatil: left_volatil,
5787                    },
5788                    CppComparableNode::Named {
5789                        name: right_name,
5790                        primitive: right_primitive,
5791                        konst: right_konst,
5792                        volatil: right_volatil,
5793                    },
5794                ) => {
5795                    if left_konst != right_konst
5796                        || left_volatil != right_volatil
5797                        || left_primitive != right_primitive
5798                        || !self.comparable_names_agree(
5799                            analyzer,
5800                            left_name,
5801                            right_name,
5802                            *left_primitive,
5803                        )
5804                    {
5805                        return false;
5806                    }
5807                }
5808                (
5809                    CppComparableNode::Pointer {
5810                        inner: left_inner,
5811                        konst: left_konst,
5812                        volatil: left_volatil,
5813                    },
5814                    CppComparableNode::Pointer {
5815                        inner: right_inner,
5816                        konst: right_konst,
5817                        volatil: right_volatil,
5818                    },
5819                ) => {
5820                    if left_konst != right_konst || left_volatil != right_volatil {
5821                        return false;
5822                    }
5823                    stack.push((*left_inner, *right_inner));
5824                }
5825                (
5826                    CppComparableNode::Reference { inner: left_inner },
5827                    CppComparableNode::Reference { inner: right_inner },
5828                )
5829                | (
5830                    CppComparableNode::Array { inner: left_inner },
5831                    CppComparableNode::Array { inner: right_inner },
5832                ) => stack.push((*left_inner, *right_inner)),
5833                (
5834                    CppComparableNode::Generic {
5835                        base: left_base,
5836                        arguments: left_arguments,
5837                    },
5838                    CppComparableNode::Generic {
5839                        base: right_base,
5840                        arguments: right_arguments,
5841                    },
5842                ) => {
5843                    if left_arguments.len() != right_arguments.len() {
5844                        return false;
5845                    }
5846                    stack.push((*left_base, *right_base));
5847                    stack.extend(
5848                        left_arguments.iter().zip(right_arguments.iter()).map(
5849                            |(left_argument, right_argument)| (*left_argument, *right_argument),
5850                        ),
5851                    );
5852                }
5853                _ => return false,
5854            }
5855        }
5856        true
5857    }
5858
5859    /// Whether two written type names denote one type.
5860    ///
5861    /// A primitive denotes the same type in every scope, so its recorded
5862    /// lexical scope is noise and its spelling decides. A nominal name is
5863    /// resolved on each side independently: two resolved names agree when they
5864    /// reach one type declaration, and two unresolved names agree only on
5865    /// exact agreement of what was written, which is no weaker than the
5866    /// whole-signature string equality this comparison replaces. Resolution on
5867    /// one side only is evidence of difference, never of agreement.
5868    fn comparable_names_agree(
5869        &self,
5870        analyzer: &CppGraphSource<'_>,
5871        left: &StructuredTypeName,
5872        right: &StructuredTypeName,
5873        primitive: bool,
5874    ) -> bool {
5875        if primitive {
5876            return left.path() == right.path();
5877        }
5878        match (
5879            self.comparable_name_terminal(analyzer, left),
5880            self.comparable_name_terminal(analyzer, right),
5881        ) {
5882            (Some(left_terminal), Some(right_terminal)) => {
5883                same_logical_symbol(&left_terminal, &right_terminal)
5884            }
5885            (None, None) => {
5886                left.path() == right.path() && left.is_absolute() == right.is_absolute()
5887            }
5888            _ => false,
5889        }
5890    }
5891
5892    /// The class declaration a written type name denotes, or `None` when the
5893    /// workspace cannot prove one.
5894    ///
5895    /// The lookup is a closure-independent lexical-scope prefix walk over the
5896    /// workspace definition index rather than a visibility lookup: the index
5897    /// handed to a definition query is rooted at the reference file, and a
5898    /// body's `.cpp` is almost never in that file's include closure. Any name
5899    /// this walk resolves is one an enclosing-scope lookup could resolve, so it
5900    /// cannot invent a type the compiler could not see; `using`-directives are
5901    /// not modelled, and a name that needs one stays unresolved.
5902    fn comparable_name_terminal(
5903        &self,
5904        analyzer: &CppGraphSource<'_>,
5905        name: &StructuredTypeName,
5906    ) -> Option<CodeUnit> {
5907        let mut current = self.comparable_name_declaration(analyzer, name)?;
5908        let mut visited = HashSet::default();
5909        for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
5910            // The alias question is asked before the class question, and
5911            // through `declared_type_alias` rather than `is_type_alias`,
5912            // because extraction records `using A8 = A7;` as a *Class* unit
5913            // whose signature is the alias declaration. Reading the kind first
5914            // would end the chase on the alias itself and report an alias
5915            // spelling and its underlying class as two types (#2010).
5916            if !declared_type_alias(analyzer, &current) {
5917                return current.is_class().then_some(current);
5918            }
5919            if !visited.insert(current.clone()) {
5920                return None;
5921            }
5922            let signature = current.signature()?;
5923            // `cpp_alias_declaration_target_text` reads the declaration's
5924            // `type` field only, so `typedef Foo *Bar` reports `Foo` and the
5925            // pointer is silently dropped. Substituting such an alias would
5926            // fuse `f(Bar)` and `f(Foo)`, which are two functions.
5927            if cpp_alias_declaration_adds_indirection(signature) {
5928                return None;
5929            }
5930            let raw_target = cpp_alias_declaration_target_text(signature)?;
5931            current = self.comparable_alias_target(analyzer, &current, &raw_target)?;
5932        }
5933        None
5934    }
5935
5936    /// The declaration one alias hop lands on: the type `raw_target` names,
5937    /// looked up from the alias declaration's own enclosing namespace.
5938    ///
5939    /// The hop takes the same closure-independent prefix walk the first lookup
5940    /// took, and deliberately not `resolve_type_for_declaration`: that one
5941    /// answers out of the `VisibilityIndex`, which is rooted at the reference
5942    /// file, while the alias declaration this hop starts from is reached
5943    /// through the workspace definition index and its file need not be in that
5944    /// root's include closure - where the visibility lookup answers nothing and
5945    /// the chase would stop on the alias itself (#2010).
5946    fn comparable_alias_target(
5947        &self,
5948        analyzer: &CppGraphSource<'_>,
5949        alias: &CodeUnit,
5950        raw_target: &str,
5951    ) -> Option<CodeUnit> {
5952        // `raw_target` is the alias declaration's written type text, so it is a
5953        // plain `::`-joined qualified-id: the same domain the shared symbol-path
5954        // parser reads, and the same leading `::` that marks an absolute name
5955        // everywhere else this crate normalizes a reference.
5956        let absolute = raw_target.trim_start().starts_with("::");
5957        let normalized = normalize_reference_name(raw_target)?;
5958        let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5959            brokk_bifrost_core::analyzer::Language::Cpp,
5960            &normalized,
5961        );
5962        let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
5963            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5964                brokk_bifrost_core::analyzer::Language::Cpp,
5965                &namespace,
5966            )
5967        });
5968        let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
5969        self.comparable_name_declaration(analyzer, &name)
5970    }
5971
5972    /// The one type declaration `name` names, by enclosing scope, innermost
5973    /// first.
5974    ///
5975    /// The first prefix depth that names anything decides: an inner scope hides
5976    /// an outer one, so a match there is the answer even when an outer scope
5977    /// also declares the name. Several logically distinct declarations at that
5978    /// depth are an ambiguity this comparison must not guess at.
5979    fn comparable_name_declaration(
5980        &self,
5981        analyzer: &CppGraphSource<'_>,
5982        name: &StructuredTypeName,
5983    ) -> Option<CodeUnit> {
5984        let definitions = analyzer.workspace_definitions();
5985        let interner = segment_interner();
5986        let first_depth = if name.is_absolute() {
5987            0
5988        } else {
5989            name.lexical_scope().len()
5990        };
5991        for depth in (0..=first_depth).rev() {
5992            let mut structured = FqName::new();
5993            for component in name.lexical_scope()[..depth].iter().chain(name.path()) {
5994                structured.push(interner.intern(component, SegmentKind::Unknown));
5995            }
5996            let mut candidates = definitions
5997                .identifier(&structured)
5998                .into_iter()
5999                .filter(|unit| unit.fq().same_segment_texts(&structured))
6000                .filter(|unit| {
6001                    unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
6002                });
6003            let Some(first) = candidates.next() else {
6004                continue;
6005            };
6006            return candidates
6007                .all(|unit| same_logical_symbol(&unit, &first))
6008                .then_some(first);
6009        }
6010        None
6011    }
6012
6013    /// The comparison inputs of one callable declaration, extracted once.
6014    ///
6015    /// The comparison itself runs only when two candidates share kind and fully
6016    /// qualified name but not signature, which is rare; re-reading the same
6017    /// declaration for every pair in a candidate set is not.
6018    fn callable_comparable(
6019        &self,
6020        analyzer: &CppGraphSource<'_>,
6021        unit: &CodeUnit,
6022    ) -> Option<Arc<ExtractedComparable>> {
6023        if let Some(cached) = self
6024            .callable_comparables
6025            .lock()
6026            .expect("C++ callable comparable cache poisoned")
6027            .get(unit)
6028            .cloned()
6029        {
6030            return cached;
6031        }
6032        let extracted = self
6033            .extract_callable_comparable(analyzer, unit)
6034            .map(Arc::new);
6035        self.callable_comparables
6036            .lock()
6037            .expect("C++ callable comparable cache poisoned")
6038            .insert(unit.clone(), extracted.clone());
6039        extracted
6040    }
6041
6042    fn extract_callable_comparable(
6043        &self,
6044        analyzer: &CppGraphSource<'_>,
6045        unit: &CodeUnit,
6046    ) -> Option<ExtractedComparable> {
6047        let prepared = self.cpp.prepared_syntax(self.token, unit.source())?;
6048        let root = prepared.tree().root_node();
6049        let declarator = analyzer
6050            .ranges(unit)
6051            .into_iter()
6052            .find_map(|range| cpp_function_declarator_at(root, range.start_byte))?;
6053        Some(ExtractedComparable {
6054            // One question about one declarator: indexing the file's tree would
6055            // cost more than the walk it saves.
6056            shapes: cpp_comparable_parameter_shapes(
6057                declarator,
6058                prepared.source(),
6059                &ParentIndex::unindexed(),
6060            ),
6061            suffix: cpp_callable_identity_suffix(declarator, prepared.source())?,
6062        })
6063    }
6064
6065    pub fn canonical_type_for_reference(
6066        &self,
6067        file: &ProjectFile,
6068        raw_name: &str,
6069    ) -> Option<CodeUnit> {
6070        let resolved = self.resolve_type(file, raw_name)?;
6071        self.alias_target(&resolved).or(Some(resolved))
6072    }
6073
6074    pub fn parser_alias_resolves_to_type(
6075        &self,
6076        analyzer: &CppGraphSource<'_>,
6077        file: &ProjectFile,
6078        raw_name: &str,
6079        target: &CodeUnit,
6080    ) -> bool {
6081        let Some(alias_name) = normalize_reference_name(raw_name) else {
6082            return false;
6083        };
6084        let Some(cpp) = analyzer.cpp else {
6085            return false;
6086        };
6087        let matches_file = |source_file: &ProjectFile| {
6088            self.file_alias_matches(cpp, source_file, &alias_name, target)
6089        };
6090        self.visible_source_files_by_root.get(file).map_or_else(
6091            || matches_file(file),
6092            |files| files.iter().any(matches_file),
6093        )
6094    }
6095
6096    fn file_alias_matches(
6097        &self,
6098        cpp: &dyn CppSource,
6099        file: &ProjectFile,
6100        alias_name: &str,
6101        target: &CodeUnit,
6102    ) -> bool {
6103        let cell = {
6104            let mut cells = self.alias_cells.lock().expect("alias cell map lock");
6105            Arc::clone(
6106                cells
6107                    .entry(file.clone())
6108                    .or_insert_with(|| Arc::new(OnceLock::new())),
6109            )
6110        };
6111        cell.get_or_init(|| {
6112            #[cfg(any(test, feature = "test-support"))]
6113            {
6114                *self
6115                    .alias_source_parse_counts
6116                    .lock()
6117                    .expect("alias source parse count lock")
6118                    .entry(file.clone())
6119                    .or_default() += 1;
6120            }
6121            aliases_from_prepared_source(cpp, self.token, file).into_boxed_slice()
6122        })
6123        .iter()
6124        .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
6125    }
6126
6127    #[cfg(any(test, feature = "test-support"))]
6128    pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
6129        self.visible_source_files_by_root
6130            .get(file)
6131            .cloned()
6132            .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
6133    }
6134
6135    #[cfg(any(test, feature = "test-support"))]
6136    pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
6137        self.alias_source_parse_counts
6138            .lock()
6139            .expect("alias source parse count lock")
6140            .get(file)
6141            .copied()
6142            .unwrap_or(0)
6143    }
6144
6145    pub fn resolve_named(
6146        &self,
6147        file: &ProjectFile,
6148        raw_name: &str,
6149        kind: TargetKind,
6150    ) -> Option<CodeUnit> {
6151        let normalized = normalize_reference_name(raw_name)?;
6152        self.named_candidates_for_normalized(file, &normalized, kind)
6153            .into_iter()
6154            .next()
6155            .cloned()
6156    }
6157
6158    pub fn contains_named_symbol(
6159        &self,
6160        file: &ProjectFile,
6161        raw_name: &str,
6162        kind: TargetKind,
6163        target: &CodeUnit,
6164    ) -> bool {
6165        let Some(normalized) = normalize_reference_name(raw_name) else {
6166            return false;
6167        };
6168        self.named_candidates_for_normalized(file, &normalized, kind)
6169            .into_iter()
6170            .any(|unit| {
6171                matches_kind_for_lookup(unit, kind)
6172                    && reference_matches_unit(&normalized, unit)
6173                    && same_visible_symbol(unit, target)
6174            })
6175    }
6176
6177    pub fn named_candidates(
6178        &self,
6179        file: &ProjectFile,
6180        raw_name: &str,
6181        kind: TargetKind,
6182    ) -> Vec<CodeUnit> {
6183        let Some(normalized) = normalize_reference_name(raw_name) else {
6184            return Vec::new();
6185        };
6186        self.named_candidates_for_normalized(file, &normalized, kind)
6187            .into_iter()
6188            .cloned()
6189            .collect()
6190    }
6191
6192    pub fn resolve_known_non_target(
6193        &self,
6194        file: &ProjectFile,
6195        raw_name: &str,
6196        kind: TargetKind,
6197        target: &CodeUnit,
6198    ) -> bool {
6199        let Some(normalized) = normalize_reference_name(raw_name) else {
6200            return false;
6201        };
6202        normalized.contains("::")
6203            && self
6204                .named_candidates_for_normalized(file, &normalized, kind)
6205                .into_iter()
6206                .any(|unit| {
6207                    matches_kind_for_lookup(unit, kind)
6208                        && reference_matches_unit(&normalized, unit)
6209                        && !same_visible_symbol(unit, target)
6210                })
6211    }
6212
6213    pub fn resolve_call_return_binding(
6214        &self,
6215        analyzer: &CppGraphSource<'_>,
6216        file: &ProjectFile,
6217        raw_name: &str,
6218        arity: usize,
6219        lexical_namespace: Option<&str>,
6220        direct_type: Option<&CodeUnit>,
6221    ) -> Option<CppScanBinding> {
6222        let normalized = normalize_reference_name(raw_name)?;
6223        let mut candidates = Vec::new();
6224        for function in
6225            self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
6226        {
6227            if cpp_callable_arity(analyzer, function).accepts(arity)
6228                && !direct_type.is_some_and(|direct_type| {
6229                    self.callable_is_constructor_declaration(analyzer, function)
6230                        && type_owner_of(analyzer, function)
6231                            .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
6232                })
6233            {
6234                candidates.push(function.clone());
6235            }
6236        }
6237        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
6238        unanimous_return_binding(analyzer, self, file, &candidates)
6239    }
6240
6241    pub fn resolve_call_return_binding_without_arity(
6242        &self,
6243        analyzer: &CppGraphSource<'_>,
6244        file: &ProjectFile,
6245        raw_name: &str,
6246        lexical_namespace: Option<&str>,
6247        direct_type: Option<&CodeUnit>,
6248    ) -> (bool, Option<CppScanBinding>) {
6249        let Some(normalized) = normalize_reference_name(raw_name) else {
6250            return (false, None);
6251        };
6252        let mut candidates = self
6253            .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
6254            .into_iter()
6255            .filter(|function| {
6256                function.is_function()
6257                    && !direct_type.is_some_and(|direct_type| {
6258                        self.callable_is_constructor_declaration(analyzer, function)
6259                            && type_owner_of(analyzer, function)
6260                                .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
6261                    })
6262            })
6263            .cloned()
6264            .collect::<Vec<_>>();
6265        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
6266        let has_candidates = !candidates.is_empty();
6267        (
6268            has_candidates,
6269            unanimous_return_binding(analyzer, self, file, &candidates),
6270        )
6271    }
6272
6273    pub fn visible_identifier_candidates<'b>(
6274        &'b self,
6275        file: &ProjectFile,
6276        identifier: &str,
6277    ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
6278        self.visible_by_identifier
6279            .get(file)
6280            .and_then(|by_name| by_name.get(identifier))
6281            .into_iter()
6282            .flatten()
6283    }
6284
6285    /// Return terminal reference names that can denote `target` from `file`.
6286    ///
6287    /// The indexed candidate table covers ordinary declarations and aliases;
6288    /// parser-only aliases are read through their per-file cells so this path
6289    /// never reparses a source that has already been inspected by the visibility
6290    /// index.
6291    pub fn visible_type_reference_component_names_for_target(
6292        &self,
6293        analyzer: &CppGraphSource<'_>,
6294        file: &ProjectFile,
6295        target: &CodeUnit,
6296    ) -> HashSet<String> {
6297        let mut names = HashSet::from_iter([target.identifier().to_string()]);
6298        if let Some(metadata) = self.cpp_template_metadata.get(target) {
6299            names.insert(metadata.primary_name.clone());
6300        }
6301
6302        if let Some(by_identifier) = self.visible_by_identifier.get(file) {
6303            for (identifier, candidates) in by_identifier {
6304                if candidates.iter().any(|candidate| {
6305                    (candidate.is_class()
6306                        && (same_visible_symbol(candidate, target)
6307                            || self.compatible_primary_template_redeclarations(candidate, target)))
6308                        || (declared_type_alias(analyzer, candidate)
6309                            && self.alias_candidate_may_preserve_target(
6310                                analyzer, file, candidate, target,
6311                            ))
6312                }) {
6313                    names.insert(identifier.clone());
6314                }
6315            }
6316        }
6317
6318        names.extend(self.visible_parser_alias_names_for_target(file, target));
6319
6320        names
6321    }
6322
6323    pub fn indexed_structural_class_scope(
6324        &self,
6325        file: &ProjectFile,
6326        class: Node<'_>,
6327        source: &str,
6328    ) -> Option<Vec<String>> {
6329        let key = (file.clone(), class.start_byte(), class.end_byte());
6330        if let Some(cached) = self
6331            .indexed_structural_class_scopes
6332            .lock()
6333            .expect("C++ indexed structural-class scope cache poisoned")
6334            .get(&key)
6335            .cloned()
6336        {
6337            return cached;
6338        }
6339        let resolved = (|| {
6340            let name = class.child_by_field_name("name")?;
6341            let identifier = if name.kind() == "template_type" {
6342                node_text(name.child_by_field_name("name")?, source).to_string()
6343            } else {
6344                let mut components = Vec::new();
6345                append_cpp_name_components(name, source, &mut components)?;
6346                components.last()?.clone()
6347            };
6348            let visible = self
6349                .visible_identifier_candidates(file, &identifier)
6350                .cloned()
6351                .collect::<Vec<_>>();
6352            let mut visible = visible;
6353            for candidate in
6354                self.visible_by_file
6355                    .get(file)
6356                    .into_iter()
6357                    .flatten()
6358                    .filter(|candidate| {
6359                        self.cpp_template_metadata
6360                            .get(candidate)
6361                            .is_some_and(|metadata| metadata.primary_name == identifier)
6362                    })
6363            {
6364                if !visible
6365                    .iter()
6366                    .any(|existing| same_logical_symbol(existing, candidate))
6367                {
6368                    visible.push(candidate.clone());
6369                }
6370            }
6371            // Built once per call rather than per candidate; `cpp_source` rebuilds
6372            // the five-field source from the same `self.cpp` on every call.
6373            let cpp_source = self.cpp_source();
6374            let candidates = visible
6375                .iter()
6376                .filter(|candidate| {
6377                    candidate.source() == file
6378                        && candidate.is_class()
6379                        && !declared_type_alias(&cpp_source, candidate)
6380                        && self.cpp.ranges(candidate).iter().any(|range| {
6381                            range.start_byte <= class.start_byte()
6382                                && class.end_byte() <= range.end_byte
6383                        })
6384                })
6385                .collect::<Vec<_>>();
6386            let owner = if name.kind() == "template_type" {
6387                let expected = normalize_cpp_whitespace(node_text(name, source));
6388                let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
6389                let exact = candidates
6390                    .iter()
6391                    .copied()
6392                    .filter(|candidate| {
6393                        candidate
6394                            .fq()
6395                            .segments()
6396                            .iter()
6397                            .rev()
6398                            .find_map(|&segment| {
6399                                let (text, kind) = interner.resolve(segment);
6400                                matches!(
6401                                    kind,
6402                                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
6403                                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
6404                                )
6405                                .then_some(text)
6406                            })
6407                            .is_some_and(|text| text == expected)
6408                    })
6409                    .collect::<Vec<_>>();
6410                unique_logical_type_candidate(exact)
6411                    .or_else(|| unique_logical_type_candidate(candidates.clone()))?
6412            } else {
6413                unique_logical_type_candidate(candidates)?
6414            };
6415            Some(canonical_cpp_scope_components(&owner))
6416        })();
6417        self.indexed_structural_class_scopes
6418            .lock()
6419            .expect("C++ indexed structural-class scope cache poisoned")
6420            .insert(key, resolved.clone());
6421        resolved
6422    }
6423
6424    pub fn indexed_enclosing_owner_scope(
6425        &self,
6426        analyzer: &CppGraphSource<'_>,
6427        file: &ProjectFile,
6428        node: Node<'_>,
6429    ) -> Option<Vec<String>> {
6430        let anchor = std::iter::successors(Some(node), |current| current.parent())
6431            .find(|current| {
6432                matches!(
6433                    current.kind(),
6434                    "function_definition"
6435                        | "class_specifier"
6436                        | "struct_specifier"
6437                        | "union_specifier"
6438                )
6439            })
6440            .unwrap_or(node);
6441        let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
6442        if let Some(cached) = self
6443            .indexed_enclosing_owner_scopes
6444            .lock()
6445            .expect("C++ indexed enclosing-owner scope cache poisoned")
6446            .get(&key)
6447            .cloned()
6448        {
6449            return cached;
6450        }
6451        let resolved = (|| {
6452            let range = Range {
6453                start_byte: node.start_byte(),
6454                end_byte: node.end_byte(),
6455                start_line: node.start_position().row,
6456                end_line: node.end_position().row,
6457            };
6458            let start = analyzer.enclosing_code_unit(file, &range)?;
6459            let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
6460                start,
6461                |unit| self.cached_precise_parent_of(analyzer, unit),
6462            )
6463            .find(|unit| {
6464                unit.is_class()
6465                    && !analyzer
6466                        .type_alias_provider()
6467                        .is_some_and(|provider| provider.is_type_alias(unit))
6468            })?;
6469            Some(canonical_cpp_scope_components(&owner))
6470        })();
6471        self.indexed_enclosing_owner_scopes
6472            .lock()
6473            .expect("C++ indexed enclosing-owner scope cache poisoned")
6474            .insert(key, resolved.clone());
6475        resolved
6476    }
6477
6478    fn cached_precise_parent_of(
6479        &self,
6480        analyzer: &CppGraphSource<'_>,
6481        code_unit: &CodeUnit,
6482    ) -> Option<CodeUnit> {
6483        if let Some(cached) = self
6484            .precise_parent_cache
6485            .lock()
6486            .expect("C++ precise-parent cache poisoned")
6487            .get(code_unit)
6488            .cloned()
6489        {
6490            return cached;
6491        }
6492        let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
6493        self.precise_parent_cache
6494            .lock()
6495            .expect("C++ precise-parent cache poisoned")
6496            .insert(code_unit.clone(), resolved.clone());
6497        resolved
6498    }
6499
6500    pub fn callable_is_constructor_declaration(
6501        &self,
6502        analyzer: &CppGraphSource<'_>,
6503        candidate: &CodeUnit,
6504    ) -> bool {
6505        if !candidate.is_function() {
6506            return false;
6507        }
6508        let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
6509            return false;
6510        };
6511        let root = prepared.tree().root_node();
6512        let candidate_ranges = analyzer.ranges(candidate);
6513        let enclosed_by_matching_type = candidate_ranges.iter().any(|range| {
6514            let mut current = root
6515                .descendant_for_byte_range(range.start_byte, range.end_byte)
6516                .and_then(|node| node.parent());
6517            while let Some(node) = current {
6518                if matches!(
6519                    node.kind(),
6520                    "class_specifier" | "struct_specifier" | "union_specifier"
6521                ) {
6522                    return node
6523                        .child_by_field_name("name")
6524                        .map(|name| terminal_name(node_text(name, prepared.source())))
6525                        .is_some_and(|name| name == candidate.identifier());
6526                }
6527                current = node.parent();
6528            }
6529            false
6530        });
6531        if enclosed_by_matching_type {
6532            return true;
6533        }
6534        let indexed_containment = analyzer
6535            .declarations(candidate.source())
6536            .into_iter()
6537            .filter(|unit| unit.is_class() && unit.identifier() == candidate.identifier())
6538            .any(|owner| {
6539                analyzer.ranges(&owner).iter().any(|owner_range| {
6540                    candidate_ranges.iter().any(|candidate_range| {
6541                        owner_range.start_byte <= candidate_range.start_byte
6542                            && candidate_range.end_byte <= owner_range.end_byte
6543                    })
6544                })
6545            });
6546        if indexed_containment {
6547            return true;
6548        }
6549        let metadata = analyzer.signature_metadata(candidate);
6550        !metadata.is_empty()
6551            && metadata
6552                .iter()
6553                .all(|signature| signature.return_type_text().is_none())
6554    }
6555
6556    /// Whether a callable declaration is a class-template deduction guide.
6557    ///
6558    /// Tree-sitter represents `Box(T) -> Box<T>;` as a declaration with no
6559    /// type field whose function declarator owns a trailing return type. This
6560    /// structured shape distinguishes a guide from both a constructor (no
6561    /// trailing return) and an ordinary trailing-return function (an `auto`
6562    /// type field).
6563    pub fn callable_is_deduction_guide_declaration(
6564        &self,
6565        analyzer: &CppGraphSource<'_>,
6566        candidate: &CodeUnit,
6567    ) -> bool {
6568        if !candidate.is_function() {
6569            return false;
6570        }
6571        let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
6572            return false;
6573        };
6574        nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
6575            .into_iter()
6576            .any(|declaration| {
6577                if declaration.kind() != "declaration"
6578                    || declaration.child_by_field_name("type").is_some()
6579                {
6580                    return false;
6581                }
6582                let Some(declarator) = declaration.child_by_field_name("declarator") else {
6583                    return false;
6584                };
6585                if declarator.kind() != "function_declarator" {
6586                    return false;
6587                }
6588                let mut cursor = declarator.walk();
6589                let has_trailing_return = declarator
6590                    .named_children(&mut cursor)
6591                    .any(|child| child.kind() == "trailing_return_type");
6592                has_trailing_return
6593                    && declarator_name_node(declarator).is_some_and(|name| {
6594                        node_text(name, prepared.source()) == candidate.identifier()
6595                    })
6596            })
6597    }
6598
6599    /// Whether a callable occurrence is directly wrapped by a C++ template
6600    /// declaration. This deliberately inspects declaration syntax instead of
6601    /// inferring template status from the rendered signature.
6602    pub fn callable_is_template_declaration(
6603        &self,
6604        analyzer: &CppGraphSource<'_>,
6605        candidate: &CodeUnit,
6606    ) -> bool {
6607        if !candidate.is_function() {
6608            return false;
6609        }
6610        let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
6611            return false;
6612        };
6613        let root = prepared.tree().root_node();
6614        analyzer.ranges(candidate).iter().any(|range| {
6615            let Some(node) = node_for_exact_range(root, range)
6616                .or_else(|| root.descendant_for_byte_range(range.start_byte, range.end_byte))
6617            else {
6618                return false;
6619            };
6620            node.parent().is_some_and(|parent| {
6621                parent.kind() == "template_declaration"
6622                    && parent
6623                        .named_child(parent.named_child_count().saturating_sub(1))
6624                        .is_some_and(|declaration| same_node(declaration, node))
6625            })
6626        })
6627    }
6628
6629    pub fn type_name_candidates<'b>(
6630        &'b self,
6631        file: &ProjectFile,
6632        normalized: &str,
6633    ) -> Vec<&'b CodeUnit> {
6634        self.candidate_units(file, normalized, TargetKind::Type)
6635    }
6636
6637    pub fn visible_members_for_owner_name<'b>(
6638        &'b self,
6639        file: &ProjectFile,
6640        owner: &CodeUnit,
6641        name: &str,
6642    ) -> Vec<&'b CodeUnit> {
6643        self.visible_identifier_candidates(file, name)
6644            .filter(|unit| {
6645                // Structured owner pop on the unit's own `fq()` (shared with
6646                // `CodeUnitIndex::parent_of`), not a re-split of its rendered fqn
6647                // string.
6648                brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
6649                    .is_some_and(|parent| parent == owner.fq_name())
6650            })
6651            .collect()
6652    }
6653
6654    pub fn visible_member_for_owner_name(
6655        &self,
6656        file: &ProjectFile,
6657        owner: &CodeUnit,
6658        name: &str,
6659    ) -> VisibleMemberResolution {
6660        let candidates = self.visible_members_for_owner_name(file, owner, name);
6661        let mut callables = Vec::new();
6662        let mut non_callable = None;
6663        for candidate in candidates {
6664            if candidate.is_function() {
6665                callables.push(candidate.clone());
6666            } else if non_callable.is_none() {
6667                non_callable = Some(candidate.clone());
6668            }
6669        }
6670        match (callables.is_empty(), non_callable) {
6671            (false, None) => VisibleMemberResolution::Callable(callables),
6672            (true, Some(_)) => VisibleMemberResolution::NonCallable,
6673            (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
6674            (true, None) => VisibleMemberResolution::Missing,
6675        }
6676    }
6677
6678    fn field_declared_type_fact(
6679        &self,
6680        analyzer: &CppGraphSource<'_>,
6681        field: &CodeUnit,
6682    ) -> Option<DeclaredFieldTypeFact> {
6683        if let Some(cached) = self
6684            .field_type_facts
6685            .lock()
6686            .expect("C++ field type fact cache poisoned")
6687            .get(field)
6688            .cloned()
6689        {
6690            return cached;
6691        }
6692        let decoded = decode_field_declared_type_fact(analyzer, field);
6693        self.field_type_facts
6694            .lock()
6695            .expect("C++ field type fact cache poisoned")
6696            .insert(field.clone(), decoded.clone());
6697        decoded
6698    }
6699
6700    fn structured_alias_target(
6701        &self,
6702        analyzer: &CppGraphSource<'_>,
6703        unit: &CodeUnit,
6704    ) -> Option<StructuredAliasTarget> {
6705        if let Some(cached) = self
6706            .structured_alias_targets
6707            .lock()
6708            .expect("C++ structured alias target cache poisoned")
6709            .get(unit)
6710            .cloned()
6711        {
6712            return cached;
6713        }
6714        let decoded = decode_structured_alias_target(analyzer, unit);
6715        self.structured_alias_targets
6716            .lock()
6717            .expect("C++ structured alias target cache poisoned")
6718            .insert(unit.clone(), decoded.clone());
6719        decoded
6720    }
6721
6722    pub fn type_candidates<'b>(
6723        &'b self,
6724        file: &ProjectFile,
6725        normalized: &str,
6726    ) -> Vec<&'b CodeUnit> {
6727        let mut candidates = self
6728            .candidate_units(file, normalized, TargetKind::Type)
6729            .into_iter()
6730            .filter(|unit| unit.kind() == CodeUnitType::Class || is_type_alias(unit))
6731            .collect::<Vec<_>>();
6732        dedup_unit_refs(&mut candidates);
6733        candidates
6734    }
6735
6736    pub fn named_candidates_for_normalized<'b>(
6737        &'b self,
6738        file: &ProjectFile,
6739        normalized: &str,
6740        kind: TargetKind,
6741    ) -> Vec<&'b CodeUnit> {
6742        let mut candidates = self
6743            .candidate_units(file, normalized, kind)
6744            .into_iter()
6745            .filter(|unit| {
6746                matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
6747            })
6748            .collect::<Vec<_>>();
6749        dedup_unit_refs(&mut candidates);
6750        candidates
6751    }
6752
6753    pub fn candidate_units<'b>(
6754        &'b self,
6755        file: &ProjectFile,
6756        normalized: &str,
6757        kind: TargetKind,
6758    ) -> Vec<&'b CodeUnit> {
6759        if normalized.contains("::") {
6760            // `normalized` comes from `normalize_cpp_reference_text`, which
6761            // truncates at the first `(`/`{`/`<`, leaving a plain `::`-joined
6762            // qualified-id with no embedded `.`/`/`/`\` and operator tokens
6763            // kept intact by the shared splitter's operator merge — the same
6764            // domain `cpp_reference_fqn_candidates` below already parses with
6765            // the shared splitter. Re-tokenizing and taking the last segment
6766            // reproduces `rsplit("::").find(non-empty)`'s terminal-component
6767            // scan exactly.
6768            let Some(identifier) = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6769                brokk_bifrost_core::analyzer::Language::Cpp,
6770                normalized,
6771            )
6772            .pop() else {
6773                return Vec::new();
6774            };
6775            let fqns = cpp_reference_fqn_candidates(normalized, kind);
6776            return self
6777                .visible_identifier_candidates(file, &identifier)
6778                .filter(|unit| {
6779                    #[cfg(any(test, feature = "test-support"))]
6780                    self.qualified_candidate_inspections
6781                        .fetch_add(1, Ordering::Relaxed);
6782                    fqns.iter().any(|fqn| unit.fq_name() == *fqn)
6783                        || canonical_cpp_name_matches(unit, normalized)
6784                })
6785                .collect();
6786        }
6787        self.visible_identifier_candidates(file, normalized)
6788            .collect()
6789    }
6790
6791    #[cfg(any(test, feature = "test-support"))]
6792    pub fn reset_qualified_candidate_inspections(&self) {
6793        self.qualified_candidate_inspections
6794            .store(0, Ordering::Relaxed);
6795    }
6796
6797    #[cfg(any(test, feature = "test-support"))]
6798    pub fn qualified_candidate_inspections(&self) -> usize {
6799        self.qualified_candidate_inspections.load(Ordering::Relaxed)
6800    }
6801
6802    #[cfg(any(test, feature = "test-support"))]
6803    pub fn reset_target_preserving_type_resolution_count(&self) {
6804        self.target_preserving_type_resolution_count
6805            .store(0, Ordering::Relaxed);
6806    }
6807
6808    #[cfg(any(test, feature = "test-support"))]
6809    pub fn target_preserving_type_resolution_count(&self) -> usize {
6810        self.target_preserving_type_resolution_count
6811            .load(Ordering::Relaxed)
6812    }
6813
6814    #[cfg(any(test, feature = "test-support"))]
6815    pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
6816        self.visible_parser_alias_name_set_build_count
6817            .load(Ordering::Relaxed)
6818    }
6819
6820    #[cfg(any(test, feature = "test-support"))]
6821    pub fn visible_parser_alias_target_names_build_count(&self) -> usize {
6822        self.visible_parser_alias_target_names_build_count
6823            .load(Ordering::Relaxed)
6824    }
6825}
6826
6827#[derive(Default)]
6828struct IncludeGraph {
6829    targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
6830}
6831
6832impl IncludeGraph {
6833    fn extend_with<F>(
6834        &mut self,
6835        root: &ProjectFile,
6836        cancellation: Option<&CancellationToken>,
6837        targets_for: &mut F,
6838    ) where
6839        F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
6840    {
6841        let mut stack = vec![root.clone()];
6842        while let Some(file) = stack.pop() {
6843            if cancellation.is_some_and(CancellationToken::is_cancelled) {
6844                break;
6845            }
6846            if self.targets_by_file.contains_key(&file) {
6847                continue;
6848            }
6849            let targets = targets_for(&file);
6850            stack.extend(targets.iter().cloned());
6851            self.targets_by_file.insert(file, targets);
6852        }
6853    }
6854
6855    fn files(&self) -> impl Iterator<Item = &ProjectFile> {
6856        self.targets_by_file.keys()
6857    }
6858
6859    fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
6860        self.targets_by_file
6861            .get(file)
6862            .map(Vec::as_slice)
6863            .unwrap_or_default()
6864    }
6865}
6866
6867pub struct VisibilityData {
6868    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
6869    pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
6870}
6871
6872/// Build the per-root include closure and the declarations each root can see
6873/// through it.
6874///
6875/// `declarations_for` takes the reading to answer in (issue #1970): a root
6876/// compiled as C sees the C reading of every file in its closure, a root
6877/// compiled as C++ sees the C++ reading, and `reading_is_c_for` decides which
6878/// per root. The two readings agree for all but a handful of headers, so the
6879/// C map is built only when some root actually asks for it, and only over the
6880/// files that root reaches.
6881pub fn build_visibility_data<F, R, D>(
6882    roots: &HashSet<ProjectFile>,
6883    cancellation: Option<&CancellationToken>,
6884    mut targets_for: F,
6885    mut reading_is_c_for: R,
6886    mut declarations_for: D,
6887) -> VisibilityData
6888where
6889    F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
6890    R: FnMut(&ProjectFile) -> bool,
6891    D: FnMut(&ProjectFile, bool) -> BTreeSet<CodeUnit>,
6892{
6893    let mut include_graph = IncludeGraph::default();
6894    for file in roots {
6895        if cancellation.is_some_and(CancellationToken::is_cancelled) {
6896            break;
6897        }
6898        include_graph.extend_with(file, cancellation, &mut targets_for);
6899    }
6900    let cpp_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
6901        .files()
6902        .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
6903        .map(|file| (file.clone(), declarations_for(file, false)))
6904        .collect();
6905    let mut c_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = HashMap::default();
6906    let mut visible_by_file = HashMap::default();
6907    let mut visible_source_files_by_root = HashMap::default();
6908    for file in roots {
6909        if cancellation.is_some_and(CancellationToken::is_cancelled) {
6910            break;
6911        }
6912        let mut visited = HashSet::default();
6913        let mut visible = HashSet::default();
6914        let declarations_by_file = if reading_is_c_for(file) {
6915            for reached in cpp_declarations_by_file.keys() {
6916                if !c_declarations_by_file.contains_key(reached) {
6917                    let declarations = declarations_for(reached, true);
6918                    c_declarations_by_file.insert(reached.clone(), declarations);
6919                }
6920            }
6921            &c_declarations_by_file
6922        } else {
6923            &cpp_declarations_by_file
6924        };
6925        collect_visible_declarations(
6926            &include_graph,
6927            declarations_by_file,
6928            file,
6929            &mut visited,
6930            &mut visible,
6931            cancellation,
6932        );
6933        visible_by_file.insert(file.clone(), visible);
6934        visible_source_files_by_root.insert(file.clone(), visited);
6935    }
6936    VisibilityData {
6937        visible_by_file,
6938        visible_source_files_by_root,
6939    }
6940}
6941
6942/// Admit the class that an out-of-line definition proves is in scope.
6943///
6944/// `Owner::member(...) { ... }` in a file is structured proof that `Owner`
6945/// names a class-like entity in that file's scope: a member declaration can
6946/// live in a file other than its class's only when it is written out of line.
6947/// A file a build concatenates rather than compiles carries no `#include` edge
6948/// to the header declaring `Owner` -- google/wuffs
6949/// `internal/cgen/auxiliary/image.cc` defines
6950/// `DecodeImageResult::DecodeImageResult` and never includes `image.hh` -- so
6951/// every unqualified member and constructor reference in it had no candidate at
6952/// all (#1832).
6953///
6954/// The evidence is the indexed declaration's own owner name, taken from its
6955/// `FqName`, so this stays a structured answer rather than a text fallback.
6956/// Only an owner the file cannot already see is admitted: that is what keeps a
6957/// header declaring its own class from additionally seeing every same-named
6958/// class in the workspace, and it makes the pass free for the ordinary file
6959/// whose owners are all visible.
6960fn extend_with_out_of_line_owner_bindings(
6961    cpp: &dyn CppSource,
6962    visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
6963) {
6964    for (file, visible) in visible_by_file.iter_mut() {
6965        // The include-closure walk seeds every root with its own declarations,
6966        // so the file's members are already here; re-reading them from the
6967        // analyzer would pay for the same declaration set twice.
6968        let mut unseen_owners: HashSet<String> = visible
6969            .iter()
6970            .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
6971            .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
6972            .collect();
6973        if unseen_owners.is_empty() {
6974            continue;
6975        }
6976        for unit in visible.iter().filter(|unit| unit.is_class()) {
6977            unseen_owners.remove(&unit.fq_name());
6978        }
6979        let admitted = unseen_owners
6980            .iter()
6981            .flat_map(|owner| cpp.definitions(owner))
6982            .filter(CodeUnit::is_class)
6983            .collect::<Vec<_>>();
6984        visible.extend(admitted);
6985    }
6986}
6987
6988pub enum VisibleMemberResolution {
6989    Callable(Vec<CodeUnit>),
6990    NonCallable,
6991    AmbiguousKind,
6992    Missing,
6993}
6994
6995#[derive(Clone)]
6996pub enum EnclosingMemberOwnerResolution {
6997    Owner(CodeUnit),
6998    Ambiguous,
6999    Missing,
7000}
7001
7002pub fn resolve_declaring_member_owner(
7003    analyzer: &CppGraphSource<'_>,
7004    visibility: &VisibilityIndex<'_>,
7005    file: &ProjectFile,
7006    receiver_owner: &CodeUnit,
7007    member_name: &str,
7008) -> EnclosingMemberOwnerResolution {
7009    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
7010        return EnclosingMemberOwnerResolution::Missing;
7011    };
7012    let Some(receiver_owner) =
7013        visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
7014    else {
7015        return EnclosingMemberOwnerResolution::Ambiguous;
7016    };
7017    let resolve_level = |frontier: &[CodeUnit]| {
7018        let mut member_owners = Vec::new();
7019        for raw_owner in frontier {
7020            let Some(owner) =
7021                visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
7022            else {
7023                return EnclosingMemberOwnerResolution::Ambiguous;
7024            };
7025            for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
7026                let Some(member_owner) = type_owner_of(analyzer, member) else {
7027                    return EnclosingMemberOwnerResolution::Ambiguous;
7028                };
7029                if !member_owners
7030                    .iter()
7031                    .any(|existing| same_visible_symbol(existing, &member_owner))
7032                {
7033                    member_owners.push(member_owner);
7034                }
7035            }
7036        }
7037        match member_owners.len() {
7038            0 => EnclosingMemberOwnerResolution::Missing,
7039            1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
7040            _ => EnclosingMemberOwnerResolution::Ambiguous,
7041        }
7042    };
7043    // The first declaration on each structured base path hides deeper names,
7044    // regardless of whether its callable overload is applicable at a particular
7045    // call site. Applicability is checked only after this owner is established.
7046    let direct = resolve_level(std::slice::from_ref(&receiver_owner));
7047    if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
7048        return direct;
7049    }
7050    let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
7051    let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
7052    let mut path_matches = Vec::new();
7053    while let Some(raw_owner) = stack.pop() {
7054        let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
7055        else {
7056            return EnclosingMemberOwnerResolution::Ambiguous;
7057        };
7058        // Persisted hierarchy edges do not encode virtual-base or base-subobject paths.
7059        // Propagate at most two occurrences of each owner: that preserves the distinction
7060        // between one and multiple resolving base paths without exponential diamond walks.
7061        let propagated = propagated_counts.entry(owner.clone()).or_default();
7062        if *propagated == 2 {
7063            continue;
7064        }
7065        *propagated += 1;
7066        match resolve_level(std::slice::from_ref(&owner)) {
7067            EnclosingMemberOwnerResolution::Owner(owner) => {
7068                path_matches.push(owner);
7069                if path_matches.len() == 2 {
7070                    return EnclosingMemberOwnerResolution::Ambiguous;
7071                }
7072            }
7073            EnclosingMemberOwnerResolution::Ambiguous => {
7074                return EnclosingMemberOwnerResolution::Ambiguous;
7075            }
7076            EnclosingMemberOwnerResolution::Missing => {
7077                stack.extend(hierarchy.get_direct_ancestors(&owner));
7078            }
7079        }
7080    }
7081    match path_matches.len() {
7082        0 => EnclosingMemberOwnerResolution::Missing,
7083        1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
7084        _ => unreachable!("base-path matches are capped at one before returning"),
7085    }
7086}
7087
7088/// Resolve the declaring owner of a callable after applying a member
7089/// `using <Base>::<member>;` declaration to one exact call arity.
7090///
7091/// Ordinary member lookup is intentionally name-based: the first class that
7092/// declares a name hides the same name on deeper bases. A member
7093/// using-declaration is the one exception. When none of the declarations on
7094/// that first owner accepts the call arity, it can reintroduce an applicable
7095/// overload from the named base. If a declaration on the first owner does
7096/// accept the arity, argument types would be needed to choose between it and
7097/// a same-arity introduced overload, so this resolver conservatively keeps the
7098/// ordinary owner (#1835/#1843).
7099///
7100/// The caller supplies ordinary name-based owner resolution so a file scan can
7101/// reuse its existing owner cache before applying this callable-only exception.
7102pub fn resolve_declaring_callable_owner(
7103    analyzer: &CppGraphSource<'_>,
7104    visibility: &VisibilityIndex<'_>,
7105    file: &ProjectFile,
7106    ordinary: EnclosingMemberOwnerResolution,
7107    member_name: &str,
7108    call_arity: usize,
7109) -> EnclosingMemberOwnerResolution {
7110    let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
7111        return ordinary;
7112    };
7113    if visibility
7114        .visible_members_for_owner_name(file, ordinary_owner, member_name)
7115        .into_iter()
7116        .any(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity))
7117    {
7118        return ordinary;
7119    }
7120
7121    let mut pending = match member_using_declaration_bases(
7122        analyzer,
7123        visibility,
7124        file,
7125        ordinary_owner,
7126        member_name,
7127    ) {
7128        Ok(bases) => bases,
7129        Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
7130    };
7131    let mut visited = HashSet::default();
7132    let mut introduced_owners = Vec::new();
7133    while let Some(owner) = pending.pop() {
7134        if !visited.insert(owner.clone()) {
7135            continue;
7136        }
7137        let accepts_arity = visibility
7138            .visible_members_for_owner_name(file, &owner, member_name)
7139            .into_iter()
7140            .any(|unit| {
7141                unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity)
7142            });
7143        if accepts_arity {
7144            if !introduced_owners
7145                .iter()
7146                .any(|existing| same_visible_symbol(existing, &owner))
7147            {
7148                introduced_owners.push(owner);
7149            }
7150            continue;
7151        }
7152        match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
7153            Ok(bases) => pending.extend(bases),
7154            Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
7155        }
7156    }
7157    match introduced_owners.as_slice() {
7158        [] => ordinary,
7159        [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
7160        _ => EnclosingMemberOwnerResolution::Ambiguous,
7161    }
7162}
7163
7164fn member_using_declaration_bases(
7165    analyzer: &CppGraphSource<'_>,
7166    visibility: &VisibilityIndex<'_>,
7167    file: &ProjectFile,
7168    owner: &CodeUnit,
7169    member_name: &str,
7170) -> Result<Vec<CodeUnit>, ()> {
7171    let Some(source) = analyzer.get_source(owner, false) else {
7172        return Ok(Vec::new());
7173    };
7174    let scopes = cpp_member_using_declaration_scopes(&source, member_name);
7175    if scopes.is_empty() {
7176        return Ok(Vec::new());
7177    }
7178    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
7179        return Ok(Vec::new());
7180    };
7181    let mut bases = Vec::new();
7182    for raw_ancestor in hierarchy.get_ancestors(owner) {
7183        let Some(ancestor) =
7184            visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
7185        else {
7186            return Err(());
7187        };
7188        let qualified = cpp_name_for(&ancestor);
7189        if scopes
7190            .iter()
7191            .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
7192            && !bases
7193                .iter()
7194                .any(|existing| same_visible_symbol(existing, &ancestor))
7195        {
7196            bases.push(ancestor);
7197        }
7198    }
7199    Ok(bases)
7200}
7201
7202pub fn lexical_component_tiers<'a>(
7203    components: &'a [String],
7204    global: bool,
7205    lexical_scope: &'a [String],
7206) -> impl Iterator<Item = Vec<String>> + 'a {
7207    let first_prefix_len = if global { 0 } else { lexical_scope.len() };
7208    (0..=first_prefix_len).rev().map(move |prefix_len| {
7209        let mut qualified = Vec::with_capacity(prefix_len + components.len());
7210        qualified.extend_from_slice(&lexical_scope[..prefix_len]);
7211        qualified.extend_from_slice(components);
7212        qualified
7213    })
7214}
7215
7216pub fn build_visible_identifier_index(
7217    analyzer: &CppGraphSource<'_>,
7218    visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
7219    visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
7220    global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
7221) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
7222    let mut out = HashMap::default();
7223    for (file, visible) in visible_by_file {
7224        let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
7225        for unit in visible {
7226            if unit.is_field()
7227                && !visible_source_files_by_root
7228                    .get(file)
7229                    .is_some_and(|sources| sources.contains(unit.source()))
7230                && cpp_global_field_has_internal_linkage_cached(
7231                    analyzer,
7232                    global_field_internal_linkage,
7233                    unit,
7234                )
7235            {
7236                continue;
7237            }
7238            by_identifier
7239                .entry(unit.identifier().to_string())
7240                .or_default()
7241                .push(unit.clone());
7242        }
7243        for units in by_identifier.values_mut() {
7244            sort_lookup_units(units);
7245            units.dedup();
7246        }
7247        out.insert(file.clone(), by_identifier);
7248    }
7249    out
7250}
7251
7252fn sort_lookup_units(units: &mut [CodeUnit]) {
7253    units.sort_by(|left, right| {
7254        left.fq_name()
7255            .cmp(&right.fq_name())
7256            .then_with(|| left.signature().cmp(&right.signature()))
7257            .then_with(|| left.source().cmp(right.source()))
7258            .then_with(|| left.kind().cmp(&right.kind()))
7259            .then_with(|| {
7260                left.package_segment_count()
7261                    .cmp(&right.package_segment_count())
7262            })
7263            .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
7264            .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
7265    });
7266}
7267
7268fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
7269    let interner = segment_interner();
7270    for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
7271        let (left_text, left_kind) = interner.resolve(left_id);
7272        let (right_text, right_kind) = interner.resolve(right_id);
7273        let order = left_text
7274            .cmp(right_text)
7275            .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
7276        if order != CmpOrdering::Equal {
7277            return order;
7278        }
7279    }
7280    left.len().cmp(&right.len())
7281}
7282
7283const fn segment_kind_order(kind: SegmentKind) -> u8 {
7284    match kind {
7285        SegmentKind::Path => 0,
7286        SegmentKind::Package => 1,
7287        SegmentKind::Type => 2,
7288        SegmentKind::Companion => 3,
7289        SegmentKind::Nested => 4,
7290        SegmentKind::Member => 5,
7291        SegmentKind::Unknown => 6,
7292    }
7293}
7294
7295fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
7296    let mut deduped = Vec::with_capacity(units.len());
7297    for unit in units.drain(..) {
7298        if !deduped.contains(&unit) {
7299            deduped.push(unit);
7300        }
7301    }
7302    *units = deduped;
7303}
7304
7305pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
7306    // Same domain as `candidate_units` above: `reference` is a plain
7307    // `::`-joined qualified-id with operator tokens kept intact by the shared
7308    // splitter's operator merge.
7309    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
7310        brokk_bifrost_core::analyzer::Language::Cpp,
7311        reference,
7312    );
7313    if parts.is_empty() {
7314        return Vec::new();
7315    }
7316
7317    let mut candidates = Vec::new();
7318    for package_len in 0..parts.len() {
7319        let package = parts[..package_len].join("::");
7320        let rest = &parts[package_len..];
7321        if rest.is_empty() {
7322            continue;
7323        }
7324        match kind {
7325            TargetKind::Type | TargetKind::Constructor => {
7326                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
7327                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
7328            }
7329            TargetKind::FreeFunction
7330            | TargetKind::Method
7331            | TargetKind::GlobalField
7332            | TargetKind::MemberField
7333            | TargetKind::Macro => {
7334                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
7335                if rest.len() > 1 {
7336                    let owner = rest[..rest.len() - 1].join("$");
7337                    let short = format!("{}.{}", owner, rest[rest.len() - 1]);
7338                    push_cpp_fqn_candidate(&mut candidates, &package, &short);
7339                }
7340            }
7341        }
7342    }
7343    candidates
7344}
7345
7346fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
7347    let fqn = if package.is_empty() {
7348        short.to_string()
7349    } else {
7350        format!("{package}.{short}")
7351    };
7352    if !out.contains(&fqn) {
7353        out.push(fqn);
7354    }
7355}
7356
7357pub fn infer_cpp_initializer_type(
7358    analyzer: &CppGraphSource<'_>,
7359    visibility: &VisibilityIndex<'_>,
7360    file: &ProjectFile,
7361    source: &str,
7362    node: Node<'_>,
7363) -> Option<CodeUnit> {
7364    infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
7365        .and_then(|binding| binding.unit)
7366}
7367
7368pub fn infer_cpp_initializer_binding(
7369    analyzer: &CppGraphSource<'_>,
7370    visibility: &VisibilityIndex<'_>,
7371    file: &ProjectFile,
7372    source: &str,
7373    node: Node<'_>,
7374    receiver_resolver: Option<&ReceiverResolver<'_>>,
7375) -> Option<CppScanBinding> {
7376    match node.kind() {
7377        "new_expression" => {
7378            let text = normalize_cpp_whitespace(node_text(node, source));
7379            let rest = text.strip_prefix("new ").unwrap_or(text.as_str());
7380            let type_text = rest.split(['(', '{']).next().unwrap_or(rest);
7381            let name = normalize_cpp_type_name(type_text);
7382            Some(CppScanBinding::from_type_name(
7383                name.clone(),
7384                visibility.resolve_type(file, &name),
7385                1,
7386            ))
7387        }
7388        "call_expression" => node.child_by_field_name("function").and_then(|function| {
7389            let function_text = node_text(function, source);
7390            let direct_type_binding = visibility
7391                .resolve_type(file, function_text)
7392                .map(|unit| CppScanBinding::from_unit(unit, 0));
7393            if function.kind() == "template_function" && direct_type_binding.is_some() {
7394                let lexical_namespace = enclosing_namespace_context(node, source);
7395                let arity = visibility.call_arity_evidence(file, node, source).exact();
7396                if let Some(arity) = arity
7397                    && let Some(binding) = visibility.resolve_call_return_binding(
7398                        analyzer,
7399                        file,
7400                        function_text,
7401                        arity,
7402                        lexical_namespace.as_deref(),
7403                        direct_type_binding
7404                            .as_ref()
7405                            .and_then(|binding| binding.unit.as_ref()),
7406                    )
7407                {
7408                    return Some(binding);
7409                }
7410                let (has_callable, callable_binding) = visibility
7411                    .resolve_call_return_binding_without_arity(
7412                        analyzer,
7413                        file,
7414                        function_text,
7415                        lexical_namespace.as_deref(),
7416                        direct_type_binding
7417                            .as_ref()
7418                            .and_then(|binding| binding.unit.as_ref()),
7419                    );
7420                if let Some(binding) = callable_binding {
7421                    return Some(binding);
7422                }
7423                if has_callable {
7424                    return None;
7425                }
7426                return direct_type_binding;
7427            }
7428            let arity = visibility.call_arity_evidence(file, node, source).exact()?;
7429            let direct_type_binding_for_call = direct_type_binding.clone();
7430            resolve_static_method_call_return_binding(
7431                analyzer, visibility, file, source, function, arity,
7432            )
7433            .or_else(|| {
7434                // An applicable free function supplies the receiver value
7435                // before an unrelated visible type with the same terminal
7436                // name. The direct type still excludes its own constructor
7437                // declaration below and remains the construction fallback.
7438                visibility.resolve_call_return_binding(
7439                    analyzer,
7440                    file,
7441                    function_text,
7442                    arity,
7443                    enclosing_namespace_context(node, source).as_deref(),
7444                    direct_type_binding_for_call
7445                        .as_ref()
7446                        .and_then(|binding| binding.unit.as_ref()),
7447                )
7448            })
7449            .or(direct_type_binding)
7450            .or_else(|| {
7451                resolve_field_method_call_return_binding(
7452                    analyzer,
7453                    visibility,
7454                    file,
7455                    source,
7456                    function,
7457                    arity,
7458                    receiver_resolver,
7459                )
7460            })
7461        }),
7462        _ => None,
7463    }
7464}
7465
7466fn resolve_static_method_call_return_binding(
7467    analyzer: &CppGraphSource<'_>,
7468    visibility: &VisibilityIndex<'_>,
7469    file: &ProjectFile,
7470    source: &str,
7471    function: Node<'_>,
7472    arity: usize,
7473) -> Option<CppScanBinding> {
7474    if function.kind() != "qualified_identifier" {
7475        return None;
7476    }
7477    let qualified = normalize_cpp_reference_text(node_text(function, source));
7478    // A C++ qualified-id is `::`-joined with no embedded delimiters in any
7479    // single component (the shared splitter's operator-token merge keeps
7480    // `operator+`-style names intact), so re-tokenizing with the shared
7481    // structured splitter and peeling the terminal segment reproduces
7482    // `rsplit_once("::")`'s (owner, member) split exactly — same shape as
7483    // `cpp_out_of_line_function_owner`'s `qualified` split above.
7484    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
7485        brokk_bifrost_core::analyzer::Language::Cpp,
7486        &qualified,
7487    );
7488    let (owner_text, member_name) = match parts.split_last() {
7489        Some((member, owner_parts)) if !owner_parts.is_empty() => {
7490            (owner_parts.join("::"), member.clone())
7491        }
7492        _ => {
7493            let scope = function.child_by_field_name("scope")?;
7494            let name = function.child_by_field_name("name")?;
7495            (
7496                node_text(scope, source).to_string(),
7497                node_text(name, source).to_string(),
7498            )
7499        }
7500    };
7501    let owner = visibility.resolve_type(file, &owner_text)?;
7502    let candidates = visibility
7503        .visible_members_for_owner_name(file, &owner, &member_name)
7504        .into_iter()
7505        .filter(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity))
7506        .cloned()
7507        .collect::<Vec<_>>();
7508    unanimous_return_binding(analyzer, visibility, file, &candidates)
7509}
7510
7511fn resolve_field_method_call_return_binding(
7512    analyzer: &CppGraphSource<'_>,
7513    visibility: &VisibilityIndex<'_>,
7514    file: &ProjectFile,
7515    source: &str,
7516    function: Node<'_>,
7517    arity: usize,
7518    receiver_resolver: Option<&ReceiverResolver<'_>>,
7519) -> Option<CppScanBinding> {
7520    if function.kind() != "field_expression" {
7521        return None;
7522    }
7523    let receiver_resolver = receiver_resolver?;
7524    let field = function.child_by_field_name("field")?;
7525    let member_name = node_text(function_terminal_node(field), source);
7526    let receiver = function
7527        .child_by_field_name("argument")
7528        .or_else(|| function.named_child(0))?;
7529    let owners = receiver_resolver(receiver, source);
7530    let mut candidates = Vec::new();
7531    for owner in owners {
7532        let declaring_owner =
7533            match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
7534                EnclosingMemberOwnerResolution::Owner(owner) => owner,
7535                EnclosingMemberOwnerResolution::Missing => continue,
7536                EnclosingMemberOwnerResolution::Ambiguous => return None,
7537            };
7538        candidates.extend(
7539            visibility
7540                .visible_members_for_owner_name(file, &declaring_owner, member_name)
7541                .into_iter()
7542                .filter(|unit| {
7543                    unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity)
7544                })
7545                .cloned(),
7546        );
7547    }
7548    unanimous_return_binding(analyzer, visibility, file, &candidates)
7549}
7550
7551fn unanimous_return_binding(
7552    analyzer: &CppGraphSource<'_>,
7553    visibility: &VisibilityIndex<'_>,
7554    file: &ProjectFile,
7555    candidates: &[CodeUnit],
7556) -> Option<CppScanBinding> {
7557    let mut resolved_return: Option<CppScanBinding> = None;
7558    for function in candidates {
7559        let metadata = analyzer.signature_metadata(function);
7560        let return_types = if metadata.is_empty() {
7561            vec![cpp_function_return_type_text(analyzer, function)?]
7562        } else {
7563            metadata
7564                .iter()
7565                .map(|metadata| metadata.return_type_text().map(str::to_string))
7566                .collect::<Option<Vec<_>>>()?
7567        };
7568        for return_text in return_types {
7569            let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
7570            let name = normalize_cpp_type_name(&return_text);
7571            let binding = CppScanBinding::from_type_name(
7572                name.clone(),
7573                visibility
7574                    .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
7575                indirection,
7576            );
7577            if let Some(existing) = resolved_return.as_ref()
7578                && (existing.indirection != binding.indirection
7579                    || match (&existing.unit, &binding.unit) {
7580                        (Some(left), Some(right)) => !same_visible_symbol(left, right),
7581                        (None, None) => existing.type_name != binding.type_name,
7582                        (Some(_), None) | (None, Some(_)) => true,
7583                    })
7584            {
7585                return None;
7586            }
7587            resolved_return = Some(binding);
7588        }
7589    }
7590    resolved_return
7591}
7592
7593fn aliases_from_prepared_source(
7594    cpp: &dyn CppSource,
7595    token: QueryToken<'_>,
7596    file: &ProjectFile,
7597) -> Vec<CppAlias> {
7598    let Some(prepared) = cpp.prepared_syntax(token, file) else {
7599        return Vec::new();
7600    };
7601    let mut aliases = Vec::new();
7602    collect_cpp_aliases(prepared.tree().root_node(), prepared.source(), &mut aliases);
7603    aliases
7604}
7605
7606fn collect_cpp_aliases(root: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
7607    let mut stack = vec![root];
7608    while let Some(node) = stack.pop() {
7609        match node.kind() {
7610            "alias_declaration" if alias_has_visible_file_scope(node) => {
7611                if let Some(alias) = cpp_alias_from_alias_declaration(node, source) {
7612                    out.push(alias);
7613                }
7614            }
7615            "type_definition" if alias_has_visible_file_scope(node) => {
7616                collect_typedef_aliases(node, source, out)
7617            }
7618            _ => {}
7619        }
7620
7621        for index in (0..node.named_child_count()).rev() {
7622            if let Some(child) = node.named_child(index) {
7623                stack.push(child);
7624            }
7625        }
7626    }
7627}
7628
7629fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
7630    let mut current = node.parent();
7631    while let Some(parent) = current {
7632        match parent.kind() {
7633            "translation_unit"
7634            | "namespace_definition"
7635            | "declaration_list"
7636            | "linkage_specification" => current = parent.parent(),
7637            "template_declaration" => current = parent.parent(),
7638            _ => return false,
7639        }
7640    }
7641    true
7642}
7643
7644fn cpp_alias_from_alias_declaration(node: Node<'_>, source: &str) -> Option<CppAlias> {
7645    let name = node
7646        .child_by_field_name("name")
7647        .and_then(|node| normalize_reference_name(node_text(node, source)))?;
7648    let target = node
7649        .child_by_field_name("type")
7650        .and_then(|node| normalize_reference_name(node_text(node, source)))?;
7651    Some(CppAlias {
7652        name,
7653        target,
7654        namespace: enclosing_namespace_context(node, source),
7655    })
7656}
7657
7658fn collect_typedef_aliases(node: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
7659    let Some(type_node) = node.child_by_field_name("type") else {
7660        return;
7661    };
7662    let Some(target) = normalize_reference_name(node_text(type_node, source)) else {
7663        return;
7664    };
7665
7666    let mut cursor = node.walk();
7667    for child in node.named_children(&mut cursor) {
7668        if same_node(child, type_node) {
7669            continue;
7670        }
7671        if let Some(name) = extract_typedef_declarator_name(child, source) {
7672            out.push(CppAlias {
7673                name,
7674                target: target.clone(),
7675                namespace: enclosing_namespace_context(node, source),
7676            });
7677        }
7678    }
7679}
7680
7681fn extract_typedef_declarator_name(node: Node<'_>, source: &str) -> Option<String> {
7682    match node.kind() {
7683        "identifier" | "field_identifier" | "type_identifier" | "qualified_identifier" => {
7684            normalize_reference_name(node_text(node, source))
7685        }
7686        _ => node
7687            .child_by_field_name("declarator")
7688            .or_else(|| node.child_by_field_name("name"))
7689            .or_else(|| last_named_child(node))
7690            .and_then(|child| extract_typedef_declarator_name(child, source)),
7691    }
7692}
7693
7694fn last_named_child(node: Node<'_>) -> Option<Node<'_>> {
7695    let count = node.named_child_count();
7696    if count == 0 {
7697        None
7698    } else {
7699        node.named_child(count - 1)
7700    }
7701}
7702
7703pub fn collect_include_closure(
7704    analyzer: &CppGraphSource<'_>,
7705    include_targets: &IncludeTargetIndex,
7706    file: &ProjectFile,
7707    out: &mut HashSet<ProjectFile>,
7708    cancellation: Option<&CancellationToken>,
7709) {
7710    let mut stack = vec![file.clone()];
7711    while let Some(file) = stack.pop() {
7712        if cancellation.is_some_and(CancellationToken::is_cancelled) {
7713            break;
7714        }
7715        if !out.insert(file.clone()) {
7716            continue;
7717        }
7718        let imports = analyzer.import_statements(&file);
7719        for include in cpp_include_paths(&imports) {
7720            for target in resolve_include_targets_with_index(&file, &include, include_targets) {
7721                stack.push(target);
7722            }
7723        }
7724    }
7725}
7726
7727fn collect_visible_declarations(
7728    include_graph: &IncludeGraph,
7729    declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
7730    file: &ProjectFile,
7731    visited: &mut HashSet<ProjectFile>,
7732    out: &mut HashSet<CodeUnit>,
7733    cancellation: Option<&CancellationToken>,
7734) {
7735    let mut stack = vec![file.clone()];
7736    while let Some(file) = stack.pop() {
7737        if cancellation.is_some_and(CancellationToken::is_cancelled) {
7738            break;
7739        }
7740        if !visited.insert(file.clone()) {
7741            continue;
7742        }
7743        if let Some(declarations) = declarations_by_file.get(&file) {
7744            out.extend(declarations.iter().cloned());
7745        }
7746        stack.extend(include_graph.targets(&file).iter().cloned());
7747    }
7748}
7749
7750pub fn signature_arity(signature: Option<&str>) -> usize {
7751    let Some(signature) = signature else {
7752        return 0;
7753    };
7754    let inner = signature
7755        .find('(')
7756        .and_then(|open| {
7757            signature[open + 1..]
7758                .find(')')
7759                .map(|close| &signature[open + 1..open + 1 + close])
7760        })
7761        .unwrap_or(signature)
7762        .trim();
7763    if inner.is_empty() || inner == "void" {
7764        return 0;
7765    }
7766    cpp_split_top_level_commas(inner).count()
7767}
7768
7769fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
7770    let source = format!("void __bifrost_macro_parameters({replacement});");
7771    let mut parser = Parser::new();
7772    parser
7773        .set_language(&tree_sitter_cpp::LANGUAGE.into())
7774        .ok()?;
7775    let tree = parser.parse(&source, None)?;
7776    let root = tree.root_node();
7777    if root.has_error() {
7778        return None;
7779    }
7780    let declaration = root.named_child(0)?;
7781    let declarator = declaration.child_by_field_name("declarator")?;
7782    let parameters = declarator.child_by_field_name("parameters")?;
7783    let mut required = 0;
7784    let mut total = 0;
7785    let mut repeated = false;
7786    let mut cursor = parameters.walk();
7787    for parameter in parameters.children(&mut cursor) {
7788        match parameter.kind() {
7789            "parameter_declaration" => {
7790                if parameter.child_by_field_name("declarator").is_none()
7791                    && parameter
7792                        .child_by_field_name("type")
7793                        .is_some_and(|type_node| node_text(type_node, &source).trim() == "void")
7794                {
7795                    continue;
7796                }
7797                required += 1;
7798                total += 1;
7799            }
7800            "optional_parameter_declaration" => total += 1,
7801            "variadic_parameter" | "variadic_parameter_declaration" | "..." => {
7802                repeated = true;
7803            }
7804            _ => {}
7805        }
7806    }
7807    Some(CallableArity::new(required, total, repeated))
7808}
7809
7810pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> CallableArity {
7811    analyzer
7812        .signature_metadata(unit)
7813        .into_iter()
7814        .find_map(|metadata| metadata.callable_arity())
7815        .unwrap_or_else(|| CallableArity::exact(signature_arity(unit.signature())))
7816}
7817
7818pub fn cpp_callable_parameter_types(
7819    analyzer: &CppGraphSource<'_>,
7820    unit: &CodeUnit,
7821) -> Option<Vec<String>> {
7822    analyzer
7823        .signature_metadata(unit)
7824        .into_iter()
7825        .find_map(|metadata| metadata.callable_parameter_types().map(<[String]>::to_vec))
7826        .or_else(|| unit.signature().and_then(cpp_signature_param_types))
7827}
7828
7829fn merge_compatible_callable_arities(
7830    left: CallableArity,
7831    right: CallableArity,
7832) -> Option<CallableArity> {
7833    let total = left.total();
7834    let left_repeated = left.accepts(total.saturating_add(1));
7835    let right_repeated = right.accepts(right.total().saturating_add(1));
7836    if total != right.total() || left_repeated != right_repeated {
7837        return None;
7838    }
7839    let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
7840    Some(CallableArity::new(required, total, left_repeated))
7841}
7842
7843fn find_include_activation(
7844    cpp: &dyn CppSource,
7845    token: QueryToken<'_>,
7846    file: &ProjectFile,
7847    prepared: &PreparedSyntaxTree,
7848    donor_source: &ProjectFile,
7849) -> Option<usize> {
7850    let include_targets = cpp.include_target_index();
7851    let mut direct_includes = Vec::new();
7852    let mut nodes = vec![prepared.tree().root_node()];
7853    // An include activates for the whole file, so only an unconditional
7854    // directive counts here.
7855    let reference = CallableReferenceContext {
7856        file,
7857        position: None,
7858    };
7859    while let Some(node) = nodes.pop() {
7860        if node.kind() == "preproc_include" {
7861            if callable_preprocessor_context_is_visible_for_reference(
7862                node,
7863                prepared.source(),
7864                &reference,
7865            ) {
7866                let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
7867                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
7868                    if let Some(target) = unique_include_target(resolve_include_targets_with_index(
7869                        file,
7870                        &include,
7871                        include_targets,
7872                    )) {
7873                        direct_includes.push((node.end_byte(), target));
7874                    }
7875                }
7876            }
7877            continue;
7878        }
7879        for index in (0..node.named_child_count()).rev() {
7880            if let Some(child) = node.named_child(index) {
7881                nodes.push(child);
7882            }
7883        }
7884    }
7885    direct_includes.sort_by_key(|(activation, _)| *activation);
7886    let mut known_missing = HashSet::default();
7887    direct_includes
7888        .into_iter()
7889        .find(|(_, direct)| {
7890            unconditional_include_reaches(
7891                cpp,
7892                token,
7893                include_targets,
7894                direct,
7895                donor_source,
7896                file,
7897                &mut known_missing,
7898            )
7899        })
7900        .map(|(activation, _)| activation)
7901}
7902
7903fn find_conditional_include_projection_index(
7904    cpp: &dyn CppSource,
7905    token: QueryToken<'_>,
7906    file: &ProjectFile,
7907    prepared: &PreparedSyntaxTree,
7908    on_state: &dyn Fn(),
7909) -> ConditionalIncludeProjectionIndex {
7910    let include_targets = cpp.include_target_index();
7911    let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
7912        HashMap::default();
7913    let mut pending = Vec::new();
7914    let mut nodes = vec![prepared.tree().root_node()];
7915    while let Some(node) = nodes.pop() {
7916        if node.kind() == "preproc_include" {
7917            let Some(required_guards) = preprocessor_guard_environment(node, prepared.source())
7918            else {
7919                continue;
7920            };
7921            let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
7922            for include in cpp_include_paths(std::slice::from_ref(&raw)) {
7923                let Some(target) = unique_include_target(resolve_include_targets_with_index(
7924                    file,
7925                    &include,
7926                    include_targets,
7927                )) else {
7928                    continue;
7929                };
7930                pending.push((target, node.end_byte(), required_guards.clone()));
7931            }
7932            continue;
7933        }
7934        for index in (0..node.named_child_count()).rev() {
7935            if let Some(child) = node.named_child(index) {
7936                nodes.push(child);
7937            }
7938        }
7939    }
7940
7941    // One reached file can have several distinct compatible guard paths. Each
7942    // (file, activation byte) key keeps only the inclusion-minimal guard sets:
7943    // the consumers ask existence questions whose answers are monotone in the
7944    // guard set -- a path whose requirements hold, stay stable, and stay
7945    // compatible under one environment does so under every subset as well --
7946    // so a state subsumed by an existing subset cannot witness anything its
7947    // subset does not, and inserting a smaller set evicts the supersets it
7948    // subsumes. Exact-set dedup still terminated cycles, but dense `#ifdef`
7949    // lattices (QMK's per-keyboard feature guards) enumerated the powerset of
7950    // path-union guard sets through it: the state space, the per-key linear
7951    // scans, and resident memory all grew without bound (#2365).
7952    let mut expanded: HashMap<(ProjectFile, usize), Vec<HashSet<PreprocessorGuard>>> =
7953        HashMap::default();
7954    while let Some((current_file, activation_byte, required_guards)) = pending.pop() {
7955        let guard_sets = expanded
7956            .entry((current_file.clone(), activation_byte))
7957            .or_default();
7958        if guard_sets
7959            .iter()
7960            .any(|existing| existing.is_subset(&required_guards))
7961        {
7962            continue;
7963        }
7964        let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
7965            .drain(..)
7966            .partition(|existing| required_guards.is_subset(existing));
7967        *guard_sets = kept;
7968        guard_sets.push(required_guards.clone());
7969        if !evicted.is_empty()
7970            && let Some(projections) = projections_by_source.get_mut(&current_file)
7971        {
7972            projections.retain(|projection| {
7973                projection.activation_byte != activation_byte
7974                    || !evicted.contains(&projection.required_guards)
7975            });
7976        }
7977        on_state();
7978
7979        // A fresh minimal set has no equal in the store: equality would have
7980        // been caught by the subset check above.
7981        projections_by_source
7982            .entry(current_file.clone())
7983            .or_default()
7984            .push(ConditionalIncludeProjection {
7985                activation_byte,
7986                required_guards: required_guards.clone(),
7987            });
7988
7989        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
7990            continue;
7991        };
7992        let mut nodes = vec![current_prepared.tree().root_node()];
7993        while let Some(node) = nodes.pop() {
7994            if node.kind() == "preproc_include" {
7995                let Some(include_guards) =
7996                    preprocessor_guard_environment(node, current_prepared.source())
7997                else {
7998                    continue;
7999                };
8000                let Some(path_guards) =
8001                    merge_preprocessor_guards(&required_guards, &include_guards)
8002                else {
8003                    continue;
8004                };
8005                let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
8006                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
8007                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
8008                        &current_file,
8009                        &include,
8010                        include_targets,
8011                    )) else {
8012                        continue;
8013                    };
8014                    pending.push((target, activation_byte, path_guards.clone()));
8015                }
8016                continue;
8017            }
8018            for index in (0..node.named_child_count()).rev() {
8019                if let Some(child) = node.named_child(index) {
8020                    nodes.push(child);
8021                }
8022            }
8023        }
8024    }
8025
8026    projections_by_source
8027        .into_iter()
8028        .map(|(source, mut projections)| {
8029            projections.sort_by_key(|projection| projection.activation_byte);
8030            (source, Arc::from(projections))
8031        })
8032        .collect()
8033}
8034
8035fn unconditional_include_reaches(
8036    cpp: &dyn CppSource,
8037    token: QueryToken<'_>,
8038    include_targets: &IncludeTargetIndex,
8039    first: &ProjectFile,
8040    donor_source: &ProjectFile,
8041    reference_file: &ProjectFile,
8042    known_missing: &mut HashSet<ProjectFile>,
8043) -> bool {
8044    if first == donor_source {
8045        return true;
8046    }
8047    if known_missing.contains(first) {
8048        return false;
8049    }
8050    let reference_is_c = reference_file
8051        .rel_path()
8052        .extension()
8053        .and_then(|extension| extension.to_str())
8054        == Some("c");
8055    if let Some(reaches) =
8056        cpp.cached_unconditional_include_reachability(first, donor_source, reference_is_c)
8057    {
8058        return reaches;
8059    }
8060    let mut visited = HashSet::default();
8061    let mut files = vec![first.clone()];
8062    // Only an unconditional directive extends the include reach, so the walk
8063    // asks the question without a reference position.
8064    let reference = CallableReferenceContext {
8065        file: reference_file,
8066        position: None,
8067    };
8068    while let Some(file) = files.pop() {
8069        if file == *donor_source {
8070            cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, true);
8071            return true;
8072        }
8073        if known_missing.contains(&file) || !visited.insert(file.clone()) {
8074            continue;
8075        }
8076        let Some(prepared) = cpp.prepared_syntax(token, &file) else {
8077            continue;
8078        };
8079        let mut nodes = vec![prepared.tree().root_node()];
8080        while let Some(node) = nodes.pop() {
8081            if node.kind() == "preproc_include" {
8082                if callable_preprocessor_context_is_visible_for_reference(
8083                    node,
8084                    prepared.source(),
8085                    &reference,
8086                ) {
8087                    let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
8088                    for include in cpp_include_paths(std::slice::from_ref(&raw)) {
8089                        if let Some(target) = unique_include_target(
8090                            resolve_include_targets_with_index(&file, &include, include_targets),
8091                        ) {
8092                            files.push(target);
8093                        }
8094                    }
8095                }
8096                continue;
8097            }
8098            for index in (0..node.named_child_count()).rev() {
8099                if let Some(child) = node.named_child(index) {
8100                    nodes.push(child);
8101                }
8102            }
8103        }
8104    }
8105    known_missing.extend(visited);
8106    cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, false);
8107    false
8108}
8109
8110fn declaration_guard_requirements(
8111    analyzer: &CppGraphSource<'_>,
8112    cpp: &dyn CppSource,
8113    candidate: &CodeUnit,
8114) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
8115    let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
8116        return Vec::new();
8117    };
8118    let root = prepared.tree().root_node();
8119    analyzer
8120        .ranges(candidate)
8121        .into_iter()
8122        .filter_map(|range| {
8123            root.descendant_for_byte_range(range.start_byte, range.end_byte)
8124                .and_then(|node| preprocessor_guard_environment(node, prepared.source()))
8125                // A class name is injected into its own body at the declaration's
8126                // introduction point, not after the complete class range. Using
8127                // the start also preserves normal before/after ordering for aliases.
8128                .map(|required| (range.start_byte, required))
8129        })
8130        .collect()
8131}
8132
8133fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
8134    analyzer
8135        .ranges(candidate)
8136        .into_iter()
8137        .map(|range| range.start_byte)
8138        .min()
8139}
8140
8141/// The macro names every configuration in `contexts` defines -- the fact set
8142/// one file's compile-database coverage proves (#2011). `None` when the
8143/// database has no entry for the file, which is different from an empty
8144/// intersection: no entry means no coverage, while an empty intersection is
8145/// covered-and-proves-nothing.
8146fn context_fact_names(contexts: &[CppCompileContext]) -> Option<HashSet<String>> {
8147    let (first, rest) = contexts.split_first()?;
8148    Some(
8149        first
8150            .defined_macros
8151            .iter()
8152            .filter(|name| {
8153                rest.iter()
8154                    .all(|context| context.defined_macros.contains(*name))
8155            })
8156            .cloned()
8157            .collect(),
8158    )
8159}
8160
8161fn guard_requirements_hold_at_reference(
8162    required: &HashSet<PreprocessorGuard>,
8163    reference: Option<&HashSet<PreprocessorGuard>>,
8164) -> bool {
8165    reference.is_some_and(|active| {
8166        required
8167            .iter()
8168            .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
8169    })
8170}
8171
8172fn preprocessor_guard_holds_at_reference(
8173    required: &PreprocessorGuard,
8174    active: &HashSet<PreprocessorGuard>,
8175) -> bool {
8176    if active.contains(required) {
8177        return true;
8178    }
8179    let active_expression = BooleanGuardExpression::all(
8180        active
8181            .iter()
8182            .filter_map(PreprocessorGuard::as_boolean_expression),
8183    );
8184    required
8185        .as_boolean_expression()
8186        .is_some_and(|required| active_expression.implies(&required))
8187}
8188
8189/// Cross-file guard rule: two guard sets are compatible when neither one
8190/// contradicts the other. Use this instead of the subset test whenever the
8191/// guards come from a foreign file, which resolves its own conditionals
8192/// independently of the reference.
8193fn guards_compatible_at_reference(
8194    declaration: &HashSet<PreprocessorGuard>,
8195    reference: Option<&HashSet<PreprocessorGuard>>,
8196) -> bool {
8197    reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
8198}
8199
8200/// The byte range of the `#if`/`#elif`/`#else` chain that encloses the smallest
8201/// node covering `[start_byte, end_byte)`, or `None` when nothing there is
8202/// conditional.
8203///
8204/// Two declarations of one name that report the same chain stand in different
8205/// branches of it, so at most one of them is compiled in any configuration.
8206/// They are alternate spellings of a single declaration, not competing
8207/// declarations, and navigation must not present them as an ambiguity.
8208pub fn preprocessor_conditional_family_range(
8209    root: Node<'_>,
8210    start_byte: usize,
8211    end_byte: usize,
8212) -> Option<(usize, usize)> {
8213    let node = root.descendant_for_byte_range(start_byte, end_byte)?;
8214    let mut ancestor = Some(node);
8215    while let Some(current) = ancestor {
8216        if is_preprocessor_conditional(current)
8217            && preprocessor_conditional_contains_descendant(current, node)
8218        {
8219            let family = preprocessor_conditional_family_root(current);
8220            return Some((family.start_byte(), family.end_byte()));
8221        }
8222        ancestor = current.parent();
8223    }
8224    None
8225}
8226
8227fn preprocessor_conditional_family_for_declaration(node: Node<'_>) -> Option<Node<'_>> {
8228    let mut ancestor = node.parent();
8229    while let Some(current) = ancestor {
8230        if is_preprocessor_conditional(current)
8231            && preprocessor_conditional_contains_descendant(current, node)
8232        {
8233            let family = preprocessor_conditional_family_root(current);
8234            if preprocessor_conditional_family_has_terminal_else(family) {
8235                return Some(family);
8236            }
8237        }
8238        ancestor = current.parent();
8239    }
8240    None
8241}
8242
8243fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
8244    while let Some(parent) = conditional.parent() {
8245        let is_alternative = parent
8246            .child_by_field_name("alternative")
8247            .is_some_and(|alternative| {
8248                alternative.start_byte() == conditional.start_byte()
8249                    && alternative.end_byte() == conditional.end_byte()
8250            });
8251        if !is_alternative {
8252            break;
8253        }
8254        conditional = parent;
8255    }
8256    conditional
8257}
8258
8259fn preprocessor_conditional_family_has_terminal_else(mut conditional: Node<'_>) -> bool {
8260    loop {
8261        let Some(alternative) = conditional.child_by_field_name("alternative") else {
8262            return false;
8263        };
8264        match alternative.kind() {
8265            "preproc_else" => return true,
8266            "preproc_elif" => conditional = alternative,
8267            _ => return false,
8268        }
8269    }
8270}
8271
8272pub fn preprocessor_guard_environment(
8273    node: Node<'_>,
8274    source: &str,
8275) -> Option<HashSet<PreprocessorGuard>> {
8276    let mut guards = HashSet::default();
8277    let mut ancestor = node.parent();
8278    while let Some(conditional) = ancestor {
8279        if matches!(
8280            conditional.kind(),
8281            "preproc_if" | "preproc_ifdef" | "preproc_elif"
8282        ) && !is_file_covering_include_guard(conditional, source)
8283            && preprocessor_conditional_contains_descendant(conditional, node)
8284        {
8285            let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
8286            match guard {
8287                PreprocessorGuard::Constant(true) => {
8288                    ancestor = conditional.parent();
8289                    continue;
8290                }
8291                PreprocessorGuard::Constant(false) => return None,
8292                _ => {}
8293            }
8294            if guards.contains(&guard.negated()) {
8295                return None;
8296            }
8297            guards.insert(guard);
8298        }
8299        ancestor = conditional.parent();
8300    }
8301    if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
8302        match guard {
8303            PreprocessorGuard::Constant(true) => {}
8304            PreprocessorGuard::Constant(false) => return None,
8305            _ => {
8306                if guards.contains(&guard.negated()) {
8307                    return None;
8308                }
8309                guards.insert(guard);
8310            }
8311        }
8312    }
8313    Some(guards)
8314}
8315
8316fn fragmented_statement_preprocessor_guard(
8317    descendant: Node<'_>,
8318    source: &str,
8319) -> Option<PreprocessorGuard> {
8320    // A conditional that starts before `} else if (...) {` crosses the
8321    // enclosing statement's grammar boundary. tree-sitter leaves its opener
8322    // as a `preproc_if` with a missing terminator in the consequence and
8323    // reparses the real `#endif` as a `preproc_call` in the alternative. Pair
8324    // those structured nodes before restoring the guard to intervening uses.
8325    let mut ancestor = descendant.parent();
8326    while let Some(statement) = ancestor {
8327        if statement.kind() == "if_statement"
8328            && let (Some(consequence), Some(alternative)) = (
8329                statement.child_by_field_name("consequence"),
8330                statement.child_by_field_name("alternative"),
8331            )
8332            && alternative.start_byte() <= descendant.start_byte()
8333            && descendant.end_byte() <= alternative.end_byte()
8334        {
8335            let mut cursor = consequence.walk();
8336            let openers = consequence
8337                .named_children(&mut cursor)
8338                .filter(|child| {
8339                    matches!(child.kind(), "preproc_if" | "preproc_ifdef")
8340                        && child
8341                            .child(child.child_count().saturating_sub(1))
8342                            .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
8343                })
8344                .collect::<Vec<_>>();
8345            if openers.len() != 1 {
8346                ancestor = statement.parent();
8347                continue;
8348            }
8349
8350            let mut terminators = Vec::new();
8351            let mut stack = vec![alternative];
8352            while let Some(node) = stack.pop() {
8353                if node.kind() == "preproc_call"
8354                    && node.start_byte() >= descendant.end_byte()
8355                    && node
8356                        .child_by_field_name("directive")
8357                        .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
8358                {
8359                    terminators.push(node);
8360                    continue;
8361                }
8362                for index in (0..node.named_child_count()).rev() {
8363                    if let Some(child) = node.named_child(index) {
8364                        stack.push(child);
8365                    }
8366                }
8367            }
8368            if terminators.len() == 1 {
8369                return simple_preprocessor_guard(openers[0], source);
8370            }
8371        }
8372        ancestor = statement.parent();
8373    }
8374    None
8375}
8376
8377fn preprocessor_guard_for_descendant(
8378    conditional: Node<'_>,
8379    descendant: Node<'_>,
8380    source: &str,
8381) -> Option<PreprocessorGuard> {
8382    let mut guard = simple_preprocessor_guard(conditional, source)?;
8383    if conditional
8384        .child_by_field_name("alternative")
8385        .is_some_and(|alternative| {
8386            alternative.start_byte() <= descendant.start_byte()
8387                && descendant.end_byte() <= alternative.end_byte()
8388        })
8389    {
8390        let alternative = conditional.child_by_field_name("alternative")?;
8391        // Tree-sitter nests an `#elif` chain in each `alternative` field. A
8392        // descendant in any later branch must first exclude the parent branch,
8393        // then collect the nested `preproc_elif` guard from its own ancestor.
8394        if !matches!(alternative.kind(), "preproc_else" | "preproc_elif") {
8395            return None;
8396        }
8397        guard = guard.negated();
8398    }
8399    Some(guard)
8400}
8401
8402fn preprocessor_conditional_contains_descendant(
8403    conditional: Node<'_>,
8404    descendant: Node<'_>,
8405) -> bool {
8406    cpp_displaced_preprocessor_boundary(conditional)
8407        .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
8408}
8409
8410pub fn merge_preprocessor_guards(
8411    left: &HashSet<PreprocessorGuard>,
8412    right: &HashSet<PreprocessorGuard>,
8413) -> Option<HashSet<PreprocessorGuard>> {
8414    let mut merged = left.clone();
8415    for guard in right {
8416        if merged.contains(&guard.negated()) {
8417            return None;
8418        }
8419        merged.insert(guard.clone());
8420    }
8421    Some(merged)
8422}
8423
8424fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
8425    if conditional.kind() == "preproc_ifdef" {
8426        let name = conditional.child_by_field_name("name")?;
8427        let name = node_text(name, source).to_string();
8428        return match conditional.child(0)?.kind() {
8429            "#ifdef" => Some(PreprocessorGuard::Defined(name)),
8430            "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
8431            _ => None,
8432        };
8433    }
8434    let condition = conditional.child_by_field_name("condition")?;
8435    simple_preprocessor_expression_guard(condition, source).or_else(|| {
8436        Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
8437            node_text(condition, source),
8438        )))
8439    })
8440}
8441
8442fn simple_preprocessor_expression_guard(
8443    expression: Node<'_>,
8444    source: &str,
8445) -> Option<PreprocessorGuard> {
8446    match expression.kind() {
8447        "number_literal" => match node_text(expression, source).trim() {
8448            "0" => Some(PreprocessorGuard::Constant(false)),
8449            "1" => Some(PreprocessorGuard::Constant(true)),
8450            _ => None,
8451        },
8452        "preproc_defined" => {
8453            let identifier = (0..expression.named_child_count())
8454                .filter_map(|index| expression.named_child(index))
8455                .find(|child| child.kind() == "identifier")?;
8456            Some(PreprocessorGuard::Defined(
8457                node_text(identifier, source).to_string(),
8458            ))
8459        }
8460        "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
8461            node_text(expression, source).to_string(),
8462        ))),
8463        "unary_expression"
8464            if expression
8465                .child_by_field_name("operator")
8466                .is_some_and(|operator| operator.kind() == "!") =>
8467        {
8468            simple_preprocessor_expression_guard(
8469                expression.child_by_field_name("argument")?,
8470                source,
8471            )
8472            .map(|guard| guard.negated())
8473        }
8474        "parenthesized_expression" => (0..expression.named_child_count())
8475            .filter_map(|index| expression.named_child(index))
8476            .next()
8477            .and_then(|child| simple_preprocessor_expression_guard(child, source)),
8478        "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
8479            expression, source,
8480        ))),
8481        _ => None,
8482    }
8483}
8484
8485fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
8486    match expression.kind() {
8487        "number_literal" => match node_text(expression, source).trim() {
8488            "0" => BooleanGuardExpression::Constant(false),
8489            "1" => BooleanGuardExpression::Constant(true),
8490            _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8491                expression, source,
8492            ))),
8493        },
8494        "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
8495        "preproc_defined" => {
8496            let identifier = (0..expression.named_child_count())
8497                .filter_map(|index| expression.named_child(index))
8498                .find(|child| child.kind() == "identifier");
8499            identifier.map_or_else(
8500                || {
8501                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8502                        expression, source,
8503                    )))
8504                },
8505                |identifier| {
8506                    BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
8507                },
8508            )
8509        }
8510        "unary_expression"
8511            if expression
8512                .child_by_field_name("operator")
8513                .is_some_and(|operator| operator.kind() == "!") =>
8514        {
8515            expression.child_by_field_name("argument").map_or_else(
8516                || {
8517                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8518                        expression, source,
8519                    )))
8520                },
8521                |argument| boolean_preprocessor_expression(argument, source).negated(),
8522            )
8523        }
8524        "parenthesized_expression" => (0..expression.named_child_count())
8525            .filter_map(|index| expression.named_child(index))
8526            .next()
8527            .map_or_else(
8528                || {
8529                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8530                        expression, source,
8531                    )))
8532                },
8533                |child| boolean_preprocessor_expression(child, source),
8534            ),
8535        "binary_expression" => {
8536            let operands = || {
8537                Some((
8538                    boolean_preprocessor_expression(
8539                        expression.child_by_field_name("left")?,
8540                        source,
8541                    ),
8542                    boolean_preprocessor_expression(
8543                        expression.child_by_field_name("right")?,
8544                        source,
8545                    ),
8546                ))
8547            };
8548            match expression
8549                .child_by_field_name("operator")
8550                .map(|operator| operator.kind())
8551            {
8552                Some("&&") => operands().map_or_else(
8553                    || {
8554                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8555                            expression, source,
8556                        )))
8557                    },
8558                    |(left, right)| BooleanGuardExpression::all([left, right]),
8559                ),
8560                Some("||") => operands().map_or_else(
8561                    || {
8562                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8563                            expression, source,
8564                        )))
8565                    },
8566                    |(left, right)| BooleanGuardExpression::any([left, right]),
8567                ),
8568                _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8569                    expression, source,
8570                ))),
8571            }
8572        }
8573        _ => {
8574            BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
8575        }
8576    }
8577}
8578
8579fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
8580    if targets.len() == 1 {
8581        targets.pop()
8582    } else {
8583        None
8584    }
8585}
8586
8587/// The declaration nodes of `candidate` in `prepared` that stand at a scope a
8588/// later reference can name.
8589///
8590/// A declaration inside a real function body, lambda, or nested block is block
8591/// local and is dropped. A declaration inside a parser-recovery wrapper that
8592/// merely looks callable -- an export macro between `class` and its name, or a
8593/// namespace-opening macro token before `namespace x {` -- keeps class or
8594/// namespace scope and is kept.
8595fn nameable_callable_declaration_nodes<'tree>(
8596    analyzer: &CppGraphSource<'_>,
8597    prepared: &'tree PreparedSyntaxTree,
8598    candidate: &CodeUnit,
8599) -> Vec<Node<'tree>> {
8600    let root = prepared.tree().root_node();
8601    analyzer
8602        .ranges(candidate)
8603        .into_iter()
8604        .filter_map(|range| {
8605            let mut declaration =
8606                root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
8607            while !matches!(
8608                declaration.kind(),
8609                "declaration" | "field_declaration" | "function_definition"
8610            ) {
8611                declaration = declaration.parent()?;
8612            }
8613            let mut ancestor = declaration.parent();
8614            while let Some(node) = ancestor {
8615                if node.kind() == "function_definition"
8616                    && is_recovered_declaration_scope_container(node, prepared.source())
8617                {
8618                    ancestor = node.parent();
8619                    continue;
8620                }
8621                if node.kind() == "compound_statement"
8622                    && node.parent().is_some_and(|parent| {
8623                        is_recovered_declaration_scope_container(parent, prepared.source())
8624                    })
8625                {
8626                    ancestor = node.parent().and_then(|parent| parent.parent());
8627                    continue;
8628                }
8629                if matches!(
8630                    node.kind(),
8631                    "compound_statement" | "function_definition" | "lambda_expression"
8632                ) {
8633                    return None;
8634                }
8635                ancestor = node.parent();
8636            }
8637            Some(declaration)
8638        })
8639        .collect()
8640}
8641
8642fn callable_declaration_activation_in_file(
8643    analyzer: &CppGraphSource<'_>,
8644    prepared: &PreparedSyntaxTree,
8645    candidate: &CodeUnit,
8646    reference: &CallableReferenceContext<'_>,
8647) -> Option<usize> {
8648    nameable_callable_declaration_nodes(analyzer, prepared, candidate)
8649        .into_iter()
8650        .filter(|declaration| {
8651            callable_preprocessor_context_is_visible_for_reference(
8652                *declaration,
8653                prepared.source(),
8654                reference,
8655            )
8656        })
8657        .map(callable_declaration_activation_byte)
8658        .min()
8659}
8660
8661/// C and C++ activate a declared name at the end of its declarator, not at the
8662/// end of the whole declaration. A function definition ends at the closing
8663/// brace of its body, so the declaration end byte would hide the function from
8664/// its own body and make self recursion unresolvable without a prototype.
8665fn callable_declaration_activation_byte(declaration: Node<'_>) -> usize {
8666    if declaration.kind() != "function_definition" {
8667        return declaration.end_byte();
8668    }
8669    declaration
8670        .child_by_field_name("declarator")
8671        .map_or(declaration.end_byte(), |declarator| declarator.end_byte())
8672}
8673
8674/// The reference side of a callable visibility question.
8675///
8676/// An include-graph walk and a whole-file arity activation ask the question
8677/// without one reference position, so they carry no `position` and therefore no
8678/// guard environment.
8679struct CallableReferenceContext<'a> {
8680    file: &'a ProjectFile,
8681    position: Option<CallableReferencePosition<'a>>,
8682}
8683
8684/// One reference position plus its preprocessor guard environment. The
8685/// environment is computed on demand because most declarations carry no
8686/// non-trivial guard.
8687struct CallableReferencePosition<'a> {
8688    prepared: &'a PreparedSyntaxTree,
8689    byte: usize,
8690    guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
8691}
8692
8693impl CallableReferenceContext<'_> {
8694    fn is_c(&self) -> bool {
8695        self.file
8696            .rel_path()
8697            .extension()
8698            .and_then(|extension| extension.to_str())
8699            == Some("c")
8700    }
8701
8702    fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
8703        let position = self.position.as_ref()?;
8704        position
8705            .guards
8706            .get_or_init(|| {
8707                position
8708                    .prepared
8709                    .tree()
8710                    .root_node()
8711                    .descendant_for_byte_range(position.byte, position.byte)
8712                    .and_then(|node| {
8713                        preprocessor_guard_environment(node, position.prepared.source())
8714                    })
8715            })
8716            .as_ref()
8717    }
8718}
8719
8720fn callable_preprocessor_context_is_visible_for_reference(
8721    node: Node<'_>,
8722    source: &str,
8723    reference: &CallableReferenceContext<'_>,
8724) -> bool {
8725    let reference_is_c = reference.is_c();
8726    let mut ancestor = node.parent();
8727    while let Some(conditional) = ancestor {
8728        if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
8729            && !is_file_covering_include_guard(conditional, source)
8730            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
8731            && preprocessor_conditional_contains_descendant(conditional, node)
8732        {
8733            let Some(guard) = preprocessor_guard_for_descendant(conditional, node, source) else {
8734                return false;
8735            };
8736            match guard {
8737                PreprocessorGuard::Constant(true) => {}
8738                PreprocessorGuard::Constant(false) => return false,
8739                PreprocessorGuard::Defined(name) if name == "__cplusplus" => {
8740                    if reference_is_c {
8741                        return false;
8742                    }
8743                }
8744                PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
8745                    if !reference_is_c {
8746                        return false;
8747                    }
8748                }
8749                // The declaration stands under a guard whose value this
8750                // analyzer cannot decide. It is still co-active with a
8751                // reference whose active guards imply it. Collecting one guard
8752                // per ancestor makes the whole walk a conjunction of the
8753                // declaration requirements.
8754                guard => {
8755                    if !reference
8756                        .guards()
8757                        .is_some_and(|active| preprocessor_guard_holds_at_reference(&guard, active))
8758                    {
8759                        return false;
8760                    }
8761                }
8762            }
8763        }
8764        ancestor = conditional.parent();
8765    }
8766    true
8767}
8768
8769fn flattened_macro_namespace_declaration_matches(
8770    analyzer: &CppGraphSource<'_>,
8771    cpp: &dyn CppSource,
8772    reference_file: &ProjectFile,
8773    visible_declaration: &CodeUnit,
8774    qualified_candidate: &CodeUnit,
8775    reference_byte: usize,
8776) -> bool {
8777    // Namespace-opening macros can leave tree-sitter unable to retain the
8778    // namespace owner after a later recovery point. In that shape the forward
8779    // declaration is indexed at translation-unit scope, while the definition
8780    // still has its qualified owner. Require all surviving structural evidence
8781    // before treating the declaration as activation for that definition.
8782    if visible_declaration.kind() != qualified_candidate.kind()
8783        || visible_declaration.identifier() != qualified_candidate.identifier()
8784        || visible_declaration.signature() != qualified_candidate.signature()
8785        || !visible_declaration.package_name().is_empty()
8786        || qualified_candidate.package_name().is_empty()
8787    {
8788        return false;
8789    }
8790
8791    let Some(prepared) = cpp.prepared_syntax(analyzer.token, visible_declaration.source()) else {
8792        return false;
8793    };
8794    let root = prepared.tree().root_node();
8795    let closing_brace_limit = if visible_declaration.source() == reference_file {
8796        reference_byte
8797    } else {
8798        usize::MAX
8799    };
8800
8801    analyzer
8802        .ranges(visible_declaration)
8803        .into_iter()
8804        .any(|range| {
8805            let Some(mut declaration) =
8806                root.descendant_for_byte_range(range.start_byte, range.end_byte)
8807            else {
8808                return false;
8809            };
8810            while !matches!(
8811                declaration.kind(),
8812                "declaration" | "field_declaration" | "function_definition"
8813            ) {
8814                let Some(parent) = declaration.parent() else {
8815                    return false;
8816                };
8817                declaration = parent;
8818            }
8819            if declaration
8820                .parent()
8821                .is_none_or(|parent| parent.kind() != "translation_unit")
8822                || !macro_displaced_cpp_return_type(declaration, prepared.source())
8823            {
8824                return false;
8825            }
8826
8827            let mut cursor = root.walk();
8828            root.named_children(&mut cursor).any(|sibling| {
8829                sibling.start_byte() >= declaration.end_byte()
8830                    && sibling.start_byte() < closing_brace_limit
8831                    && direct_unmatched_closing_brace(sibling)
8832            })
8833        })
8834}
8835
8836fn flattened_macro_namespace_components(
8837    declaration: Node<'_>,
8838    source: &str,
8839) -> Option<Vec<String>> {
8840    flattened_macro_function_namespace_components(declaration, source)
8841        .or_else(|| flattened_macro_error_namespace_components(declaration, source))
8842}
8843
8844fn flattened_macro_function_namespace_components(
8845    declaration: Node<'_>,
8846    source: &str,
8847) -> Option<Vec<String>> {
8848    let body = declaration
8849        .parent()
8850        .filter(|parent| parent.kind() == "compound_statement")?;
8851    let function = body.parent()?;
8852    if function.child_by_field_name("body") != Some(body) {
8853        return None;
8854    }
8855    let namespace_name = recovered_macro_namespace_name(function, source)?;
8856    let mut components = enclosing_namespace_components(declaration, source)?;
8857    components.push(namespace_name);
8858    Some(components)
8859}
8860
8861/// The namespace name a namespace-opening macro token displaced into a
8862/// synthetic `function_definition`, or `None` when `function` is not that
8863/// recovery shape.
8864///
8865/// `ABSL_NAMESPACE_BEGIN` (or `FMT_BEGIN_NAMESPACE`, ...) immediately before
8866/// `namespace x {` leaves tree-sitter with a `function_definition` whose type is
8867/// the macro token, whose declarator is the namespace name behind an `ERROR`
8868/// holding the `namespace` keyword, and whose body spans the whole namespace
8869/// region. The matching `*_NAMESPACE_END` sibling is what separates the recovery
8870/// artifact from a real function definition.
8871fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
8872    if function.kind() != "function_definition" || !function.has_error() {
8873        return None;
8874    }
8875    let body = function
8876        .child_by_field_name("body")
8877        .filter(|body| body.kind() == "compound_statement")?;
8878    let mut cursor = function.walk();
8879    let prefix = function
8880        .named_children(&mut cursor)
8881        .take_while(|child| child.start_byte() < body.start_byte())
8882        .filter(|child| child.kind() != "comment")
8883        .collect::<Vec<_>>();
8884    let begin_index = prefix.iter().rposition(|child| {
8885        flattened_macro_sentinel_name(*child, source)
8886            .is_some_and(|name| is_namespace_begin_sentinel(&name))
8887    })?;
8888    let mut identifiers = Vec::new();
8889    let mut stack = prefix[begin_index + 1..]
8890        .iter()
8891        .rev()
8892        .copied()
8893        .collect::<Vec<_>>();
8894    while let Some(current) = stack.pop() {
8895        if let Some(identifier) = direct_cpp_identifier_name(current, source) {
8896            identifiers.push(identifier);
8897            continue;
8898        }
8899        let mut cursor = current.walk();
8900        let children = current.named_children(&mut cursor).collect::<Vec<_>>();
8901        stack.extend(children.into_iter().rev());
8902    }
8903    let [keyword, namespace_name] = identifiers.as_slice() else {
8904        return None;
8905    };
8906    if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
8907    {
8908        return None;
8909    }
8910    let mut next = function.next_named_sibling();
8911    let next = loop {
8912        let candidate = next?;
8913        next = candidate.next_named_sibling();
8914        if candidate.kind() != "comment" {
8915            break candidate;
8916        }
8917    };
8918    flattened_macro_sentinel_name(next, source)
8919        .is_some_and(|name| is_namespace_end_sentinel(&name))
8920        .then(|| namespace_name.clone())
8921}
8922
8923/// A `function_definition` that exists only because tree-sitter recovered a
8924/// macro-decorated class head or a namespace-opening macro token. A declaration
8925/// in such a body keeps class or namespace scope, so a scope walk must step over
8926/// the wrapper instead of treating the declaration as block local.
8927fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
8928    crate::declarations::is_recovered_exported_class_container(node, source)
8929        || recovered_macro_namespace_name(node, source).is_some()
8930}
8931
8932fn flattened_macro_error_namespace_components(
8933    declaration: Node<'_>,
8934    source: &str,
8935) -> Option<Vec<String>> {
8936    let parent = declaration
8937        .parent()
8938        .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
8939    let mut cursor = parent.walk();
8940    let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
8941    let declaration_index = siblings
8942        .iter()
8943        .position(|candidate| same_node(*candidate, declaration))?;
8944    let begin_index = (0..declaration_index).rev().find(|index| {
8945        flattened_macro_sentinel_name(siblings[*index], source)
8946            .is_some_and(|name| is_namespace_begin_sentinel(&name))
8947    })?;
8948
8949    let significant = siblings[begin_index + 1..declaration_index]
8950        .iter()
8951        .copied()
8952        .filter(|node| node.kind() != "comment")
8953        .collect::<Vec<_>>();
8954    let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
8955        return None;
8956    };
8957    if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
8958        return None;
8959    }
8960    let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
8961    if significant[2..].iter().any(|node| {
8962        flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
8963            is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
8964        })
8965    }) {
8966        return None;
8967    }
8968
8969    let mut saw_namespace_close = false;
8970    for sibling in siblings.iter().skip(declaration_index + 1).copied() {
8971        if sibling.kind() == "comment" {
8972            continue;
8973        }
8974        if !saw_namespace_close {
8975            if direct_unmatched_closing_brace(sibling) {
8976                saw_namespace_close = true;
8977                continue;
8978            }
8979            if flattened_macro_sentinel_name(sibling, source).is_some() {
8980                return None;
8981            }
8982            continue;
8983        }
8984        if !flattened_macro_sentinel_name(sibling, source)
8985            .is_some_and(|name| is_namespace_end_sentinel(&name))
8986        {
8987            return None;
8988        }
8989        let mut components = enclosing_namespace_components(declaration, source)?;
8990        components.push(namespace_name);
8991        return Some(components);
8992    }
8993    None
8994}
8995
8996fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
8997    // At translation-unit scope the trailing `X_NAMESPACE_END` token parses as
8998    // an `expression_statement` with a missing semicolon; inside a namespace
8999    // body the same token stays a bare `type_identifier`.
9000    let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
9001        node.named_child(0)?
9002    } else {
9003        node
9004    };
9005    let candidate = direct_cpp_identifier_name(node, source).or_else(|| {
9006        node.child_by_field_name("type")
9007            .and_then(|type_node| direct_cpp_identifier_name(type_node, source))
9008    })?;
9009    (cpp_export_macro_token(&candidate)
9010        && (is_namespace_begin_sentinel(&candidate) || is_namespace_end_sentinel(&candidate)))
9011    .then_some(candidate)
9012}
9013
9014/// Namespace-opening macros are spelled both ways in the wild:
9015/// `ABSL_NAMESPACE_BEGIN` (abseil, nlohmann) and `FMT_BEGIN_NAMESPACE` (fmt).
9016fn is_namespace_begin_sentinel(name: &str) -> bool {
9017    name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE")
9018}
9019
9020fn is_namespace_end_sentinel(name: &str) -> bool {
9021    name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE")
9022}
9023
9024fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
9025    if node.kind() != "ERROR" || node.named_child_count() != 1 {
9026        return None;
9027    }
9028    let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
9029    (!cpp_export_macro_token(&name)).then_some(name)
9030}
9031
9032fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
9033    if !matches!(
9034        node.kind(),
9035        "identifier" | "namespace_identifier" | "type_identifier"
9036    ) {
9037        return None;
9038    }
9039    let name = normalize_cpp_whitespace(node_text(node, source));
9040    (!name.is_empty()).then_some(name)
9041}
9042
9043fn guard_requirement_sets_match(
9044    left: &[(usize, HashSet<PreprocessorGuard>)],
9045    right: &[(usize, HashSet<PreprocessorGuard>)],
9046) -> bool {
9047    left.len() == right.len()
9048        && left.iter().all(|(_, left_guards)| {
9049            right
9050                .iter()
9051                .any(|(_, right_guards)| left_guards == right_guards)
9052        })
9053        && right.iter().all(|(_, right_guards)| {
9054            left.iter()
9055                .any(|(_, left_guards)| right_guards == left_guards)
9056        })
9057}
9058
9059fn macro_displaced_cpp_return_type(declaration: Node<'_>, source: &str) -> bool {
9060    let Some(type_node) = declaration.child_by_field_name("type") else {
9061        return false;
9062    };
9063    let type_name = normalize_cpp_whitespace(node_text(type_node, source));
9064    !type_name.is_empty()
9065        && type_name
9066            .chars()
9067            .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
9068        && (0..declaration.named_child_count()).any(|index| {
9069            declaration
9070                .named_child(index)
9071                .is_some_and(|child| child.kind() == "ERROR")
9072        })
9073}
9074
9075fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
9076    node.kind() == "ERROR"
9077        && (0..node.child_count())
9078            .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
9079}
9080
9081pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
9082    let mut ancestor = node.parent();
9083    while let Some(parent) = ancestor {
9084        if is_preprocessor_conditional(parent)
9085            && !is_file_covering_include_guard(parent, source)
9086            && !is_split_cpp_language_linkage_wrapper(parent, node, source)
9087        {
9088            return false;
9089        }
9090        ancestor = parent.parent();
9091    }
9092    true
9093}
9094
9095fn is_split_cpp_language_linkage_wrapper(
9096    conditional: Node<'_>,
9097    descendant: Node<'_>,
9098    source: &str,
9099) -> bool {
9100    if conditional.child_by_field_name("alternative").is_some()
9101        || !matches!(
9102            simple_preprocessor_guard(conditional, source),
9103            Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
9104        )
9105    {
9106        return false;
9107    }
9108    let mut current = descendant.parent();
9109    let linkage = loop {
9110        let Some(node) = current else {
9111            return false;
9112        };
9113        if node == conditional {
9114            return false;
9115        }
9116        if node.kind() == "linkage_specification" {
9117            break node;
9118        }
9119        current = node.parent();
9120    };
9121    if linkage
9122        .child_by_field_name("value")
9123        .is_none_or(|value| node_text(value, source) != "\"C\"")
9124    {
9125        return false;
9126    }
9127    let Some(body) = linkage.child_by_field_name("body") else {
9128        return false;
9129    };
9130    let closes_opening_branch = (0..body.named_child_count())
9131        .filter_map(|index| body.named_child(index))
9132        .take_while(|child| child.end_byte() <= descendant.start_byte())
9133        .any(|child| {
9134            child.kind() == "preproc_call"
9135                && child
9136                    .child_by_field_name("directive")
9137                    .is_some_and(|directive| node_text(directive, source) == "#endif")
9138        });
9139    let reopens_for_closing_brace = (0..body.named_child_count())
9140        .filter_map(|index| body.named_child(index))
9141        .skip_while(|child| child.start_byte() < descendant.end_byte())
9142        .any(|child| {
9143            matches!(
9144                simple_preprocessor_guard(child, source),
9145                Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
9146            ) && (0..child.child_count()).any(|index| {
9147                child
9148                    .child(index)
9149                    .is_some_and(|token| token.kind() == "#endif" && token.is_missing())
9150            })
9151        });
9152    closes_opening_branch && reopens_for_closing_brace
9153}
9154
9155pub fn call_arity(node: Node<'_>) -> usize {
9156    node.child_by_field_name("arguments")
9157        .or_else(|| node.child_by_field_name("parameters"))
9158        .or_else(|| node.child_by_field_name("value"))
9159        .or_else(|| first_named_child_of_kind(node, "argument_list"))
9160        .or_else(|| first_named_child_of_kind(node, "initializer_list"))
9161        .map(|args| argument_children(args).count())
9162        .unwrap_or(0)
9163}
9164
9165pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
9166    let recovered_block_arguments = recovered_block_literal_arguments(node);
9167    (0..node.child_count())
9168        .filter_map(move |index| node.child(index))
9169        .filter(|child| child.is_named() && !child.is_extra())
9170        .flat_map(move |child| {
9171            if let Some((raw, left, right)) = recovered_block_arguments
9172                && child == raw
9173            {
9174                [Some(left), Some(right)]
9175            } else {
9176                [Some(child), None]
9177            }
9178        })
9179        .flatten()
9180}
9181
9182fn recovered_c_keyword_argument_count(
9183    file: &ProjectFile,
9184    call: Node<'_>,
9185    arguments: Node<'_>,
9186    source: &str,
9187) -> usize {
9188    // A C identifier that is a C++ keyword can be displaced twice by the C++
9189    // grammar: first into a direct parameter-list `ERROR(keyword)`, then into
9190    // a direct argument-list `ERROR(',', keyword)`. Match those CST tokens in
9191    // the enclosing C function before restoring the otherwise dropped slot.
9192    if !is_c_source_file(file) || arguments.kind() != "argument_list" {
9193        return 0;
9194    }
9195    let mut ancestor = Some(call);
9196    let function = loop {
9197        let Some(current) = ancestor else {
9198            return 0;
9199        };
9200        if current.kind() == "function_definition" {
9201            break current;
9202        }
9203        ancestor = current.parent();
9204    };
9205    let Some(parameters) = function
9206        .child_by_field_name("declarator")
9207        .and_then(|declarator| declarator.child_by_field_name("parameters"))
9208    else {
9209        return 0;
9210    };
9211    let displaced_parameter_keywords = (0..parameters.child_count())
9212        .filter_map(|index| parameters.child(index))
9213        .filter(|error| error.kind() == "ERROR")
9214        .filter_map(|error| {
9215            let parameter = error.prev_named_sibling()?;
9216            if parameter.kind() != "parameter_declaration"
9217                || parameter.end_byte() != error.start_byte()
9218                || extract_variable_name(parameter, source).is_some()
9219            {
9220                return None;
9221            }
9222            let mut children = (0..error.child_count())
9223                .filter_map(|index| error.child(index))
9224                .filter(|child| !child.is_extra() && !child.is_missing());
9225            let keyword = children.next()?;
9226            (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
9227                .then_some(keyword)
9228        })
9229        .collect::<Vec<_>>();
9230    if displaced_parameter_keywords.is_empty() {
9231        return 0;
9232    }
9233
9234    (0..arguments.child_count())
9235        .filter_map(|index| arguments.child(index))
9236        .filter(|error| error.kind() == "ERROR" && error.is_extra())
9237        .filter(|error| {
9238            let mut children = (0..error.child_count())
9239                .filter_map(|index| error.child(index))
9240                .filter(|child| !child.is_extra() && !child.is_missing());
9241            let Some(comma) = children.next() else {
9242                return false;
9243            };
9244            let Some(keyword) = children.next() else {
9245                return false;
9246            };
9247            children.next().is_none()
9248                && comma.kind() == ","
9249                && !keyword.is_named()
9250                && keyword.child_count() == 0
9251                && displaced_parameter_keywords
9252                    .iter()
9253                    .any(|parameter| parameter.kind_id() == keyword.kind_id())
9254        })
9255        .count()
9256}
9257
9258fn recovered_block_literal_arguments<'tree>(
9259    arguments: Node<'tree>,
9260) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
9261    if arguments.kind() != "argument_list" {
9262        return None;
9263    }
9264    let mut raw_arguments = (0..arguments.child_count())
9265        .filter_map(|index| arguments.child(index))
9266        .filter(|child| child.is_named() && !child.is_extra());
9267    let raw = raw_arguments.next()?;
9268    if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
9269        return None;
9270    }
9271
9272    let left = raw.child_by_field_name("left")?;
9273    if left.is_missing() || left.start_byte() == left.end_byte() {
9274        return None;
9275    }
9276    let right = raw.child_by_field_name("right")?;
9277    if right.kind() != "compound_literal_expression"
9278        || right.is_missing()
9279        || right
9280            .child_by_field_name("type")
9281            .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
9282        || right
9283            .child_by_field_name("value")
9284            .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
9285    {
9286        return None;
9287    }
9288    let has_intervening_error = (0..raw.child_count())
9289        .filter_map(|index| raw.child(index))
9290        .any(|child| {
9291            child.kind() == "ERROR"
9292                && !child.is_missing()
9293                && child.start_byte() >= left.end_byte()
9294                && child.end_byte() <= right.start_byte()
9295        });
9296    has_intervening_error.then_some((raw, left, right))
9297}
9298
9299pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
9300    match node.kind() {
9301        "new_expression" => node
9302            .child_by_field_name("type")
9303            .or_else(|| node.named_child(0)),
9304        "compound_literal_expression" => node.child_by_field_name("type"),
9305        "call_expression" => node.child_by_field_name("function"),
9306        _ => None,
9307    }
9308}
9309
9310pub fn field_initializer_constructs_target(
9311    node: Node<'_>,
9312    ctx: &ScanCtx<'_>,
9313    owner: &CodeUnit,
9314) -> bool {
9315    // A qualified name in a constructor initializer denotes a base
9316    // subobject constructor (`namespace::Base(args)`), not a member field.  The
9317    // field-initializer grammar exposes the qualified name as one structured
9318    // `qualified_identifier`; resolve its owner through the same lexical type
9319    // machinery used for ordinary C++ type references before considering the
9320    // initializer a hit.  This keeps an unrelated `namespace::Other(...)`, a
9321    // qualified non-constructor member, and an unresolved owner out of the
9322    // target constructor's inverse usage set.
9323    if first_named_child_of_kind(node, "qualified_identifier").is_some() {
9324        return qualified_base_initializer_constructs_target(node, ctx, owner);
9325    }
9326    let Some(name) = node
9327        .child_by_field_name("name")
9328        .or_else(|| first_named_child_of_kind(node, "field_identifier"))
9329        .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
9330    else {
9331        return false;
9332    };
9333    let field_name = node_text(name, ctx.source);
9334    ctx.visibility
9335        .visible_identifier_candidates(ctx.file, field_name)
9336        .filter(|unit| unit.is_field() && unit.identifier() == field_name)
9337        .any(|unit| field_declares_type(unit, ctx, owner))
9338}
9339
9340fn qualified_base_initializer_constructs_target(
9341    node: Node<'_>,
9342    ctx: &ScanCtx<'_>,
9343    owner: &CodeUnit,
9344) -> bool {
9345    let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
9346        return false;
9347    };
9348    let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
9349        return false;
9350    };
9351    let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
9352        return false;
9353    };
9354    let resolves_target = |components: &[String]| {
9355        matches!(
9356            ctx.visibility.resolve_type_components_lexically_for_target(
9357                &ctx.analyzer,
9358                ctx.file,
9359                components,
9360                is_globally_qualified_cpp_name(qualified),
9361                &lexical_scope,
9362                owner,
9363            ),
9364            LexicalTypeResolution::Resolved { unit, .. }
9365                if same_visible_symbol(&unit, owner)
9366        )
9367    };
9368    if resolves_target(&components) {
9369        return true;
9370    }
9371
9372    // Some real-world code spells a base mem-initializer as
9373    // `Base::Base(args)`. In that structured path the final component repeats
9374    // the constructor name; resolve the preceding type path. The terminal
9375    // identity check prevents an arbitrary qualified member from taking this
9376    // route.
9377    components
9378        .last()
9379        .is_some_and(|terminal| terminal == owner.identifier())
9380        && resolves_target(&components[..components.len() - 1])
9381}
9382
9383fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
9384    unit.signature()
9385        .is_some_and(|declaration| field_declaration_type_matches(declaration, unit, ctx, owner))
9386        || ctx
9387            .analyzer
9388            .get_source(unit, false)
9389            .is_some_and(|declaration| {
9390                field_declaration_type_matches(&declaration, unit, ctx, owner)
9391            })
9392}
9393
9394pub fn field_declared_binding(
9395    analyzer: &CppGraphSource<'_>,
9396    visibility: &VisibilityIndex<'_>,
9397    visible_from: &ProjectFile,
9398    field: &CodeUnit,
9399) -> Option<CppScanBinding> {
9400    let fact = visibility.field_declared_type_fact(analyzer, field)?;
9401    let normalized = normalize_field_type_text(&fact.type_text);
9402    let resolved = visibility.resolve_unique_canonical_type_for_declaration(
9403        analyzer,
9404        visible_from,
9405        field,
9406        &normalized,
9407    );
9408    let resolved = match (resolved, fact.template_arguments.as_deref()) {
9409        (Some(primary), Some(arguments)) => visibility
9410            .resolve_template_arguments(visible_from, primary, arguments)
9411            .ok(),
9412        (resolved, None) => resolved,
9413        (None, Some(_)) => None,
9414    };
9415    Some(CppScanBinding::from_type_name(
9416        normalized,
9417        resolved,
9418        fact.indirection,
9419    ))
9420}
9421
9422/// The one logical type the candidates name, or why they do not name one.
9423fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
9424    let Some(first) = candidates.first() else {
9425        return Err(TypeCandidateFailure::Unresolvable);
9426    };
9427    if candidates
9428        .iter()
9429        .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
9430    {
9431        Ok((*first).clone())
9432    } else {
9433        Err(TypeCandidateFailure::Ambiguous)
9434    }
9435}
9436
9437fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
9438    logical_type_candidate(candidates).ok()
9439}
9440
9441fn unique_type_candidate_preserving_alias(
9442    analyzer: &CppGraphSource<'_>,
9443    candidates: &[&CodeUnit],
9444) -> Option<CodeUnit> {
9445    let first = *candidates.first()?;
9446    if declared_type_alias(analyzer, first) {
9447        return candidates
9448            .iter()
9449            .all(|candidate| {
9450                declared_type_alias(analyzer, candidate)
9451                    && candidate.kind() == first.kind()
9452                    && candidate.fq_name() == first.fq_name()
9453                    && candidate.source() == first.source()
9454            })
9455            .then(|| first.clone());
9456    }
9457    candidates
9458        .iter()
9459        .all(|candidate| {
9460            !declared_type_alias(analyzer, candidate)
9461                && candidate.kind() == first.kind()
9462                && candidate.fq_name() == first.fq_name()
9463        })
9464        .then(|| first.clone())
9465}
9466
9467fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
9468    is_type_alias(unit)
9469        || analyzer
9470            .type_alias_provider()
9471            .is_some_and(|provider| provider.is_type_alias(unit))
9472}
9473
9474pub fn field_declared_type_binding(
9475    analyzer: &CppGraphSource<'_>,
9476    visibility: &VisibilityIndex<'_>,
9477    visible_from: &ProjectFile,
9478    field: &CodeUnit,
9479) -> Option<(String, Option<CodeUnit>, i32)> {
9480    let fact = visibility.field_declared_type_fact(analyzer, field)?;
9481    let normalized = normalize_field_type_text(&fact.type_text);
9482    let primary = visibility.resolve_unique_canonical_type_for_declaration(
9483        analyzer,
9484        visible_from,
9485        field,
9486        &normalized,
9487    );
9488    let resolved = match (primary, fact.template_arguments.as_deref()) {
9489        (Some(primary), Some(arguments)) => visibility
9490            .resolve_template_arguments(visible_from, primary, arguments)
9491            .ok(),
9492        (resolved, None) => resolved,
9493        (None, Some(_)) => None,
9494    };
9495    Some((normalized, resolved, fact.indirection))
9496}
9497
9498fn decode_field_declared_type_fact(
9499    analyzer: &CppGraphSource<'_>,
9500    field: &CodeUnit,
9501) -> Option<DeclaredFieldTypeFact> {
9502    let declaration = analyzer.get_source(field, false)?;
9503    let mut parser = Parser::new();
9504    parser
9505        .set_language(&tree_sitter_cpp::LANGUAGE.into())
9506        .ok()?;
9507    let tree = parser.parse(&declaration, None)?;
9508    let mut stack = vec![tree.root_node()];
9509    while let Some(node) = stack.pop() {
9510        if matches!(node.kind(), "declaration" | "field_declaration")
9511            && let Some(type_node) = node
9512                .child_by_field_name("type")
9513                .or_else(|| first_type_child(node))
9514            && let Some(indirection) =
9515                declared_name_indirection(node, type_node, field.identifier(), &declaration)
9516        {
9517            let declared_type = if matches!(
9518                type_node.kind(),
9519                "class_specifier" | "struct_specifier" | "union_specifier"
9520            ) {
9521                type_node.child_by_field_name("name")
9522            } else {
9523                Some(type_node)
9524            };
9525            let type_text = declared_type.map_or_else(
9526                || field.identifier().to_string(),
9527                |declared_type| node_text(declared_type, &declaration).to_string(),
9528            );
9529            return Some(DeclaredFieldTypeFact {
9530                type_text,
9531                indirection,
9532                template_arguments: declared_type.and_then(|declared_type| {
9533                    cpp_template_reference_arguments(declared_type, &declaration)
9534                }),
9535            });
9536        }
9537        let mut cursor = node.walk();
9538        stack.extend(node.named_children(&mut cursor));
9539    }
9540    None
9541}
9542
9543/// Text of the type that a C or C++ alias declaration names, read from the
9544/// `type_definition` or `alias_declaration` node's `type` field.
9545///
9546/// The declaration text is never scanned. A function-pointer typedef
9547/// interleaves its aliased type with its declarator (`typedef R (*F)(int)`),
9548/// so no prefix or suffix of the spelling isolates the target.
9549///
9550/// An alias whose declarator is a function declarator names a function type:
9551/// `typedef R F(int)`, `typedef R (*F)(int)`, `typedef R *F(int)`, and
9552/// `using F = R (*)(int)`. The analyzer's type model names declared types only,
9553/// so such an alias has no canonical target. Its `type` field holds the return
9554/// type `R`, which is a different type from the alias, so this returns `None`
9555/// rather than that return type.
9556pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
9557    let mut parser = Parser::new();
9558    parser
9559        .set_language(&tree_sitter_cpp::LANGUAGE.into())
9560        .ok()?;
9561    let tree = parser.parse(declaration, None)?;
9562    let mut stack = vec![tree.root_node()];
9563    while let Some(node) = stack.pop() {
9564        let type_node = match node.kind() {
9565            "type_definition" => {
9566                let mut cursor = node.walk();
9567                if node
9568                    .children_by_field_name("declarator", &mut cursor)
9569                    .any(declarator_names_function_type)
9570                {
9571                    return None;
9572                }
9573                node.child_by_field_name("type")?
9574            }
9575            "alias_declaration" => {
9576                let type_node = node.child_by_field_name("type")?;
9577                if type_node
9578                    .child_by_field_name("declarator")
9579                    .is_some_and(declarator_names_function_type)
9580                {
9581                    return None;
9582                }
9583                type_node
9584            }
9585            _ => {
9586                let mut cursor = node.walk();
9587                let children = node.named_children(&mut cursor).collect::<Vec<_>>();
9588                stack.extend(children.into_iter().rev());
9589                continue;
9590            }
9591        };
9592        return Some(node_text(type_node, declaration).to_string());
9593    }
9594    None
9595}
9596
9597/// Whether an alias declaration's own declarator adds indirection that
9598/// [`cpp_alias_declaration_target_text`] does not report.
9599///
9600/// That function reads the declaration's `type` field, where `typedef Foo *Bar`
9601/// keeps only `Foo`: the `*` lives in the sibling declarator. Substituting such
9602/// an alias would equate `f(Bar)` with `f(Foo)`, so a comparison that cannot
9603/// prove the alias adds no indirection must refuse to follow it. A declaration
9604/// this cannot read at all is refused for the same reason.
9605fn cpp_alias_declaration_adds_indirection(declaration: &str) -> bool {
9606    let mut parser = Parser::new();
9607    if parser
9608        .set_language(&tree_sitter_cpp::LANGUAGE.into())
9609        .is_err()
9610    {
9611        return true;
9612    }
9613    let Some(tree) = parser.parse(declaration, None) else {
9614        return true;
9615    };
9616    let mut stack = vec![tree.root_node()];
9617    while let Some(node) = stack.pop() {
9618        let declarators = match node.kind() {
9619            "type_definition" => {
9620                let mut cursor = node.walk();
9621                node.children_by_field_name("declarator", &mut cursor)
9622                    .collect::<Vec<_>>()
9623            }
9624            "alias_declaration" => node
9625                .child_by_field_name("type")
9626                .and_then(|type_node| type_node.child_by_field_name("declarator"))
9627                .into_iter()
9628                .collect::<Vec<_>>(),
9629            _ => {
9630                let mut cursor = node.walk();
9631                let children = node.named_children(&mut cursor).collect::<Vec<_>>();
9632                stack.extend(children.into_iter().rev());
9633                continue;
9634            }
9635        };
9636        return declarators.into_iter().any(cpp_declarator_adds_indirection);
9637    }
9638    true
9639}
9640
9641/// True when an alias declarator names a function type.
9642///
9643/// The declarator chain is walked through the `declarator` field, so the
9644/// parameter list -- a sibling field -- is never entered and a parameter's own
9645/// function declarator cannot be mistaken for the alias's.
9646fn declarator_names_function_type(declarator: Node<'_>) -> bool {
9647    let mut current = Some(declarator);
9648    while let Some(node) = current {
9649        match node.kind() {
9650            "function_declarator" | "abstract_function_declarator" => return true,
9651            "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
9652                current = node.named_child(0);
9653            }
9654            _ => current = node.child_by_field_name("declarator"),
9655        }
9656    }
9657    false
9658}
9659
9660fn decode_structured_alias_target(
9661    analyzer: &CppGraphSource<'_>,
9662    unit: &CodeUnit,
9663) -> Option<StructuredAliasTarget> {
9664    analyzer
9665        .get_source(unit, false)
9666        .and_then(|declaration| decode_structured_alias_target_source(unit, &declaration, true))
9667        .or_else(|| {
9668            let signature = unit.signature()?;
9669            decode_structured_alias_target_source(unit, signature, false)
9670        })
9671}
9672
9673fn decode_structured_alias_target_source(
9674    unit: &CodeUnit,
9675    declaration: &str,
9676    require_top_level: bool,
9677) -> Option<StructuredAliasTarget> {
9678    let mut parser = Parser::new();
9679    parser
9680        .set_language(&tree_sitter_cpp::LANGUAGE.into())
9681        .ok()?;
9682    let tree = parser.parse(declaration, None)?;
9683    let mut stack = vec![tree.root_node()];
9684    while let Some(node) = stack.pop() {
9685        let type_node = match node.kind() {
9686            "type_definition" => {
9687                if require_top_level
9688                    && node
9689                        .parent()
9690                        .is_none_or(|parent| parent.kind() != "translation_unit")
9691                {
9692                    let mut cursor = node.walk();
9693                    stack.extend(node.named_children(&mut cursor));
9694                    continue;
9695                }
9696                let mut declarator_cursor = node.walk();
9697                let declarator = node
9698                    .children_by_field_name("declarator", &mut declarator_cursor)
9699                    .find(|declarator| {
9700                        extract_typedef_declarator_name(*declarator, declaration)
9701                            .is_some_and(|name| name == unit.identifier())
9702                    })?;
9703                if declarator_names_function_type(declarator) {
9704                    return None;
9705                }
9706                node.child_by_field_name("type")?
9707            }
9708            "alias_declaration" => {
9709                if require_top_level
9710                    && node
9711                        .parent()
9712                        .is_none_or(|parent| parent.kind() != "translation_unit")
9713                {
9714                    let mut cursor = node.walk();
9715                    stack.extend(node.named_children(&mut cursor));
9716                    continue;
9717                }
9718                let name = node.child_by_field_name("name")?;
9719                if node_text(name, declaration) != unit.identifier() {
9720                    return None;
9721                }
9722                let type_node = node.child_by_field_name("type")?;
9723                if type_node
9724                    .child_by_field_name("declarator")
9725                    .is_some_and(declarator_names_function_type)
9726                {
9727                    return None;
9728                }
9729                type_node
9730            }
9731            _ => {
9732                let mut cursor = node.walk();
9733                stack.extend(node.named_children(&mut cursor));
9734                continue;
9735            }
9736        };
9737        return structured_alias_type_target(type_node, declaration);
9738    }
9739    None
9740}
9741
9742fn structured_alias_type_target(
9743    mut type_node: Node<'_>,
9744    source: &str,
9745) -> Option<StructuredAliasTarget> {
9746    while type_node.kind() == "type_descriptor" {
9747        type_node = type_node.child_by_field_name("type")?;
9748    }
9749    if type_node.kind() == "primitive_type" {
9750        return Some(StructuredAliasTarget::Builtin);
9751    }
9752    if matches!(
9753        type_node.kind(),
9754        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
9755    ) {
9756        type_node = type_node.child_by_field_name("name")?;
9757    }
9758    let global = type_node.child_by_field_name("scope").is_none()
9759        && type_node.child(0).is_some_and(|child| child.kind() == "::");
9760    let mut components = Vec::new();
9761    append_structured_type_components(type_node, source, &mut components)?;
9762    let arguments = cpp_template_reference_arguments(type_node, source);
9763    (!components.is_empty()).then_some(StructuredAliasTarget::Named {
9764        components,
9765        global,
9766        arguments,
9767    })
9768}
9769
9770fn append_structured_type_components(
9771    node: Node<'_>,
9772    source: &str,
9773    out: &mut Vec<String>,
9774) -> Option<()> {
9775    match node.kind() {
9776        "identifier" | "namespace_identifier" | "type_identifier" => {
9777            out.push(node_text(node, source).to_string());
9778            Some(())
9779        }
9780        "template_type" => {
9781            append_structured_type_components(node.child_by_field_name("name")?, source, out)
9782        }
9783        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
9784            if let Some(scope) = node.child_by_field_name("scope") {
9785                append_structured_type_components(scope, source, out)?;
9786            }
9787            append_structured_type_components(node.child_by_field_name("name")?, source, out)
9788        }
9789        _ => None,
9790    }
9791}
9792
9793fn declared_name_indirection(
9794    declaration: Node<'_>,
9795    type_node: Node<'_>,
9796    field_name: &str,
9797    source: &str,
9798) -> Option<i32> {
9799    let mut stack = Vec::new();
9800    let mut cursor = declaration.walk();
9801    stack.extend(
9802        declaration
9803            .named_children(&mut cursor)
9804            .filter(|child| !same_node(*child, type_node)),
9805    );
9806    while let Some(node) = stack.pop() {
9807        if matches!(node.kind(), "identifier" | "field_identifier")
9808            && node_text(node, source) == field_name
9809        {
9810            let mut indirection = 0;
9811            let mut current = node.parent();
9812            while let Some(parent) = current {
9813                if same_node(parent, declaration) {
9814                    return Some(indirection);
9815                }
9816                if parent.kind() == "pointer_declarator" {
9817                    indirection += 1;
9818                }
9819                current = parent.parent();
9820            }
9821            return None;
9822        }
9823        let mut cursor = node.walk();
9824        stack.extend(node.named_children(&mut cursor));
9825    }
9826    None
9827}
9828
9829fn field_declaration_type_matches(
9830    declaration: &str,
9831    unit: &CodeUnit,
9832    ctx: &ScanCtx<'_>,
9833    owner: &CodeUnit,
9834) -> bool {
9835    ctx.visibility
9836        .resolves_to_type(&ctx.analyzer, ctx.file, declaration, owner)
9837        || field_type_prefix(declaration, unit.identifier()).is_some_and(|type_text| {
9838            let normalized = normalize_field_type_text(type_text);
9839            ctx.visibility
9840                .resolves_to_type(&ctx.analyzer, ctx.file, type_text, owner)
9841                || ctx.visibility.resolves_to_type(
9842                    &ctx.analyzer,
9843                    ctx.file,
9844                    normalized.as_str(),
9845                    owner,
9846                )
9847        })
9848}
9849
9850fn field_type_prefix<'a>(declaration: &'a str, field_name: &str) -> Option<&'a str> {
9851    let declaration = declaration
9852        .split(['=', ';'])
9853        .next()
9854        .unwrap_or(declaration)
9855        .trim();
9856    let index = declaration.rfind(field_name)?;
9857    let before = &declaration[..index];
9858    let after = &declaration[index + field_name.len()..];
9859    if before.chars().next_back().is_some_and(is_identifier_char)
9860        || after.chars().next().is_some_and(is_identifier_char)
9861    {
9862        return None;
9863    }
9864    Some(before.trim())
9865}
9866
9867fn normalize_field_type_text(type_text: &str) -> String {
9868    const FIELD_SPECIFIERS: [&str; 8] = [
9869        "extern ",
9870        "static ",
9871        "mutable ",
9872        "constexpr ",
9873        "constinit ",
9874        "inline ",
9875        "volatile ",
9876        "const ",
9877    ];
9878
9879    let mut normalized = normalize_type_text(type_text);
9880    loop {
9881        let Some(stripped) = FIELD_SPECIFIERS
9882            .iter()
9883            .find_map(|specifier| normalized.strip_prefix(specifier))
9884        else {
9885            return normalized;
9886        };
9887        normalized = normalize_type_text(stripped);
9888    }
9889}
9890
9891fn is_identifier_char(ch: char) -> bool {
9892    ch == '_' || ch.is_ascii_alphanumeric()
9893}
9894
9895pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
9896    let Some(type_node) = node.child_by_field_name("type") else {
9897        return false;
9898    };
9899    ctx.visibility.resolves_to_type(
9900        &ctx.analyzer,
9901        ctx.file,
9902        node_text(type_node, ctx.source),
9903        owner,
9904    )
9905}
9906
9907pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9908    !ctx.analyzer
9909        .declarations(ctx.file)
9910        .into_iter()
9911        .filter(|unit| unit.is_function())
9912        .any(|unit| {
9913            ctx.analyzer.ranges(&unit).iter().any(|range| {
9914                node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
9915            })
9916        })
9917}
9918
9919pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
9920    let mut cursor = node.walk();
9921    for child in node.named_children(&mut cursor) {
9922        if child.kind() == "init_declarator" {
9923            return child
9924                .child_by_field_name("value")
9925                .or_else(|| first_named_child_of_kind(child, "initializer_list"))
9926                .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
9927                .map(declaration_init_value_arity)
9928                .unwrap_or(0);
9929        }
9930        if is_declarator_node(child) {
9931            return declaration_declarator_arity(child);
9932        }
9933    }
9934    0
9935}
9936
9937fn declaration_init_value_arity(value: Node<'_>) -> usize {
9938    match value.kind() {
9939        "argument_list" | "initializer_list" => argument_children(value).count(),
9940        "compound_literal_expression" => call_arity(value),
9941        _ => 1,
9942    }
9943}
9944
9945fn declaration_declarator_arity(node: Node<'_>) -> usize {
9946    if let Some(parameters) = node.child_by_field_name("parameters") {
9947        return argument_children(parameters).count();
9948    }
9949    node.child_by_field_name("declarator")
9950        .map(declaration_declarator_arity)
9951        .unwrap_or(0)
9952}
9953
9954fn first_named_child_of_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
9955    let mut cursor = node.walk();
9956    node.named_children(&mut cursor)
9957        .find(|child| child.kind() == kind)
9958}
9959
9960fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
9961    let mut stack = vec![root];
9962    while let Some(node) = stack.pop() {
9963        if node.kind() == kind {
9964            return Some(node);
9965        }
9966        for index in (0..node.named_child_count()).rev() {
9967            if let Some(child) = node.named_child(index) {
9968                stack.push(child);
9969            }
9970        }
9971    }
9972    None
9973}
9974
9975fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
9976    if node.kind() == "identifier" {
9977        return true;
9978    }
9979    if node.kind() == "parenthesized_expression" {
9980        return false;
9981    }
9982    if node.kind() == "call_expression" {
9983        return node
9984            .child_by_field_name("function")
9985            .is_some_and(|function| function.kind() == "identifier");
9986    }
9987    let mut stack = vec![node];
9988    while let Some(descendant) = stack.pop() {
9989        if descendant != node && descendant.kind() == "parenthesized_expression" {
9990            continue;
9991        }
9992        if descendant.kind() == "identifier" {
9993            return true;
9994        }
9995        if descendant.kind() == "call_expression" {
9996            if descendant
9997                .child_by_field_name("function")
9998                .is_some_and(|function| function.kind() == "identifier")
9999            {
10000                return true;
10001            }
10002            continue;
10003        }
10004        for index in (0..descendant.named_child_count()).rev() {
10005            if let Some(child) = descendant.named_child(index) {
10006                stack.push(child);
10007            }
10008        }
10009    }
10010    false
10011}
10012
10013fn macro_expansion_shape_is_safe(
10014    node: Node<'_>,
10015    source: &str,
10016    parameters: &[String],
10017    environment: &MacroEnvironment,
10018) -> bool {
10019    if matches!(node.kind(), "identifier" | "parenthesized_expression") {
10020        return true;
10021    }
10022    if node.kind() == "call_expression" {
10023        let Some(function) = node.child_by_field_name("function") else {
10024            return true;
10025        };
10026        if function.kind() != "identifier" {
10027            return true;
10028        }
10029        let function_name = node_text(function, source);
10030        if parameters
10031            .iter()
10032            .any(|parameter| parameter == function_name)
10033        {
10034            return false;
10035        }
10036        if !environment.may_bind(function_name) {
10037            return true;
10038        }
10039        let Some(arguments) = node.child_by_field_name("arguments") else {
10040            return false;
10041        };
10042        return argument_children(arguments).all(|argument| {
10043            if argument.kind() == "identifier"
10044                && parameters
10045                    .iter()
10046                    .any(|parameter| parameter == node_text(argument, source))
10047            {
10048                return false;
10049            }
10050            macro_expansion_shape_is_safe(argument, source, parameters, environment)
10051        });
10052    }
10053    let mut stack = vec![node];
10054    while let Some(descendant) = stack.pop() {
10055        if descendant != node {
10056            if descendant.kind() == "parenthesized_expression" {
10057                continue;
10058            }
10059            if descendant.kind() == "call_expression" {
10060                let expands = descendant
10061                    .child_by_field_name("function")
10062                    .filter(|function| function.kind() == "identifier")
10063                    .is_some_and(|function| environment.may_bind(node_text(function, source)));
10064                if expands {
10065                    return false;
10066                }
10067                continue;
10068            }
10069        }
10070        if descendant.kind() == "identifier" {
10071            let identifier = node_text(descendant, source);
10072            if parameters.iter().any(|parameter| parameter == identifier)
10073                || environment.may_bind(identifier)
10074            {
10075                return false;
10076            }
10077        }
10078        for index in (0..descendant.named_child_count()).rev() {
10079            if let Some(child) = descendant.named_child(index) {
10080                stack.push(child);
10081            }
10082        }
10083    }
10084    true
10085}
10086
10087fn structured_include_path<'a>(path: Node<'_>, source: &'a str) -> Option<&'a str> {
10088    let text = node_text(path, source);
10089    match path.kind() {
10090        "string_literal" => text.strip_prefix('"')?.strip_suffix('"'),
10091        "system_lib_string" => text.strip_prefix('<')?.strip_suffix('>'),
10092        _ => None,
10093    }
10094}
10095
10096fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
10097    let descendant = node;
10098    while let Some(parent) = node.parent() {
10099        if is_preprocessor_conditional(parent)
10100            && !is_file_covering_include_guard(parent, source)
10101            && preprocessor_conditional_contains_descendant(parent, descendant)
10102        {
10103            return true;
10104        }
10105        node = parent;
10106    }
10107    false
10108}
10109
10110fn is_preprocessor_conditional(node: Node<'_>) -> bool {
10111    matches!(
10112        node.kind(),
10113        "preproc_if"
10114            | "preproc_ifdef"
10115            | "preproc_ifndef"
10116            | "preproc_elif"
10117            | "preproc_elifdef"
10118            | "preproc_else"
10119    )
10120}
10121
10122fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
10123    node.parent()
10124        .filter(|parent| parent.kind() == "translation_unit")
10125        .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
10126        && is_canonical_include_guard(node, source)
10127}
10128
10129fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
10130    if node.kind() != "preproc_ifdef"
10131        || node
10132            .child(0)
10133            .is_none_or(|directive| directive.kind() != "#ifndef")
10134        || node.child_by_field_name("alternative").is_some()
10135    {
10136        return false;
10137    }
10138    let Some(guard_name) = node.child_by_field_name("name") else {
10139        return false;
10140    };
10141    let mut cursor = node.walk();
10142    node.named_children(&mut cursor)
10143        .find(|child| *child != guard_name && child.kind() != "comment")
10144        .filter(|child| child.kind() == "preproc_def")
10145        .and_then(|definition| definition.child_by_field_name("name"))
10146        .is_some_and(|defined_name| {
10147            node_text(defined_name, source) == node_text(guard_name, source)
10148        })
10149}
10150
10151fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
10152    let mut guard = None;
10153    for index in 0..root.named_child_count() {
10154        let Some(child) = root.named_child(index) else {
10155            continue;
10156        };
10157        if child.kind() == "comment" || is_pragma_once(child, source) {
10158            continue;
10159        }
10160        if guard.is_none() && is_canonical_include_guard(child, source) {
10161            guard = Some(child);
10162        } else {
10163            return None;
10164        }
10165    }
10166    guard
10167        .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
10168        .map(|name| node_text(name, source).to_string())
10169}
10170
10171fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
10172    if (0..root.named_child_count())
10173        .filter_map(|index| root.named_child(index))
10174        .any(|child| is_pragma_once(child, source))
10175    {
10176        return MacroIncludeProtection::PragmaOnce;
10177    }
10178    top_level_canonical_include_guard_name(root, source)
10179        .map(MacroIncludeProtection::MacroGuard)
10180        .unwrap_or(MacroIncludeProtection::None)
10181}
10182
10183fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
10184    node.kind() == "preproc_call"
10185        && node
10186            .child_by_field_name("directive")
10187            .is_some_and(|directive| node_text(directive, source) == "#pragma")
10188        && node
10189            .child_by_field_name("argument")
10190            .is_some_and(|argument| node_text(argument, source).trim() == "once")
10191}
10192
10193fn parse_preproc_identifier(argument: &str) -> Option<String> {
10194    let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
10195    let mut parser = Parser::new();
10196    parser
10197        .set_language(&tree_sitter_cpp::LANGUAGE.into())
10198        .ok()?;
10199    let tree = parser.parse(&sentinel, None)?;
10200    if tree.root_node().has_error() {
10201        return None;
10202    }
10203    let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
10204    let identifier = statement.named_child(0)?;
10205    (identifier.kind() == "identifier" && statement.named_child_count() == 1)
10206        .then(|| node_text(identifier, &sentinel).to_string())
10207}
10208
10209pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
10210    match node.kind() {
10211        "identifier" | "field_identifier" => {
10212            let name = node_text(node, source).trim();
10213            (!name.is_empty()).then(|| name.to_string())
10214        }
10215        "abstract_array_declarator"
10216        | "abstract_function_declarator"
10217        | "abstract_parenthesized_declarator"
10218        | "abstract_pointer_declarator"
10219        | "abstract_reference_declarator" => None,
10220        "function_declarator" => node
10221            .child_by_field_name("declarator")
10222            .or_else(|| node.child_by_field_name("name"))
10223            .and_then(|child| extract_variable_name(child, source)),
10224        _ => node
10225            .child_by_field_name("declarator")
10226            .or_else(|| node.child_by_field_name("name"))
10227            .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
10228            .and_then(|child| extract_variable_name(child, source)),
10229    }
10230}
10231
10232/// Whether `file` is proven to use plain-C source semantics.
10233///
10234/// `Language::Cpp` intentionally serves both C and C++. Headers do not carry a
10235/// compilation dialect on their own, so only an exact `.c` source extension is
10236/// sufficient to reinterpret C++-grammar keyword nodes such as `this` as C
10237/// identifiers.
10238///
10239/// The exact-lowercase-`.c` rule itself lives in [`LanguageDialect::for_path`],
10240/// which extraction reads too (a `.c` file is extracted with C tag scope), so
10241/// the doctrine has exactly one definition.
10242pub fn is_c_source_file(file: &ProjectFile) -> bool {
10243    LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
10244}
10245
10246/// Whether a reference written in `file` reads C++ source with C semantics.
10247///
10248/// [`is_c_source_file`] answers the half a path settles on its own. The other
10249/// half is a header, which has no dialect of its own: it is read as C exactly
10250/// when every workspace translation unit that provably compiles it compiles it
10251/// as C ([`CppSource::header_uses_c_semantics`], issue #1970).
10252///
10253/// This is the gate for anything that is really about the compilation
10254/// language of the code being read -- which reading of an included header's
10255/// declarations is in scope, whether `this` is an ordinary identifier. It is
10256/// NOT the gate for a question that is genuinely about a `.c` file on disk;
10257/// those keep calling [`is_c_source_file`].
10258pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
10259    is_c_source_file(file) || cpp.header_uses_c_semantics(file)
10260}
10261
10262pub fn is_declarator_node(node: Node<'_>) -> bool {
10263    matches!(
10264        node.kind(),
10265        "identifier"
10266            | "field_identifier"
10267            | "pointer_declarator"
10268            | "reference_declarator"
10269            | "array_declarator"
10270            | "parenthesized_declarator"
10271            | "function_declarator"
10272    )
10273}
10274
10275#[derive(Clone, Default)]
10276pub struct OrphanedNamespaceTypeScopeIndex {
10277    scopes: Vec<OrphanedNamespaceTypeScope>,
10278}
10279
10280#[derive(Clone)]
10281struct OrphanedNamespaceTypeScope {
10282    body_end: usize,
10283    scope_end: usize,
10284    components: Vec<String>,
10285}
10286
10287impl OrphanedNamespaceTypeScopeIndex {
10288    /// Index the physical namespace interval that remains after tree-sitter
10289    /// prematurely closes an error-marked namespace at a recovered class body.
10290    /// The later unmatched `}` is the structured upper bound: declarations
10291    /// between the truncated body and that token remain in the namespace, while
10292    /// declarations after it do not.
10293    pub fn build(root: Node<'_>, source: &str) -> Self {
10294        let mut scopes = Vec::new();
10295        let mut stack = vec![root];
10296        while let Some(current) = stack.pop() {
10297            if current.kind() == "namespace_definition"
10298                && current.has_error()
10299                && let Some(body) = current.child_by_field_name("body")
10300                && current.end_byte() == body.end_byte()
10301                && let Some(name) = current.child_by_field_name("name")
10302            {
10303                let mut components =
10304                    enclosing_namespace_components(current, source).unwrap_or_default();
10305                if append_cpp_name_components(name, source, &mut components).is_some()
10306                    && !components.is_empty()
10307                {
10308                    let mut following = current.next_named_sibling();
10309                    while let Some(candidate) = following {
10310                        if direct_unmatched_closing_brace(candidate) {
10311                            scopes.push(OrphanedNamespaceTypeScope {
10312                                body_end: body.end_byte(),
10313                                scope_end: candidate.start_byte(),
10314                                components,
10315                            });
10316                            break;
10317                        }
10318                        following = candidate.next_named_sibling();
10319                    }
10320                }
10321            }
10322            if !current.has_error() {
10323                continue;
10324            }
10325            let mut cursor = current.walk();
10326            stack.extend(
10327                current
10328                    .named_children(&mut cursor)
10329                    .filter(|child| child.has_error()),
10330            );
10331        }
10332        Self { scopes }
10333    }
10334
10335    pub fn scope_at(&self, byte: usize) -> Option<(usize, &[String])> {
10336        self.scopes
10337            .iter()
10338            .filter(|scope| scope.body_end < byte && byte < scope.scope_end)
10339            .max_by_key(|scope| (scope.components.len(), scope.body_end))
10340            .map(|scope| (scope.body_end, scope.components.as_slice()))
10341    }
10342}
10343
10344#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10345pub enum RecoveredDeclaratorTypeContext {
10346    Declaration,
10347    FunctionDefinition,
10348    Parameter,
10349}
10350
10351/// Recognize a real type displaced into a qualified declarator by parser
10352/// recovery.
10353///
10354/// Tree-sitter parses `API Result *make(Arg);` as if `API` were the declared
10355/// type and `Result` were the scope of a qualified declarator with a missing
10356/// `::`. A template return such as `API Result<T> make()` uses a
10357/// `template_type` for the same recovered scope. The same recovery occurs for
10358/// macro-prefixed definitions, extern variables, and macro-decorated
10359/// parameters (`f(MACRO T* p)`, where the parameter's own `type` field takes
10360/// the macro). Keep this intentionally structural: the recovered scope must
10361/// have the grammar's missing separator, the qualified node must occupy the
10362/// declaration's declarator chain, a separate nonempty type must occupy the
10363/// normal type field, and the recovered name must unwrap to a real declarator
10364/// name.
10365pub fn recovered_macro_decorated_declarator_type(
10366    node: Node<'_>,
10367) -> Option<RecoveredDeclaratorTypeContext> {
10368    recovered_macro_decorated_type_node(node).map(|(_, context)| context)
10369}
10370
10371/// Return the declaration/function `type` displaced by a macro-shaped
10372/// qualified declarator, together with the enclosing declaration context.
10373/// Callers use the macro scope only as structural admission evidence; the
10374/// returned node is the real type reference to resolve and record.
10375pub fn recovered_macro_decorated_type_node(
10376    node: Node<'_>,
10377) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
10378    if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
10379        return None;
10380    }
10381    let qualified = node.parent()?;
10382    if qualified.kind() != "qualified_identifier"
10383        || qualified.child_by_field_name("scope") != Some(node)
10384        || !(0..qualified.child_count())
10385            .filter_map(|index| qualified.child(index))
10386            .any(|child| child.kind() == "::" && child.is_missing())
10387    {
10388        return None;
10389    }
10390    if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
10391        return None;
10392    }
10393
10394    let (declaration, context) = recovered_declarator_container(qualified)?;
10395    let type_node = declaration
10396        .child_by_field_name("type")
10397        .filter(|type_node| {
10398            *type_node != qualified
10399                && !type_node.is_missing()
10400                && type_node.start_byte() != type_node.end_byte()
10401        })?;
10402    Some((type_node, context))
10403}
10404
10405fn recovered_declarator_container(
10406    mut declarator: Node<'_>,
10407) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
10408    loop {
10409        let parent = declarator.parent()?;
10410        if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
10411            return Some((
10412                parent
10413                    .parent()
10414                    .filter(|declaration| declaration.kind() == "declaration")?,
10415                RecoveredDeclaratorTypeContext::Declaration,
10416            ));
10417        }
10418        if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
10419            return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
10420        }
10421        if parent.kind() == "function_definition"
10422            && has_field_child(parent, "declarator", declarator)
10423        {
10424            return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
10425        }
10426        // `f(MACRO T* p)` recovers exactly like `MACRO T *make(...)` does, one
10427        // level down: the parameter's `type` field takes the macro token and
10428        // the real type `T` becomes the recovered scope of the declarator.
10429        // Declining here left every xxhash `XXH_NOESCAPE` parameter with no
10430        // candidate at all (#1830).
10431        if matches!(
10432            parent.kind(),
10433            "parameter_declaration" | "optional_parameter_declaration"
10434        ) && has_field_child(parent, "declarator", declarator)
10435        {
10436            return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
10437        }
10438        if !matches!(
10439            parent.kind(),
10440            "array_declarator"
10441                | "function_declarator"
10442                | "parenthesized_declarator"
10443                | "pointer_declarator"
10444                | "pointer_type_declarator"
10445                | "reference_declarator"
10446        ) || !has_field_child(parent, "declarator", declarator)
10447        {
10448            return None;
10449        }
10450        declarator = parent;
10451    }
10452}
10453
10454fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
10455    let mut cursor = parent.walk();
10456    parent
10457        .children_by_field_name(field, &mut cursor)
10458        .any(|child| child == target)
10459}
10460
10461fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
10462    loop {
10463        if node.is_missing() || node.start_byte() == node.end_byte() {
10464            return false;
10465        }
10466        match node.kind() {
10467            "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
10468                return true;
10469            }
10470            "array_declarator"
10471            | "function_declarator"
10472            | "parenthesized_declarator"
10473            | "pointer_declarator"
10474            | "pointer_type_declarator"
10475            | "reference_declarator" => {
10476                let Some(declarator) = node.child_by_field_name("declarator") else {
10477                    return false;
10478                };
10479                node = declarator;
10480            }
10481            _ => return false,
10482        }
10483    }
10484}
10485
10486/// Aggregate-owner proof for a structurally recognized designated initializer.
10487pub enum DesignatedInitializerOwner {
10488    Resolved(CodeUnit),
10489    Unresolved,
10490}
10491
10492/// Recognize a designated-initializer field and, when possible, resolve its
10493/// aggregate owner.
10494///
10495/// Covers both the grammar's ordinary `field_designator` shape and the exact
10496/// recovery used for `.field = value` after a preprocessor-split array
10497/// initializer. Nested aggregate levels are deliberately left unresolved unless
10498/// the single outer level is the containing array initializer: resolving those
10499/// would require following the enclosing field's declared type. `None` means the
10500/// node is not a designator at all; an unresolved designator remains classified so
10501/// callers cannot fall through to unrelated global/member heuristics.
10502pub fn designated_initializer_owner(
10503    visibility: &VisibilityIndex<'_>,
10504    file: &ProjectFile,
10505    source: &str,
10506    node: Node<'_>,
10507) -> Option<DesignatedInitializerOwner> {
10508    if let Some(designator) = node
10509        .parent()
10510        .filter(|parent| parent.kind() == "field_designator")
10511    {
10512        let pair = designator.parent()?;
10513        if pair.kind() != "initializer_pair"
10514            || pair.child_by_field_name("designator") != Some(designator)
10515        {
10516            return None;
10517        }
10518        let initializer = pair.parent()?;
10519        if initializer.kind() != "initializer_list" {
10520            return None;
10521        }
10522        return Some(classified_designated_owner(initializer_list_owner(
10523            visibility,
10524            file,
10525            source,
10526            initializer,
10527        )));
10528    }
10529
10530    let init_declarator = node.parent()?;
10531    if init_declarator.child_by_field_name("declarator") != Some(node)
10532        || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
10533    {
10534        return None;
10535    }
10536    Some(classified_designated_owner(declaration_owner(
10537        visibility,
10538        file,
10539        source,
10540        init_declarator.parent()?,
10541    )))
10542}
10543
10544fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
10545    owner.map_or(
10546        DesignatedInitializerOwner::Unresolved,
10547        DesignatedInitializerOwner::Resolved,
10548    )
10549}
10550
10551fn initializer_list_owner(
10552    visibility: &VisibilityIndex<'_>,
10553    file: &ProjectFile,
10554    source: &str,
10555    initializer: Node<'_>,
10556) -> Option<CodeUnit> {
10557    let mut current = initializer;
10558    let mut outer_initializer_lists = 0usize;
10559    loop {
10560        let parent = current.parent()?;
10561        match parent.kind() {
10562            "initializer_pair" => return None,
10563            "initializer_list" => {
10564                outer_initializer_lists += 1;
10565                if outer_initializer_lists > 1 {
10566                    return None;
10567                }
10568                current = parent;
10569            }
10570            "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
10571                let declaration = parent.parent()?;
10572                if outer_initializer_lists == 1
10573                    && !parent
10574                        .child_by_field_name("declarator")
10575                        .is_some_and(contains_array_declarator)
10576                {
10577                    return None;
10578                }
10579                return declaration_owner(visibility, file, source, declaration);
10580            }
10581            "compound_literal_expression"
10582                if parent.child_by_field_name("value") == Some(current)
10583                    && outer_initializer_lists == 0 =>
10584            {
10585                let type_node = parent.child_by_field_name("type")?;
10586                return resolve_designated_owner_type(visibility, file, source, type_node);
10587            }
10588            "ERROR" => current = parent,
10589            _ => return None,
10590        }
10591    }
10592}
10593
10594fn declaration_owner(
10595    visibility: &VisibilityIndex<'_>,
10596    file: &ProjectFile,
10597    source: &str,
10598    declaration: Node<'_>,
10599) -> Option<CodeUnit> {
10600    if !matches!(declaration.kind(), "declaration" | "field_declaration") {
10601        return None;
10602    }
10603    let type_node = declaration
10604        .child_by_field_name("type")
10605        .or_else(|| first_type_child(declaration))?;
10606    resolve_designated_owner_type(visibility, file, source, type_node)
10607}
10608
10609fn resolve_designated_owner_type(
10610    visibility: &VisibilityIndex<'_>,
10611    file: &ProjectFile,
10612    source: &str,
10613    type_node: Node<'_>,
10614) -> Option<CodeUnit> {
10615    let type_name = normalize_type_text(node_text(type_node, source));
10616    visibility
10617        .resolve_type(file, &type_name)
10618        .filter(CodeUnit::is_class)
10619}
10620
10621fn contains_array_declarator(declarator: Node<'_>) -> bool {
10622    let mut stack = vec![declarator];
10623    while let Some(node) = stack.pop() {
10624        if node.kind() == "array_declarator" {
10625            return true;
10626        }
10627        if matches!(node.kind(), "initializer_list" | "compound_statement") {
10628            continue;
10629        }
10630        let mut cursor = node.walk();
10631        stack.extend(node.named_children(&mut cursor));
10632    }
10633    false
10634}
10635
10636pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
10637    let mut cursor = node.walk();
10638    node.named_children(&mut cursor).find(|child| {
10639        matches!(
10640            child.kind(),
10641            "type_identifier"
10642                | "primitive_type"
10643                | "qualified_identifier"
10644                | "scoped_type_identifier"
10645                | "struct_specifier"
10646                | "union_specifier"
10647                | "enum_specifier"
10648        )
10649    })
10650}
10651
10652pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
10653    visibility: &VisibilityIndex<'_>,
10654    file: &ProjectFile,
10655    source: &str,
10656    declarator: Node<'_>,
10657    type_text: Option<&str>,
10658    bindings: &LocalInferenceEngine<T>,
10659) -> bool {
10660    if !has_ancestor_kind(declarator, "compound_statement") {
10661        return false;
10662    }
10663    if declarator
10664        .child_by_field_name("declarator")
10665        .is_none_or(|declarator| declarator.kind() != "identifier")
10666    {
10667        return false;
10668    }
10669    if !type_text
10670        .and_then(|text| visibility.resolve_type(file, text))
10671        .is_some_and(|unit| unit.is_class())
10672    {
10673        return false;
10674    }
10675    declarator
10676        .child_by_field_name("parameters")
10677        .is_some_and(|parameters| {
10678            constructor_parameters_look_like_expressions(parameters, source, bindings)
10679        })
10680}
10681
10682fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
10683    parameters: Node<'_>,
10684    source: &str,
10685    bindings: &LocalInferenceEngine<T>,
10686) -> bool {
10687    let mut cursor = parameters.walk();
10688    parameters.named_children(&mut cursor).any(|parameter| {
10689        !matches!(
10690            parameter.kind(),
10691            "parameter_declaration" | "optional_parameter_declaration"
10692        ) || parameter_declaration_is_local_expression(parameter, source, bindings)
10693    })
10694}
10695
10696fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
10697    parameter: Node<'_>,
10698    source: &str,
10699    bindings: &LocalInferenceEngine<T>,
10700) -> bool {
10701    let text = node_text(parameter, source).trim();
10702    if text
10703        .chars()
10704        .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
10705        && bindings.is_shadowed(text)
10706    {
10707        return true;
10708    }
10709
10710    let Some(base) = parameter
10711        .child_by_field_name("type")
10712        .filter(|base| base.kind() == "type_identifier")
10713    else {
10714        return false;
10715    };
10716    let Some(subscript) = parameter
10717        .child_by_field_name("declarator")
10718        .filter(|declarator| declarator.kind() == "abstract_array_declarator")
10719    else {
10720        return false;
10721    };
10722    subscript.child_by_field_name("size").is_some()
10723        && bindings.is_shadowed(node_text(base, source).trim())
10724}
10725
10726pub fn is_declaration_name(node: Node<'_>) -> bool {
10727    let Some(parent) = node.parent() else {
10728        return false;
10729    };
10730    if parent
10731        .child_by_field_name("name")
10732        .is_some_and(|name| same_node(name, node))
10733    {
10734        if matches!(
10735            parent.kind(),
10736            "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
10737        ) {
10738            return cpp_tag_specifier_declares_name(parent);
10739        }
10740        if matches!(
10741            parent.kind(),
10742            "namespace_definition"
10743                | "namespace_alias_definition"
10744                | "alias_declaration"
10745                | "enumerator"
10746        ) {
10747            return true;
10748        }
10749    }
10750
10751    let mut current = Some(parent);
10752    while let Some(ancestor) = current {
10753        let type_definition = ancestor.kind() == "type_definition";
10754        let mut declarator_cursor = ancestor.walk();
10755        if ancestor
10756            .children_by_field_name("declarator", &mut declarator_cursor)
10757            .any(|declarator| declarator_name_path_contains(declarator, node, type_definition))
10758        {
10759            return true;
10760        }
10761        if matches!(
10762            ancestor.kind(),
10763            "declaration"
10764                | "field_declaration"
10765                | "parameter_declaration"
10766                | "optional_parameter_declaration"
10767                | "function_definition"
10768                | "type_definition"
10769                | "alias_declaration"
10770                | "class_specifier"
10771                | "struct_specifier"
10772                | "union_specifier"
10773                | "enum_specifier"
10774        ) {
10775            return false;
10776        }
10777        current = ancestor.parent();
10778    }
10779    false
10780}
10781
10782/// Whether tree-sitter recovered a qualified friend-class type as an ordinary
10783/// declaration's declarator inside a malformed class body.
10784///
10785/// An export macro between `class` and the class name can make the containing
10786/// body parse as a function body. A source declaration such as
10787/// `friend class internal::Friend;` then retains this exact structure:
10788/// `declaration(type: friend, ERROR(class), declarator: internal::Friend)`.
10789/// The declarator is a type reference despite its field role.
10790pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
10791    if !matches!(
10792        node.kind(),
10793        "qualified_identifier" | "scoped_type_identifier"
10794    ) {
10795        return false;
10796    }
10797    let Some(declaration) = node
10798        .parent()
10799        .filter(|parent| parent.kind() == "declaration")
10800    else {
10801        return false;
10802    };
10803    if declaration.child_by_field_name("declarator") != Some(node)
10804        || !declaration
10805            .child_by_field_name("type")
10806            .is_some_and(|friend| {
10807                friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
10808            })
10809    {
10810        return false;
10811    }
10812    let mut cursor = declaration.walk();
10813    let mut errors = declaration
10814        .named_children(&mut cursor)
10815        .filter(|child| child.kind() == "ERROR");
10816    let Some(error) = errors.next() else {
10817        return false;
10818    };
10819    errors.next().is_none()
10820        && error.named_child_count() == 1
10821        && error.named_child(0).is_some_and(|class| {
10822            class.kind() == "identifier" && node_text(class, source) == "class"
10823        })
10824}
10825
10826pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
10827    if !matches!(node.kind(), "identifier" | "field_identifier") || is_declaration_name(node) {
10828        return false;
10829    }
10830    if let Some(parent) = node.parent() {
10831        if parent.kind() == "call_expression"
10832            && parent.child_by_field_name("function") == Some(node)
10833        {
10834            return false;
10835        }
10836        if matches!(parent.kind(), "labeled_statement" | "goto_statement")
10837            && parent.child_by_field_name("label") == Some(node)
10838        {
10839            return false;
10840        }
10841    }
10842    let mut current = node.parent();
10843    while let Some(ancestor) = current {
10844        if ancestor.kind().starts_with("preproc_") {
10845            return false;
10846        }
10847        if matches!(
10848            ancestor.kind(),
10849            "translation_unit" | "function_definition" | "compound_statement"
10850        ) {
10851            break;
10852        }
10853        current = ancestor.parent();
10854    }
10855    true
10856}
10857
10858fn recovered_c_reference_node(
10859    visibility: &VisibilityIndex<'_>,
10860    file: &ProjectFile,
10861    node: Node<'_>,
10862    source: &str,
10863) -> bool {
10864    if node.start_byte() >= node.end_byte()
10865        || node.is_error()
10866        || node.is_missing()
10867        || !matches!(
10868            node.kind(),
10869            "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
10870        )
10871        || recovered_c_macro_binding_role(node)
10872        || recovered_c_label_role(node)
10873    {
10874        return false;
10875    }
10876
10877    let name = node_text(node, source);
10878    if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
10879        return true;
10880    }
10881    if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
10882        return true;
10883    }
10884    if is_declaration_name(node) {
10885        return false;
10886    }
10887    if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
10888        return true;
10889    }
10890    recovered_c_reference_anchor(node)
10891}
10892
10893fn recovered_c_explicit_assignment_callee(
10894    visibility: &VisibilityIndex<'_>,
10895    file: &ProjectFile,
10896    node: Node<'_>,
10897    name: &str,
10898) -> bool {
10899    let mut current = node;
10900    let error = loop {
10901        let Some(parent) = current.parent() else {
10902            return false;
10903        };
10904        if parent.is_error() {
10905            break parent;
10906        }
10907        current = parent;
10908    };
10909    let mut cursor = error.walk();
10910    let explicit_recovery_precedes_callee = error
10911        .named_children(&mut cursor)
10912        .take_while(|child| child.start_byte() < node.start_byte())
10913        .any(|child| child.kind() == "explicit_function_specifier");
10914    if !explicit_recovery_precedes_callee {
10915        return false;
10916    }
10917    visibility
10918        .cpp
10919        .declarations(file)
10920        .iter()
10921        .chain(visibility.visible_by_file.get(file).into_iter().flatten())
10922        .any(|candidate| candidate.identifier() == name && candidate.is_function())
10923}
10924
10925fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
10926    while let Some(parent) = node.parent() {
10927        if matches!(
10928            parent.kind(),
10929            "preproc_def" | "preproc_function_def" | "preproc_params"
10930        ) {
10931            return true;
10932        }
10933        if parent.is_error()
10934            || matches!(
10935                parent.kind(),
10936                "translation_unit" | "function_definition" | "compound_statement"
10937            )
10938        {
10939            return false;
10940        }
10941        node = parent;
10942    }
10943    false
10944}
10945
10946fn recovered_c_label_role(node: Node<'_>) -> bool {
10947    node.parent().is_some_and(|parent| {
10948        matches!(parent.kind(), "labeled_statement" | "goto_statement")
10949            && parent.child_by_field_name("label") == Some(node)
10950    })
10951}
10952
10953fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
10954    while let Some(parent) = node.parent() {
10955        if parent.is_error() {
10956            return false;
10957        }
10958        if parent.kind().ends_with("_expression")
10959            || matches!(
10960                parent.kind(),
10961                "argument_list"
10962                    | "return_statement"
10963                    | "expression_statement"
10964                    | "case_statement"
10965                    | "initializer_list"
10966                    | "init_declarator"
10967                    | "array_declarator"
10968                    | "field_designator"
10969                    | "enumerator"
10970            )
10971        {
10972            return true;
10973        }
10974        if matches!(
10975            parent.kind(),
10976            "translation_unit"
10977                | "function_definition"
10978                | "compound_statement"
10979                | "declaration"
10980                | "field_declaration"
10981                | "parameter_declaration"
10982        ) {
10983            return false;
10984        }
10985        node = parent;
10986    }
10987    false
10988}
10989
10990/// Whether a parameter declaration belongs to the callable scope whose body can
10991/// contain references to it.
10992///
10993/// Error recovery can wrap a macro-decorated class body in a synthetic outer
10994/// `function_definition`. Merely finding any callable ancestor would then leak
10995/// parameters from member prototypes into later member bodies. Require the
10996/// parameter to be inside that definition's own declarator instead.
10997pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
10998    let mut current = parameter.parent();
10999    while let Some(ancestor) = current {
11000        if ancestor.kind() == "lambda_expression" {
11001            return ancestor
11002                .child_by_field_name("declarator")
11003                .is_some_and(|declarator| {
11004                    declarator.start_byte() <= parameter.start_byte()
11005                        && parameter.end_byte() <= declarator.end_byte()
11006                });
11007        }
11008        if ancestor.kind() == "function_definition" {
11009            return ancestor
11010                .child_by_field_name("declarator")
11011                .is_some_and(|declarator| {
11012                    declarator.start_byte() <= parameter.start_byte()
11013                        && parameter.end_byte() <= declarator.end_byte()
11014                });
11015        }
11016        current = ancestor.parent();
11017    }
11018    false
11019}
11020
11021pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
11022    let mut current = node.parent();
11023    while let Some(ancestor) = current {
11024        if matches!(
11025            ancestor.kind(),
11026            "parameter_declaration" | "optional_parameter_declaration"
11027        ) {
11028            return ancestor
11029                .child_by_field_name("type")
11030                .is_some_and(|type_node| {
11031                    type_node.start_byte() <= node.start_byte()
11032                        && node.end_byte() <= type_node.end_byte()
11033                });
11034        }
11035        if matches!(
11036            ancestor.kind(),
11037            "function_definition" | "lambda_expression" | "compound_statement"
11038        ) {
11039            return false;
11040        }
11041        current = ancestor.parent();
11042    }
11043    false
11044}
11045
11046fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
11047    if specifier.child_by_field_name("body").is_some() {
11048        return true;
11049    }
11050    let mut current = specifier.parent();
11051    while let Some(ancestor) = current {
11052        match ancestor.kind() {
11053            "type_descriptor"
11054            | "parameter_declaration"
11055            | "optional_parameter_declaration"
11056            | "template_argument_list"
11057            | "cast_expression" => return false,
11058            "declaration" | "field_declaration" => {
11059                let mut cursor = ancestor.walk();
11060                return ancestor
11061                    .children_by_field_name("declarator", &mut cursor)
11062                    .next()
11063                    .is_none();
11064            }
11065            "translation_unit" => return true,
11066            _ => current = ancestor.parent(),
11067        }
11068    }
11069    false
11070}
11071
11072pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
11073    match node.kind() {
11074        "identifier"
11075        | "field_identifier"
11076        | "qualified_identifier"
11077        | "scoped_identifier"
11078        | "operator_name"
11079        | "destructor_name"
11080        | "literal_operator_name" => Some(node),
11081        "reference_declarator" | "parenthesized_declarator" => {
11082            node.named_child(0).and_then(declarator_name_node)
11083        }
11084        _ => node
11085            .child_by_field_name("declarator")
11086            .or_else(|| node.child_by_field_name("name"))
11087            .or_else(|| node.child_by_field_name("field"))
11088            .and_then(declarator_name_node),
11089    }
11090}
11091
11092fn declarator_name_path_contains(
11093    declarator: Node<'_>,
11094    candidate: Node<'_>,
11095    allow_type_identifier: bool,
11096) -> bool {
11097    let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
11098        return false;
11099    };
11100    let mut current = Some(declarator);
11101    while let Some(node) = current {
11102        if same_node(node, candidate) {
11103            return true;
11104        }
11105        if same_node(node, name) {
11106            return false;
11107        }
11108        current = node
11109            .child_by_field_name("declarator")
11110            .or_else(|| node.child_by_field_name("name"))
11111            .or_else(|| node.child_by_field_name("field"));
11112    }
11113    false
11114}
11115
11116fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
11117    match node.kind() {
11118        "identifier"
11119        | "field_identifier"
11120        | "operator_name"
11121        | "destructor_name"
11122        | "literal_operator_name" => Some(node),
11123        "type_identifier" if allow_type_identifier => Some(node),
11124        _ => node
11125            .child_by_field_name("declarator")
11126            .or_else(|| node.child_by_field_name("name"))
11127            .or_else(|| node.child_by_field_name("field"))
11128            .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
11129    }
11130}
11131
11132/// True when `node` is a component of a larger structured type node whose outer
11133/// range is the single reference surfaced to callers.
11134pub fn is_nested_type_node(node: Node<'_>) -> bool {
11135    node.parent().is_some_and(|parent| {
11136        matches!(
11137            parent.kind(),
11138            "qualified_identifier" | "scoped_type_identifier" | "template_type"
11139        )
11140    })
11141}
11142
11143pub struct OutOfLineMemberDefinitionOwners<'tree> {
11144    pub owners: Vec<(Node<'tree>, CodeUnit)>,
11145    innermost: Option<(Node<'tree>, CodeUnit)>,
11146}
11147
11148impl OutOfLineMemberDefinitionOwners<'_> {
11149    pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
11150        self.innermost.as_ref().map(|(node, owner)| (*node, owner))
11151    }
11152}
11153
11154pub struct QualifiedOwnerComponents<'tree> {
11155    pub nodes: Vec<Node<'tree>>,
11156    pub names: Vec<String>,
11157    pub global: bool,
11158}
11159
11160/// True when each structured qualifier on the callable-name path has a real
11161/// `::` token. A macro-prefixed return type can make tree-sitter insert a
11162/// zero-width missing separator and parse `TYPE Result<T> method()` as the
11163/// false qualified declarator `Result<T>::method`.
11164pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
11165    let mut stack = vec![node];
11166    let mut found_separator = false;
11167    while let Some(current) = stack.pop() {
11168        if !matches!(
11169            current.kind(),
11170            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
11171        ) {
11172            continue;
11173        }
11174        let mut current_has_separator = false;
11175        for index in 0..current.child_count() {
11176            let Some(child) = current.child(index) else {
11177                continue;
11178            };
11179            if child.kind() == "::" {
11180                if child.is_missing() {
11181                    return false;
11182                }
11183                current_has_separator = true;
11184                found_separator = true;
11185            }
11186        }
11187        if !current_has_separator {
11188            return false;
11189        }
11190        for field in ["scope", "name"] {
11191            if let Some(child) = current.child_by_field_name(field)
11192                && matches!(
11193                    child.kind(),
11194                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
11195                )
11196            {
11197                stack.push(child);
11198            }
11199        }
11200    }
11201    found_separator
11202}
11203
11204pub fn qualified_owner_components<'tree>(
11205    node: Node<'tree>,
11206    source: &str,
11207) -> Option<QualifiedOwnerComponents<'tree>> {
11208    if !qualified_name_has_concrete_scope_separators(node) {
11209        return None;
11210    }
11211    let mut nodes = cpp_name_component_nodes(node)?;
11212    nodes.pop()?;
11213    if nodes.is_empty() {
11214        return None;
11215    }
11216    let names = nodes
11217        .iter()
11218        .map(|component| node_text(*component, source).to_string())
11219        .collect();
11220    Some(QualifiedOwnerComponents {
11221        nodes,
11222        names,
11223        global: is_globally_qualified_cpp_name(node),
11224    })
11225}
11226
11227pub fn out_of_line_member_definition_owner<'tree>(
11228    analyzer: &CppGraphSource<'_>,
11229    visibility: &VisibilityIndex<'_>,
11230    file: &ProjectFile,
11231    source: &str,
11232    node: Node<'tree>,
11233) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
11234    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
11235        || !has_ancestor_kind(node, "function_definition")
11236        || !is_function_declarator_name_root(node)
11237    {
11238        return None;
11239    }
11240    let qualified = qualified_owner_components(node, source)?;
11241    let lexical_scope = enclosing_namespace_components(node, source)?;
11242    let mut owners = Vec::new();
11243    let mut innermost = None;
11244
11245    for component_count in 1..=qualified.names.len() {
11246        if let LexicalTypeResolution::Resolved { unit, .. } = visibility
11247            .resolve_type_components_lexically(
11248                analyzer,
11249                file,
11250                &qualified.names[..component_count],
11251                qualified.global,
11252                &lexical_scope,
11253            )
11254            && !owners
11255                .iter()
11256                .any(|(_, existing)| same_visible_symbol(existing, &unit))
11257        {
11258            if component_count == qualified.names.len() {
11259                innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
11260            }
11261            owners.push((qualified.nodes[component_count - 1], unit));
11262        }
11263    }
11264
11265    // The C++ analyzer has already reconciled an indexed out-of-line callable
11266    // against the include-visible class table. Consult that canonical owner
11267    // chain only when ordinary lexical lookup could not recover the innermost
11268    // owner.  A one-segment qualifier is safe here only when the enclosing
11269    // indexed callable has an authoritative class owner and the parser's
11270    // namespace path is a (possibly sparse) subsequence of that owner path.
11271    // The latter is what lets macro-wrapped namespace sentinels recover a
11272    // missing `time_internal`/`cord_internal` component without guessing an
11273    // unrelated short name.
11274    if innermost.is_none() {
11275        let indexed_owner_components = visibility
11276            .indexed_enclosing_owner_scope(analyzer, file, node)
11277            .or_else(|| {
11278                // Retain the legacy rendered-name fallback for the existing
11279                // multi-segment path when an enclosing owner chain is not
11280                // available (for example, cache-loaded units without parent
11281                // links).  One-segment recovery must stay canonical-only.
11282                if qualified.names.len() <= 1 {
11283                    return None;
11284                }
11285                let range = Range {
11286                    start_byte: node.start_byte(),
11287                    end_byte: node.end_byte(),
11288                    start_line: node.start_position().row,
11289                    end_line: node.end_position().row,
11290                };
11291                let start = analyzer.enclosing_code_unit(file, &range)?;
11292                let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11293                    brokk_bifrost_core::analyzer::Language::Cpp,
11294                    &cpp_name_for(&start),
11295                );
11296                components.pop();
11297                Some(components)
11298            });
11299        if let Some(indexed_owner_components) = indexed_owner_components
11300            && indexed_owner_components.len() > qualified.names.len()
11301            && indexed_owner_components.ends_with(&qualified.names)
11302            && indexed_namespace_path_is_recoverable(
11303                &lexical_scope,
11304                &indexed_owner_components,
11305                qualified.names.len(),
11306            )
11307            // A globally-qualified one-segment owner is an explicit request
11308            // for the top-level binding; do not reinterpret it as a missing
11309            // namespace component.  Existing multi-segment global lookups
11310            // retain their historical indexed recovery.
11311            && (qualified.names.len() > 1 || !qualified.global)
11312        {
11313            let namespace_count = indexed_owner_components.len() - qualified.names.len();
11314            for component_count in 1..=qualified.names.len() {
11315                let expected = &indexed_owner_components[..namespace_count + component_count];
11316                let owner_node = qualified.nodes[component_count - 1];
11317                for owner in visibility
11318                    .visible_identifier_candidates(file, &qualified.names[component_count - 1])
11319                    .filter(|candidate| candidate.is_class())
11320                    .filter(|candidate| {
11321                        canonical_cpp_scope_components(candidate) == expected
11322                            && visibility.external_type_candidate_visible_in_context(
11323                                analyzer, file, candidate, node,
11324                            )
11325                    })
11326                {
11327                    if component_count == qualified.names.len() && innermost.is_none() {
11328                        innermost = Some((owner_node, owner.clone()));
11329                    }
11330                    if !owners
11331                        .iter()
11332                        .any(|(_, existing)| same_symbol(existing, owner))
11333                    {
11334                        owners.push((owner_node, owner.clone()));
11335                    }
11336                }
11337            }
11338        }
11339    }
11340    (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
11341}
11342
11343fn is_function_declarator_name_root(node: Node<'_>) -> bool {
11344    let mut current = node;
11345    while let Some(parent) = current.parent() {
11346        if parent.kind() == "function_declarator" {
11347            return parent.child_by_field_name("declarator") == Some(current);
11348        }
11349        if matches!(
11350            parent.kind(),
11351            "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
11352        ) && parent.child_by_field_name("declarator") == Some(current)
11353        {
11354            current = parent;
11355            continue;
11356        }
11357        return false;
11358    }
11359    false
11360}
11361
11362pub fn append_cpp_name_components(
11363    node: Node<'_>,
11364    source: &str,
11365    out: &mut Vec<String>,
11366) -> Option<()> {
11367    out.extend(
11368        cpp_name_component_nodes(node)?
11369            .into_iter()
11370            .map(|component| node_text(component, source).to_string()),
11371    );
11372    Some(())
11373}
11374
11375pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
11376    let mut components = Vec::new();
11377    append_cpp_name_components(node, source, &mut components)?;
11378    Some(components)
11379}
11380
11381/// Resolve a structured type spelling from an object-like macro replacement
11382/// when definition-site source order has no answer.
11383///
11384/// Macro replacement tokens are looked up where the macro is expanded, so a
11385/// type declared later in the defining header can still be their destination.
11386/// Without expanding every invocation, accept only one include-visible logical
11387/// class or alias whose structured path ends in the replacement components.
11388/// An ordinary lexical answer always takes precedence at the call site.
11389pub fn unique_macro_replacement_type_candidate(
11390    analyzer: &CppGraphSource<'_>,
11391    visibility: &VisibilityIndex<'_>,
11392    file: &ProjectFile,
11393    components: &[String],
11394) -> Option<CodeUnit> {
11395    let terminal = components.last()?;
11396    let mut candidates = Vec::new();
11397    for candidate in visibility
11398        .visible_identifier_candidates(file, terminal)
11399        .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
11400        .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
11401    {
11402        if !candidates
11403            .iter()
11404            .any(|existing| same_logical_symbol(existing, candidate))
11405        {
11406            candidates.push(candidate.clone());
11407        }
11408    }
11409    (candidates.len() == 1).then(|| candidates.remove(0))
11410}
11411
11412/// The base scopes named by member using-declarations for `member` in one
11413/// class source range.
11414///
11415/// The grammar supplies the qualified identifier and each component. Keep
11416/// this interpretation shared between forward overload lookup and inverse
11417/// owner routing rather than reparsing a rendered `Base::member` string at
11418/// either call site.
11419pub fn cpp_member_using_declaration_scopes(source: &str, member: &str) -> Vec<String> {
11420    let mut parser = Parser::new();
11421    if parser
11422        .set_language(&tree_sitter_cpp::LANGUAGE.into())
11423        .is_err()
11424    {
11425        return Vec::new();
11426    }
11427    let Some(tree) = parser.parse(source, None) else {
11428        return Vec::new();
11429    };
11430    let mut scopes = Vec::new();
11431    let mut pending = vec![tree.root_node()];
11432    while let Some(node) = pending.pop() {
11433        if node.kind() == "using_declaration" {
11434            let Some(imported) = node.named_child(0) else {
11435                continue;
11436            };
11437            let Some(mut components) = cpp_type_name_components(imported, source) else {
11438                continue;
11439            };
11440            if components.pop().as_deref() == Some(member) && !components.is_empty() {
11441                scopes.push(components.join("::"));
11442            }
11443            continue;
11444        }
11445        for index in (0..node.named_child_count()).rev() {
11446            if let Some(child) = node.named_child(index) {
11447                pending.push(child);
11448            }
11449        }
11450    }
11451    scopes
11452}
11453
11454/// Whether a structured using-declaration scope can name `qualified` as an
11455/// ancestor class. The boundary check prevents `Base` from matching
11456/// `OtherBase` while allowing a relative `Base` spelling to match `ns::Base`.
11457pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
11458    qualified == scope
11459        || qualified
11460            .strip_suffix(scope)
11461            .is_some_and(|prefix| prefix.ends_with("::"))
11462}
11463
11464/// Whether `node` is the direct structured type payload of a template
11465/// argument. This role remains meaningful even when a surrounding expression
11466/// is below tree-sitter recovery, because both the `template_argument_list`
11467/// and the `type_descriptor` retain their named fields.
11468pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
11469    let Some(type_descriptor) = node.parent() else {
11470        return false;
11471    };
11472    if type_descriptor.kind() != "type_descriptor"
11473        || type_descriptor.child_by_field_name("type") != Some(node)
11474    {
11475        return false;
11476    }
11477    let Some(arguments) = type_descriptor.parent() else {
11478        return false;
11479    };
11480    if arguments.kind() != "template_argument_list" {
11481        return false;
11482    }
11483    arguments.parent().is_some_and(|parent| {
11484        matches!(parent.kind(), "template_type" | "template_function")
11485            && parent.child_by_field_name("arguments") == Some(arguments)
11486    })
11487}
11488
11489pub fn cpp_template_reference_arguments(
11490    mut node: Node<'_>,
11491    source: &str,
11492) -> Option<Vec<CppTemplateExpression>> {
11493    loop {
11494        match node.kind() {
11495            "template_type" | "template_function" => {
11496                let arguments = node.child_by_field_name("arguments")?;
11497                let mut cursor = arguments.walk();
11498                return Some(
11499                    arguments
11500                        .named_children(&mut cursor)
11501                        .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
11502                        .map(|argument| CppTemplateExpression {
11503                            text: normalize_cpp_whitespace(node_text(argument, source)),
11504                            // One template term from a resolver query; see `ParentIndex::unindexed`.
11505                            term: cpp_template_term(
11506                                argument,
11507                                source,
11508                                &[],
11509                                &ParentIndex::unindexed(),
11510                            ),
11511                        })
11512                        .collect(),
11513                );
11514            }
11515            "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
11516                node = node
11517                    .child_by_field_name("name")
11518                    .or_else(|| node.child_by_field_name("type"))?;
11519            }
11520            _ => return None,
11521        }
11522    }
11523}
11524
11525fn cpp_reconcile_primary_template_parameters(
11526    candidates: &[(&CodeUnit, &CppTemplateMetadata)],
11527    preferred: &CodeUnit,
11528) -> Option<Vec<CppTemplateParameterMetadata>> {
11529    let canonical = candidates
11530        .iter()
11531        .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
11532    let mut merged = canonical
11533        .parameters
11534        .iter()
11535        .map(|parameter| CppTemplateParameterMetadata {
11536            name: parameter.name.clone(),
11537            kind: parameter.kind,
11538            variadic: parameter.variadic,
11539            default: None,
11540        })
11541        .collect::<Vec<_>>();
11542
11543    for (_, metadata) in candidates {
11544        if metadata.parameters.len() != merged.len() {
11545            return None;
11546        }
11547        let rename_bindings = metadata
11548            .parameters
11549            .iter()
11550            .zip(&merged)
11551            .map(|(parameter, canonical)| {
11552                (
11553                    parameter.name.clone(),
11554                    CppTemplateTerm::Parameter(canonical.name.clone()),
11555                )
11556            })
11557            .collect::<HashMap<_, _>>();
11558        for ((parameter, canonical), merged_parameter) in metadata
11559            .parameters
11560            .iter()
11561            .zip(&canonical.parameters)
11562            .zip(&mut merged)
11563        {
11564            if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
11565                return None;
11566            }
11567            let Some(default) = &parameter.default else {
11568                continue;
11569            };
11570            let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
11571            if let Some(existing) = &merged_parameter.default {
11572                if !cpp_template_terms_equal(&existing.term, &normalized_term) {
11573                    return None;
11574                }
11575            } else {
11576                merged_parameter.default = Some(CppTemplateExpression {
11577                    text: default.text.clone(),
11578                    term: normalized_term,
11579                });
11580            }
11581        }
11582    }
11583    Some(merged)
11584}
11585
11586pub fn cpp_bind_template_arguments(
11587    parameters: &[CppTemplateParameterMetadata],
11588    explicit_arguments: &[CppTemplateExpression],
11589) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
11590    let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
11591    if variadic_index.is_some_and(|index| {
11592        index + 1 != parameters.len()
11593            || parameters[index + 1..]
11594                .iter()
11595                .any(|parameter| parameter.variadic)
11596    }) {
11597        return None;
11598    }
11599    let fixed_count = variadic_index.unwrap_or(parameters.len());
11600    if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
11601        return None;
11602    }
11603    let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
11604    let mut expanded = explicit_arguments[..explicit_fixed_count]
11605        .iter()
11606        .map(cpp_clone_template_expression_iterative)
11607        .collect::<Vec<_>>();
11608    let mut bindings = HashMap::default();
11609    for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
11610        bindings.insert(
11611            parameter.name.clone(),
11612            cpp_clone_template_term_iterative(&argument.term),
11613        );
11614    }
11615    for parameter in &parameters[explicit_fixed_count..fixed_count] {
11616        let default = parameter.default.as_ref()?;
11617        let term = cpp_substitute_template_term(&default.term, &bindings)?;
11618        bindings.insert(parameter.name.clone(), term.clone());
11619        expanded.push(CppTemplateExpression {
11620            text: default.text.clone(),
11621            term,
11622        });
11623    }
11624    if let Some(index) = variadic_index {
11625        let packed_arguments = &explicit_arguments[explicit_fixed_count..];
11626        expanded.extend(
11627            packed_arguments
11628                .iter()
11629                .map(cpp_clone_template_expression_iterative),
11630        );
11631        bindings.insert(
11632            parameters[index].name.clone(),
11633            CppTemplateTerm::Node {
11634                kind: "parameter_pack".to_string(),
11635                children: packed_arguments
11636                    .iter()
11637                    .map(|argument| cpp_clone_template_term_iterative(&argument.term))
11638                    .collect(),
11639            },
11640        );
11641    }
11642    Some((expanded, bindings))
11643}
11644
11645fn cpp_specialization_matches(
11646    metadata: &CppTemplateMetadata,
11647    arguments: &[CppTemplateExpression],
11648) -> bool {
11649    if metadata.specialization_arguments.len() != arguments.len() {
11650        return false;
11651    }
11652    let parameter_names = metadata
11653        .parameters
11654        .iter()
11655        .map(|parameter| parameter.name.as_str())
11656        .collect::<HashSet<_>>();
11657    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
11658    for (pattern, argument) in metadata.specialization_arguments.iter().zip(arguments) {
11659        if !cpp_unify_template_term(
11660            &pattern.term,
11661            &argument.term,
11662            &parameter_names,
11663            &mut bindings,
11664        ) {
11665            return false;
11666        }
11667    }
11668    true
11669}
11670
11671fn cpp_specialization_more_specialized(
11672    candidate: &CppTemplateMetadata,
11673    other: &CppTemplateMetadata,
11674) -> bool {
11675    cpp_specialization_pattern_accepts(other, candidate)
11676        && !cpp_specialization_pattern_accepts(candidate, other)
11677}
11678
11679fn cpp_specialization_pattern_accepts(
11680    broader: &CppTemplateMetadata,
11681    narrower: &CppTemplateMetadata,
11682) -> bool {
11683    if broader.specialization_arguments.len() != narrower.specialization_arguments.len() {
11684        return false;
11685    }
11686    let parameter_names = broader
11687        .parameters
11688        .iter()
11689        .map(|parameter| parameter.name.as_str())
11690        .collect::<HashSet<_>>();
11691    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
11692    broader
11693        .specialization_arguments
11694        .iter()
11695        .zip(&narrower.specialization_arguments)
11696        .all(|(pattern, argument)| {
11697            cpp_unify_template_term(
11698                &pattern.term,
11699                &argument.term,
11700                &parameter_names,
11701                &mut bindings,
11702            )
11703        })
11704}
11705
11706pub fn cpp_substitute_template_term(
11707    term: &CppTemplateTerm,
11708    bindings: &HashMap<String, CppTemplateTerm>,
11709) -> Option<CppTemplateTerm> {
11710    enum Work<'a> {
11711        Visit(&'a CppTemplateTerm),
11712        Build { kind: String, child_count: usize },
11713    }
11714
11715    let mut work = vec![Work::Visit(term)];
11716    let mut substituted = Vec::new();
11717    while let Some(next) = work.pop() {
11718        match next {
11719            Work::Visit(CppTemplateTerm::Parameter(name)) => {
11720                substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
11721            }
11722            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
11723                substituted.push(CppTemplateTerm::Atom {
11724                    kind: kind.clone(),
11725                    text: text.clone(),
11726                });
11727            }
11728            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
11729                work.push(Work::Build {
11730                    kind: kind.clone(),
11731                    child_count: children.len(),
11732                });
11733                work.extend(children.iter().rev().map(Work::Visit));
11734            }
11735            Work::Build { kind, child_count } => {
11736                let children = substituted.split_off(substituted.len() - child_count);
11737                substituted.push(CppTemplateTerm::Node { kind, children });
11738            }
11739        }
11740    }
11741    substituted.pop()
11742}
11743
11744pub fn cpp_substitute_template_arguments(
11745    arguments: &[CppTemplateExpression],
11746    bindings: &HashMap<String, CppTemplateTerm>,
11747) -> Option<Vec<CppTemplateExpression>> {
11748    let mut substituted = Vec::new();
11749    for argument in arguments {
11750        let CppTemplateTerm::Node { kind, children } = &argument.term else {
11751            substituted.push(CppTemplateExpression {
11752                text: argument.text.clone(),
11753                term: cpp_substitute_template_term(&argument.term, bindings)?,
11754            });
11755            continue;
11756        };
11757        if kind != "parameter_pack_expansion" {
11758            substituted.push(CppTemplateExpression {
11759                text: argument.text.clone(),
11760                term: cpp_substitute_template_term(&argument.term, bindings)?,
11761            });
11762            continue;
11763        }
11764        let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
11765            return None;
11766        };
11767        if ellipsis != "..." {
11768            return None;
11769        }
11770
11771        let mut pack_names = Vec::new();
11772        let mut work = vec![pattern];
11773        while let Some(term) = work.pop() {
11774            match term {
11775                CppTemplateTerm::Parameter(name)
11776                    if matches!(
11777                        bindings.get(name),
11778                        Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
11779                    ) =>
11780                {
11781                    if !pack_names.contains(name) {
11782                        pack_names.push(name.clone());
11783                    }
11784                }
11785                CppTemplateTerm::Node { children, .. } => work.extend(children),
11786                CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
11787            }
11788        }
11789        let first_pack = pack_names.first()?;
11790        let CppTemplateTerm::Node {
11791            children: first_elements,
11792            ..
11793        } = bindings.get(first_pack)?
11794        else {
11795            return None;
11796        };
11797        let pack_len = first_elements.len();
11798        for pack_name in &pack_names {
11799            let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
11800                return None;
11801            };
11802            if children.len() != pack_len {
11803                return None;
11804            }
11805        }
11806        for index in 0..pack_len {
11807            let mut element_bindings = bindings.clone();
11808            for pack_name in &pack_names {
11809                let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
11810                    return None;
11811                };
11812                element_bindings.insert(
11813                    pack_name.clone(),
11814                    cpp_clone_template_term_iterative(&children[index]),
11815                );
11816            }
11817            substituted.push(CppTemplateExpression {
11818                text: argument.text.clone(),
11819                term: cpp_substitute_template_term(pattern, &element_bindings)?,
11820            });
11821        }
11822    }
11823    Some(substituted)
11824}
11825
11826fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
11827    enum Work<'a> {
11828        Visit(&'a CppTemplateTerm),
11829        Build { kind: String, child_count: usize },
11830    }
11831
11832    let mut work = vec![Work::Visit(term)];
11833    let mut cloned = Vec::new();
11834    while let Some(next) = work.pop() {
11835        match next {
11836            Work::Visit(CppTemplateTerm::Parameter(name)) => {
11837                cloned.push(CppTemplateTerm::Parameter(name.clone()));
11838            }
11839            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
11840                cloned.push(CppTemplateTerm::Atom {
11841                    kind: kind.clone(),
11842                    text: text.clone(),
11843                });
11844            }
11845            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
11846                work.push(Work::Build {
11847                    kind: kind.clone(),
11848                    child_count: children.len(),
11849                });
11850                work.extend(children.iter().rev().map(Work::Visit));
11851            }
11852            Work::Build { kind, child_count } => {
11853                let children = cloned.split_off(cloned.len() - child_count);
11854                cloned.push(CppTemplateTerm::Node { kind, children });
11855            }
11856        }
11857    }
11858    cloned
11859        .pop()
11860        .expect("template term traversal emits one root")
11861}
11862
11863fn cpp_clone_template_expression_iterative(
11864    expression: &CppTemplateExpression,
11865) -> CppTemplateExpression {
11866    CppTemplateExpression {
11867        text: expression.text.clone(),
11868        term: cpp_clone_template_term_iterative(&expression.term),
11869    }
11870}
11871
11872pub fn cpp_unify_template_term(
11873    pattern: &CppTemplateTerm,
11874    argument: &CppTemplateTerm,
11875    parameters: &HashSet<&str>,
11876    bindings: &mut HashMap<String, CppTemplateTerm>,
11877) -> bool {
11878    let mut work = vec![(pattern, argument)];
11879    while let Some((pattern, argument)) = work.pop() {
11880        match pattern {
11881            CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
11882                if let Some(bound) = bindings.get(name) {
11883                    if !cpp_template_terms_equal(bound, argument) {
11884                        return false;
11885                    }
11886                } else {
11887                    bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
11888                }
11889            }
11890            CppTemplateTerm::Atom {
11891                kind: pattern_kind,
11892                text: pattern_text,
11893            } => {
11894                if !matches!(
11895                    argument,
11896                    CppTemplateTerm::Atom { kind, text }
11897                        if kind == pattern_kind && text == pattern_text
11898                ) {
11899                    return false;
11900                }
11901            }
11902            CppTemplateTerm::Node {
11903                kind: pattern_kind,
11904                children: pattern_children,
11905            } => {
11906                let CppTemplateTerm::Node { kind, children } = argument else {
11907                    return false;
11908                };
11909                if kind != pattern_kind || children.len() != pattern_children.len() {
11910                    return false;
11911                }
11912                work.extend(pattern_children.iter().zip(children).rev());
11913            }
11914            CppTemplateTerm::Parameter(_) => return false,
11915        }
11916    }
11917    true
11918}
11919
11920fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
11921    let mut work = vec![(left, right)];
11922    while let Some((left, right)) = work.pop() {
11923        match (left, right) {
11924            (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
11925                if left != right {
11926                    return false;
11927                }
11928            }
11929            (
11930                CppTemplateTerm::Atom {
11931                    kind: left_kind,
11932                    text: left_text,
11933                },
11934                CppTemplateTerm::Atom {
11935                    kind: right_kind,
11936                    text: right_text,
11937                },
11938            ) => {
11939                if left_kind != right_kind || left_text != right_text {
11940                    return false;
11941                }
11942            }
11943            (
11944                CppTemplateTerm::Node {
11945                    kind: left_kind,
11946                    children: left_children,
11947                },
11948                CppTemplateTerm::Node {
11949                    kind: right_kind,
11950                    children: right_children,
11951                },
11952            ) => {
11953                if left_kind != right_kind || left_children.len() != right_children.len() {
11954                    return false;
11955                }
11956                work.extend(left_children.iter().zip(right_children).rev());
11957            }
11958            _ => return false,
11959        }
11960    }
11961    true
11962}
11963
11964pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
11965    let mut components = Vec::new();
11966    let mut stack = vec![node];
11967    while let Some(current) = stack.pop() {
11968        match current.kind() {
11969            "identifier"
11970            | "field_identifier"
11971            | "namespace_identifier"
11972            | "type_identifier"
11973            | "operator_name"
11974            | "destructor_name" => components.push(current),
11975            "template_type" | "template_function" => {
11976                stack.push(current.child_by_field_name("name")?);
11977            }
11978            "dependent_name" => stack.push(current.named_child(0)?),
11979            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
11980                stack.push(current.child_by_field_name("name")?);
11981                if let Some(scope) = current.child_by_field_name("scope") {
11982                    stack.push(scope);
11983                }
11984            }
11985            "nested_namespace_specifier" => {
11986                for index in (0..current.named_child_count()).rev() {
11987                    stack.push(current.named_child(index)?);
11988                }
11989            }
11990            _ => return None,
11991        }
11992    }
11993    Some(components)
11994}
11995
11996pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
11997    node.child_by_field_name("scope").is_none()
11998        && node.child(0).is_some_and(|child| child.kind() == "::")
11999}
12000
12001fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
12002    let mut namespaces = Vec::new();
12003    let mut current = node.parent();
12004    while let Some(parent) = current {
12005        if parent.kind() == "namespace_definition"
12006            && let Some(name) = parent.child_by_field_name("name")
12007        {
12008            let mut components = Vec::new();
12009            append_cpp_name_components(name, source, &mut components)?;
12010            namespaces.push(components);
12011        }
12012        current = parent.parent();
12013    }
12014    namespaces.reverse();
12015    Some(namespaces.into_iter().flatten().collect())
12016}
12017
12018/// Whether a parser-derived namespace path can be reconciled with an indexed
12019/// owner scope without inventing an unrelated short-name binding.
12020///
12021/// Macro namespace sentinels can make tree-sitter omit one or more namespace
12022/// definitions from the ancestor chain. Preserve the order of every namespace
12023/// that did survive parsing, but allow indexed components between them. An
12024/// empty path is accepted only when the declarator itself supplies a nested
12025/// owner suffix such as `Outer::Inner`: together with the indexed enclosing
12026/// owner chain, that suffix is structural evidence that a namespace was lost.
12027/// A one-segment owner at the translation-unit root remains insufficient.
12028fn indexed_namespace_path_is_recoverable(
12029    lexical_scope: &[String],
12030    indexed_owner_scope: &[String],
12031    explicit_owner_component_count: usize,
12032) -> bool {
12033    if lexical_scope.is_empty() {
12034        return explicit_owner_component_count > 1;
12035    }
12036    if lexical_scope.len() >= indexed_owner_scope.len() {
12037        return false;
12038    }
12039    let mut indexed = indexed_owner_scope.iter();
12040    lexical_scope
12041        .iter()
12042        .all(|component| indexed.any(|candidate| candidate == component))
12043}
12044
12045pub fn has_ancestor_kind(node: Node<'_>, kind: &str) -> bool {
12046    let mut current = node.parent();
12047    while let Some(parent) = current {
12048        if parent.kind() == kind {
12049            return true;
12050        }
12051        current = parent.parent();
12052    }
12053    false
12054}
12055
12056/// Whether a declaration type is initialized with a pointer cast.
12057///
12058/// This structured shape has an independent qualified occurrence in addition
12059/// to the cast descriptor below it. Other declarations must keep their normal
12060/// full-range occurrence only.
12061pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
12062    let mut current = Some(node);
12063    while let Some(candidate) = current {
12064        if candidate.kind() == "declaration" {
12065            let Some(type_node) = candidate.child_by_field_name("type") else {
12066                return false;
12067            };
12068            if !(type_node.start_byte() <= node.start_byte()
12069                && node.end_byte() <= type_node.end_byte())
12070            {
12071                return false;
12072            }
12073            let mut cursor = candidate.walk();
12074            return candidate.named_children(&mut cursor).any(|child| {
12075                child.kind() == "init_declarator"
12076                    && child
12077                        .child_by_field_name("value")
12078                        .is_some_and(|value| value.kind() == "cast_expression")
12079            });
12080        }
12081        current = candidate.parent();
12082    }
12083    false
12084}
12085
12086#[derive(Clone, Copy, PartialEq, Eq)]
12087pub(crate) enum QualifiedAliasReferenceKind {
12088    Ordinary,
12089    ConstructorWithExpressionArgument,
12090    ExhaustiveTemplate,
12091}
12092
12093/// Whether a qualified alias reference preserves the requested target.
12094///
12095/// The complete qualified spelling and its terminal identifier are both valid
12096/// occurrences when the visible alias path is structurally proven to name the
12097/// target. Template aliases use their bound arguments; ordinary aliases use
12098/// their structured primary chain.
12099pub(crate) fn qualified_alias_reference_preserves_target(
12100    node: Node<'_>,
12101    target: &CodeUnit,
12102    analyzer: &CppGraphSource<'_>,
12103    visibility: &VisibilityIndex<'_>,
12104    file: &ProjectFile,
12105    source: &str,
12106) -> Option<QualifiedAliasReferenceKind> {
12107    if !matches!(
12108        node.kind(),
12109        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
12110    ) {
12111        return None;
12112    }
12113    let components = cpp_type_name_components(node, source)?;
12114    let name = components.last()?;
12115    analyzer.type_alias_provider().and_then(|provider| {
12116        visibility
12117            .visible_identifier_candidates(file, name)
12118            .find_map(|candidate| {
12119                let proof = provider.is_type_alias(candidate)
12120                    && canonical_cpp_scope_components(candidate) == components
12121                    && visibility.external_type_candidate_visible_in_context(
12122                        analyzer, file, candidate, node,
12123                    )
12124                    && match cpp_template_reference_arguments(node, source) {
12125                        Some(arguments) => visibility.template_alias_arguments_preserve_target(
12126                            analyzer, file, candidate, &arguments, target,
12127                        ),
12128                        None => visibility.structured_alias_primary_preserves_target(
12129                            analyzer, file, candidate, target,
12130                        ),
12131                    };
12132                proof.then(|| {
12133                    if cpp_template_reference_arguments(node, source).is_some()
12134                        && visibility.is_exhaustive_same_fqn_type_declaration_family(
12135                            analyzer, file, candidate,
12136                        )
12137                    {
12138                        QualifiedAliasReferenceKind::ExhaustiveTemplate
12139                    } else if qualified_alias_constructor_has_expression_argument(node)
12140                        || qualified_alias_local_constructor_declaration(node)
12141                    {
12142                        QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
12143                    } else {
12144                        QualifiedAliasReferenceKind::Ordinary
12145                    }
12146                })
12147            })
12148    })
12149}
12150
12151pub(crate) fn qualified_alias_reference_requires_terminal(
12152    reference: Option<QualifiedAliasReferenceKind>,
12153) -> bool {
12154    matches!(
12155        reference,
12156        Some(
12157            QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
12158                | QualifiedAliasReferenceKind::ExhaustiveTemplate
12159        )
12160    )
12161}
12162
12163fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
12164    let Some(declaration) = node.parent().filter(|parent| {
12165        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
12166    }) else {
12167        return false;
12168    };
12169    let mut cursor = declaration.walk();
12170    declaration.named_children(&mut cursor).any(|child| {
12171        child.kind() == "init_declarator"
12172            && child
12173                .child_by_field_name("value")
12174                .filter(|value| value.kind() == "argument_list")
12175                .is_some_and(|arguments| {
12176                    let mut cursor = arguments.walk();
12177                    arguments.named_children(&mut cursor).any(|argument| {
12178                        let is_parameter = matches!(
12179                            argument.kind(),
12180                            "parameter_declaration" | "optional_parameter_declaration"
12181                        );
12182                        if is_parameter {
12183                            argument
12184                                .child_by_field_name("type")
12185                                .is_some_and(|type_node| {
12186                                    type_node.kind() == "type_identifier"
12187                                        && argument.child_by_field_name("declarator").is_none()
12188                                })
12189                        } else {
12190                            !argument.kind().ends_with("_literal")
12191                                && !matches!(argument.kind(), "true" | "false" | "nullptr")
12192                        }
12193                    })
12194                })
12195    })
12196}
12197
12198/// Tree-sitter represents a local C++ direct construction such as
12199/// `Alias value(argument)` as a function declarator. Restrict that recovery to
12200/// declarations inside a compound statement so namespace-scope function
12201/// declarations with the same qualified return type stay full-range only.
12202fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
12203    let Some(declaration) = node.parent().filter(|parent| {
12204        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
12205    }) else {
12206        return false;
12207    };
12208    if declaration
12209        .parent()
12210        .is_none_or(|parent| parent.kind() != "compound_statement")
12211    {
12212        return false;
12213    }
12214    let mut cursor = declaration.walk();
12215    declaration
12216        .named_children(&mut cursor)
12217        .any(|child| child.kind() == "function_declarator")
12218}
12219
12220/// Return the terminal identifier represented by a callable or type callee.
12221///
12222/// Qualified, scoped, template, and field wrappers are traversed through their
12223/// grammar fields so both function calls and type constructions emit the token
12224/// that names the referenced declaration.
12225pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
12226    loop {
12227        let next = match node.kind() {
12228            "qualified_identifier"
12229            | "scoped_identifier"
12230            | "template_method"
12231            | "template_function"
12232            | "template_type" => node.child_by_field_name("name"),
12233            "field_expression" => node.child_by_field_name("field"),
12234            _ => None,
12235        };
12236        let Some(next) = next else {
12237            return node;
12238        };
12239        node = next;
12240    }
12241}
12242
12243#[derive(Clone, Copy)]
12244pub struct RecoveredRelationalTemplateMemberCall<'tree> {
12245    pub receiver: Node<'tree>,
12246    pub member: Node<'tree>,
12247    pub arity: usize,
12248}
12249
12250/// Recover `receiver.member<argument>(call_arguments)` when tree-sitter chose
12251/// nested relational expressions instead of a `template_method` call.
12252///
12253/// The recovery uses only grammar fields: the selected field must be the left
12254/// side of `<`, that expression must be the left side of `>`, and the right
12255/// side of `>` must be the parenthesized call arguments. Semantic callers must
12256/// additionally prove the receiver owner and the member's template status.
12257pub fn recovered_relational_template_member_call(
12258    field: Node<'_>,
12259) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
12260    if field.kind() != "field_expression" {
12261        return None;
12262    }
12263    let receiver = field
12264        .child_by_field_name("argument")
12265        .or_else(|| field.child_by_field_name("object"))?;
12266    let member = field.child_by_field_name("field")?;
12267    let less = field.parent()?;
12268    if less.kind() != "binary_expression"
12269        || less.child_by_field_name("left") != Some(field)
12270        || less
12271            .child_by_field_name("operator")
12272            .is_none_or(|operator| operator.kind() != "<")
12273        || less.child_by_field_name("right").is_none()
12274    {
12275        return None;
12276    }
12277    let greater = less.parent()?;
12278    if greater.kind() != "binary_expression"
12279        || greater.child_by_field_name("left") != Some(less)
12280        || greater
12281            .child_by_field_name("operator")
12282            .is_none_or(|operator| operator.kind() != ">")
12283    {
12284        return None;
12285    }
12286    let arguments = greater.child_by_field_name("right")?;
12287    if arguments.kind() != "parenthesized_expression" {
12288        return None;
12289    }
12290    let arity = parenthesized_call_argument_arity(arguments)?;
12291    Some(RecoveredRelationalTemplateMemberCall {
12292        receiver,
12293        member,
12294        arity,
12295    })
12296}
12297
12298fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
12299    let expression = arguments.named_child(0)?;
12300    if expression.kind() != "comma_expression" {
12301        return Some(1);
12302    }
12303    let mut arity = 0usize;
12304    let mut stack = vec![expression];
12305    while let Some(node) = stack.pop() {
12306        if node.kind() == "comma_expression" {
12307            stack.push(node.child_by_field_name("right")?);
12308            stack.push(node.child_by_field_name("left")?);
12309        } else {
12310            arity += 1;
12311        }
12312    }
12313    Some(arity)
12314}
12315
12316/// Whether `node` is part of a call's callee expression, walking only through
12317/// the grammar wrappers that can structurally contain that callee.
12318pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
12319    while let Some(parent) = node.parent() {
12320        match parent.kind() {
12321            "call_expression" => {
12322                return parent
12323                    .child_by_field_name("function")
12324                    .or_else(|| parent.named_child(0))
12325                    == Some(node);
12326            }
12327            "qualified_identifier"
12328            | "scoped_identifier"
12329            | "template_function"
12330            | "template_type"
12331            | "field_expression" => node = parent,
12332            _ => return false,
12333        }
12334    }
12335    false
12336}
12337
12338pub fn type_reference_hit_node(node: Node<'_>) -> Node<'_> {
12339    if is_call_callee_node(node) {
12340        function_terminal_node(node)
12341    } else {
12342        node
12343    }
12344}
12345
12346pub fn normalize_type_text(value: &str) -> String {
12347    strip_tag_type_prefix(
12348        normalize_cpp_whitespace(value)
12349            .trim_start_matches("const ")
12350            .trim_end_matches('*')
12351            .trim_end_matches('&')
12352            .trim(),
12353    )
12354    .to_string()
12355}
12356
12357fn strip_tag_type_prefix(value: &str) -> &str {
12358    let value = value.trim_start_matches("const ");
12359    value
12360        .strip_prefix("struct ")
12361        .or_else(|| value.strip_prefix("class "))
12362        .or_else(|| value.strip_prefix("enum "))
12363        .unwrap_or(value)
12364        .trim()
12365}
12366
12367pub fn normalize_reference_name(value: &str) -> Option<String> {
12368    let normalized = normalize_cpp_reference_text(value);
12369    (!normalized.is_empty()).then_some(normalized)
12370}
12371
12372pub fn normalize_cpp_reference_text(value: &str) -> String {
12373    let mut text = normalize_cpp_whitespace(value)
12374        .trim_start_matches("new ")
12375        .trim()
12376        .to_string();
12377    if let Some(index) = text.find(['(', '{']) {
12378        text.truncate(index);
12379    }
12380    if let Some(index) = text.find('<') {
12381        text.truncate(index);
12382    }
12383    let normalized = text
12384        .trim()
12385        .trim_start_matches("const ")
12386        .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
12387        .trim_matches(':')
12388        .trim();
12389    strip_tag_type_prefix(normalized).to_string()
12390}
12391
12392pub fn cpp_name_for(unit: &CodeUnit) -> String {
12393    let short = unit.short_name().replace(['.', '$'], "::");
12394    if unit.package_name().is_empty() {
12395        short
12396    } else {
12397        format!("{}::{}", unit.package_name(), short)
12398    }
12399}
12400
12401/// Render an indexed C++ qualified name from its authoritative FqName
12402/// segments. Unlike the legacy `cpp_name_for` renderer, this preserves dots
12403/// that belong to a template argument (for example `Args...`).
12404fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
12405    let fq = unit.fq();
12406    if fq.is_empty() {
12407        return None;
12408    }
12409    let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
12410    Some(
12411        fq.segments()
12412            .iter()
12413            .map(|&segment| interner.resolve(segment).0)
12414            .collect::<Vec<_>>()
12415            .join("::"),
12416    )
12417}
12418
12419fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
12420    canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
12421        || unit.fq().is_empty() && cpp_name_for(unit) == expected
12422}
12423
12424/// Return the indexed C++ owner scope without reparsing its rendered name.
12425///
12426/// Template spellings are opaque within an indexed `FqName` segment.  In
12427/// particular, the ellipsis in a parameter pack (`Args...`) is part of the
12428/// `AtomicHook<...>` type segment; feeding the legacy all-`::` rendering back
12429/// through `parse_symbol_path` would mistake those dots for component
12430/// separators.  Cache-loaded/legacy units may still have an empty structured
12431/// name, so retain the parser only as that explicit fallback.
12432pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
12433    let fq = unit.fq();
12434    if !fq.is_empty() {
12435        let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
12436        let scope = fq
12437            .segments()
12438            .iter()
12439            .filter_map(|&segment| {
12440                let (text, kind) = interner.resolve(segment);
12441                matches!(
12442                    kind,
12443                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
12444                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
12445                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
12446                )
12447                .then(|| text.to_string())
12448            })
12449            .collect();
12450        return scope;
12451    }
12452    brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
12453        brokk_bifrost_core::analyzer::Language::Cpp,
12454        &cpp_name_for(unit),
12455    )
12456}
12457
12458// fqname-M4: the second stage splits on the individual chars '.', '-', '>'
12459// (not the substring "->"), which deliberately reduces an `operator->`-style
12460// terminal segment to an empty tail rather than keeping it intact; the shared
12461// structured splitter's cpp operator-token merge would keep `operator->`
12462// whole instead, changing this function's result — `name_matches_callable`'s
12463// `expected.starts_with("operator")` fallback exists specifically to
12464// compensate for that reduction, and a pinned regression test
12465// (`operator-> must not be reduced with terminal_name-style punctuation
12466// splitting`) asserts today's char-class behavior. Not equivalence-provable;
12467// revisit alongside that pinned test if it is ever relaxed.
12468pub fn terminal_name(value: &str) -> &str {
12469    value
12470        .rsplit("::")
12471        .next()
12472        .unwrap_or(value)
12473        .rsplit(['.', '-', '>'])
12474        .next()
12475        .unwrap_or(value)
12476        .trim()
12477}
12478
12479pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
12480    terminal_name(&normalize_cpp_reference_text(value)) == expected
12481}
12482
12483pub fn name_matches_callable(value: &str, expected: &str) -> bool {
12484    name_matches_terminal(value, expected)
12485        || expected.starts_with("operator")
12486            && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
12487}
12488
12489pub fn name_mentions(value: &str, expected: &str) -> bool {
12490    normalize_cpp_reference_text(value)
12491        .split("::")
12492        .any(|part| part == expected)
12493}
12494
12495pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
12496    let cpp_name = cpp_name_for(unit);
12497    if reference.contains("::") {
12498        return reference == cpp_name;
12499    }
12500    reference == cpp_name
12501        || terminal_name(reference) == unit.identifier()
12502            && (unit.package_name().is_empty() || reference == unit.identifier())
12503}
12504
12505pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
12506    match kind {
12507        TargetKind::Type
12508        | TargetKind::Constructor
12509        | TargetKind::Method
12510        | TargetKind::MemberField => true,
12511        TargetKind::FreeFunction => unit.is_function(),
12512        TargetKind::GlobalField => unit.is_field(),
12513        TargetKind::Macro => unit.is_macro(),
12514    }
12515}
12516
12517pub fn is_type_alias(unit: &CodeUnit) -> bool {
12518    unit.kind() == CodeUnitType::Field
12519        && unit.signature().is_some_and(|signature| {
12520            signature.starts_with("typedef ") || signature.starts_with("using ")
12521        })
12522}
12523
12524fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
12525    let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
12526    let target_name = cpp_name_for(target);
12527    if normalized.contains("::") {
12528        return normalized == target_name;
12529    }
12530    if let Some(namespace) = alias.namespace.as_deref() {
12531        return namespace_prefixes(namespace)
12532            .into_iter()
12533            .any(|prefix| format!("{prefix}::{normalized}") == target_name);
12534    }
12535    target.package_name().is_empty() && normalized == target.identifier()
12536}
12537
12538fn parser_alias_target_names(alias: &CppAlias) -> Vec<String> {
12539    let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
12540    if normalized.contains("::") {
12541        return vec![normalized];
12542    }
12543    alias
12544        .namespace
12545        .as_deref()
12546        .map(namespace_prefixes)
12547        .map(|prefixes| {
12548            prefixes
12549                .into_iter()
12550                .map(|prefix| format!("{prefix}::{normalized}"))
12551                .collect()
12552        })
12553        .unwrap_or_else(|| vec![normalized])
12554}
12555
12556/// The declared return type text of a C++ function unit, with leading declaration specifiers
12557/// stripped, e.g. `T*` for `T* operator->()`.
12558pub fn cpp_function_return_type_text(
12559    analyzer: &CppGraphSource<'_>,
12560    function: &CodeUnit,
12561) -> Option<String> {
12562    let metadata = analyzer.signature_metadata(function);
12563    if !metadata.is_empty() {
12564        let first = metadata.first()?.return_type_text()?;
12565        return metadata
12566            .iter()
12567            .all(|metadata| metadata.return_type_text() == Some(first))
12568            .then(|| first.to_string());
12569    }
12570    let signature = cpp_function_signature_text(analyzer, function)?;
12571    cpp_function_return_type_text_from_signature(&signature)
12572}
12573
12574fn cpp_function_signature_text(
12575    analyzer: &CppGraphSource<'_>,
12576    function: &CodeUnit,
12577) -> Option<String> {
12578    function
12579        .signature()
12580        .filter(|signature| signature.contains(function.identifier()))
12581        .map(str::to_string)
12582        .or_else(|| analyzer.signatures(function).first().cloned())
12583        .or_else(|| analyzer.get_source(function, false))
12584}
12585
12586fn cpp_function_return_type_text_from_signature(signature: &str) -> Option<String> {
12587    let open = signature.find('(')?;
12588    let name_at = cpp_function_name_start(signature, open)?;
12589    if let Some(return_type) = cpp_trailing_return_type(&signature[name_at..]) {
12590        return Some(return_type);
12591    }
12592    let type_text = cpp_strip_leading_template_clause(&signature[..name_at])
12593        .split_whitespace()
12594        .filter(|token| {
12595            !matches!(
12596                *token,
12597                "static" | "virtual" | "inline" | "constexpr" | "explicit" | "friend"
12598            )
12599        })
12600        .collect::<Vec<_>>()
12601        .join(" ");
12602    let type_text = type_text.trim();
12603    (!type_text.is_empty()).then(|| type_text.to_string())
12604}
12605
12606fn cpp_function_name_start(signature: &str, open: usize) -> Option<usize> {
12607    let before_parameters = &signature[..open];
12608    if let Some(operator_at) = before_parameters.rfind("operator") {
12609        let boundary = operator_at == 0
12610            || before_parameters[..operator_at]
12611                .chars()
12612                .next_back()
12613                .is_some_and(|ch| !(ch == '_' || ch.is_ascii_alphanumeric()));
12614        if boundary {
12615            return Some(operator_at);
12616        }
12617    }
12618    before_parameters
12619        .rfind(|ch: char| !(ch == '_' || ch.is_ascii_alphanumeric()))
12620        .map(|index| index + 1)
12621}
12622
12623fn cpp_trailing_return_type(signature_from_name: &str) -> Option<String> {
12624    let open = signature_from_name.find('(')?;
12625    let mut depth = 0i32;
12626    for (offset, ch) in signature_from_name[open..].char_indices() {
12627        match ch {
12628            '(' => depth += 1,
12629            ')' => {
12630                depth -= 1;
12631                if depth == 0 {
12632                    let rest = signature_from_name[open + offset + ch.len_utf8()..].trim_start();
12633                    let arrow = rest.find("->")?;
12634                    let return_type = rest[arrow + 2..].trim_start();
12635                    let return_type = return_type
12636                        .split(['{', ';'])
12637                        .next()
12638                        .unwrap_or(return_type)
12639                        .trim();
12640                    return (!return_type.is_empty()).then(|| return_type.to_string());
12641                }
12642            }
12643            _ => {}
12644        }
12645    }
12646    None
12647}
12648
12649/// Strip a leading `template <...>` parameter clause, leaving the declaration that follows.
12650/// Returns the input unchanged when there is no such clause.
12651fn cpp_strip_leading_template_clause(text: &str) -> &str {
12652    let trimmed = text.trim_start();
12653    let Some(rest) = trimmed.strip_prefix("template") else {
12654        return text;
12655    };
12656    let rest = rest.trim_start();
12657    if !rest.starts_with('<') {
12658        return text;
12659    }
12660    let mut depth = 0i32;
12661    for (offset, ch) in rest.char_indices() {
12662        match ch {
12663            '<' => depth += 1,
12664            '>' => {
12665                depth -= 1;
12666                if depth == 0 {
12667                    return rest[offset + ch.len_utf8()..].trim_start();
12668                }
12669            }
12670            _ => {}
12671        }
12672    }
12673    text
12674}
12675
12676pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
12677    // fqname-M4: `cpp_name_for` is a bespoke all-`::` rendering of the unit's
12678    // name (it replaces every `.`/`$` in `short_name` with `::`), which is NOT
12679    // the same string `default_parent_fq_name`/`fq().parent()` would render:
12680    // the structured `FqName`'s native cpp display deliberately keeps `.` (not
12681    // `::`) between a trailing `Package` segment and a following `Type`
12682    // segment (see `separator` in `fq_name.rs`, landed for issue #1163), so
12683    // popping the unit's own `fq()` segment would NOT reproduce this
12684    // fully-`::`-joined string. Left as a split on the locally-built
12685    // all-colon string rather than the unit's structured name.
12686    cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
12687        namespace
12688            .strip_prefix("anonymous_namespace::")
12689            .unwrap_or(namespace)
12690            .to_string()
12691    })
12692}
12693
12694fn namespace_prefixes(namespace: &str) -> Vec<String> {
12695    // `namespace` is built by `cpp_name_for`/`cpp_namespace_for` with every
12696    // non-`::` separator already converted to `::`, so re-tokenizing it with
12697    // the shared structured splitter and progressively popping the last
12698    // component reproduces the `rsplit_once("::")` outward walk exactly (same
12699    // shape as `cpp_qualifier_lookup_tiers`'s namespace-chain walk).
12700    let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
12701        brokk_bifrost_core::analyzer::Language::Cpp,
12702        namespace,
12703    );
12704    let mut prefixes = Vec::new();
12705    while !parts.is_empty() {
12706        prefixes.push(parts.join("::"));
12707        parts.pop();
12708    }
12709    prefixes
12710}
12711
12712fn nearest_namespace_candidates(
12713    candidates: Vec<CodeUnit>,
12714    normalized: &str,
12715    lexical_namespace: Option<&str>,
12716) -> Vec<CodeUnit> {
12717    if normalized.contains("::") {
12718        return candidates;
12719    }
12720    if let Some(namespace) = lexical_namespace {
12721        for prefix in namespace_prefixes(namespace) {
12722            let scoped = candidates
12723                .iter()
12724                .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
12725                .cloned()
12726                .collect::<Vec<_>>();
12727            if !scoped.is_empty() {
12728                return scoped;
12729            }
12730        }
12731    }
12732    candidates
12733        .into_iter()
12734        .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
12735        .collect()
12736}
12737
12738pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
12739    let mut namespaces = Vec::new();
12740    let mut current = node.parent();
12741    while let Some(parent) = current {
12742        if parent.kind() == "namespace_definition"
12743            && let Some(name) = parent.child_by_field_name("name")
12744        {
12745            let namespace = normalize_cpp_reference_text(node_text(name, source));
12746            if !namespace.is_empty() {
12747                namespaces.push(namespace);
12748            }
12749        }
12750        current = parent.parent();
12751    }
12752    if namespaces.is_empty() {
12753        None
12754    } else {
12755        namespaces.reverse();
12756        Some(namespaces.join("::"))
12757    }
12758}
12759
12760/// Like [`precise_parent_of`], but drops module (namespace) parents. A namespace is a scope, not a
12761/// type or receiver, so namespace-scoped functions and constants resolve as free functions and
12762/// globals rather than members.
12763pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
12764    type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
12765}
12766
12767fn type_owner_resolution(
12768    analyzer: &CppGraphSource<'_>,
12769    code_unit: &CodeUnit,
12770) -> Option<ResolvedTypeOwner> {
12771    precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
12772}
12773
12774fn target_type_owner_resolution(
12775    analyzer: &CppGraphSource<'_>,
12776    code_unit: &CodeUnit,
12777) -> Option<ResolvedTypeOwner> {
12778    match type_owner_resolution(analyzer, code_unit) {
12779        Some(owner) if !owner.is_forward_declaration => Some(owner),
12780        Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
12781    }
12782}
12783
12784/// Recover method identity for an indexed out-of-line definition when the
12785/// analyzer has retained only its unique include-visible class forward
12786/// declaration. This is deliberately target-only: canonical declaration
12787/// resolution must continue to prefer the callable definition rather than
12788/// replacing it with the forward owner.
12789fn target_forward_owner_resolution(
12790    analyzer: &CppGraphSource<'_>,
12791    code_unit: &CodeUnit,
12792) -> Option<ResolvedTypeOwner> {
12793    if !code_unit.is_function() {
12794        return None;
12795    }
12796    // A top-level free function has no owner at all, and `FqName::parent`
12797    // answers the empty name rather than `None` for a one-segment identity.
12798    // `default_parent_fq_name`, which this replaced, filtered that case out;
12799    // asking the relational store for the empty name is a batch error that
12800    // fails the whole target frontier.
12801    let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
12802    let cpp = analyzer.cpp?;
12803    let mut visible_files = HashSet::default();
12804    collect_include_closure(
12805        analyzer,
12806        cpp.include_target_index(),
12807        code_unit.source(),
12808        &mut visible_files,
12809        None,
12810    );
12811    let mut forward = None;
12812    for candidate in analyzer
12813        .workspace_definitions()
12814        .exact(&owner_name)
12815        .into_iter()
12816        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
12817    {
12818        match cpp_class_declaration_strength(analyzer, &candidate) {
12819            CppClassDeclarationStrength::Forward if forward.is_none() => {
12820                forward = Some(candidate);
12821            }
12822            CppClassDeclarationStrength::Forward
12823            | CppClassDeclarationStrength::Full
12824            | CppClassDeclarationStrength::Unknown => return None,
12825        }
12826    }
12827    forward.map(|unit| ResolvedTypeOwner {
12828        unit,
12829        is_forward_declaration: true,
12830    })
12831}
12832
12833pub fn precise_parent_of(
12834    analyzer: &CppGraphSource<'_>,
12835    visibility: &VisibilityIndex<'_>,
12836    code_unit: &CodeUnit,
12837) -> Option<CodeUnit> {
12838    visibility.cached_precise_parent_of(analyzer, code_unit)
12839}
12840
12841fn precise_parent_resolution(
12842    analyzer: &CppGraphSource<'_>,
12843    code_unit: &CodeUnit,
12844) -> Option<ResolvedTypeOwner> {
12845    #[cfg(any(test, feature = "test-support"))]
12846    if let Some(cpp) = analyzer.cpp {
12847        cpp.record_cpp_parent_resolution_for_test();
12848    }
12849    if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
12850        return Some(ResolvedTypeOwner {
12851            unit,
12852            is_forward_declaration: false,
12853        });
12854    }
12855    let fallback = analyzer.parent_of(code_unit);
12856    if !code_unit.owner_is_type_scope() {
12857        return fallback.map(|unit| ResolvedTypeOwner {
12858            unit,
12859            is_forward_declaration: false,
12860        });
12861    }
12862    let owner_fq = code_unit
12863        .fq()
12864        .parent()
12865        .expect("a unit with an owner identifier has a structured parent");
12866    let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
12867    match same_source_owner(analyzer, code_unit, &owner_candidates) {
12868        DirectOwnerResolution::UniqueFull(owner) => {
12869            return Some(ResolvedTypeOwner {
12870                unit: owner,
12871                is_forward_declaration: false,
12872            });
12873        }
12874        DirectOwnerResolution::Ambiguous => return None,
12875        DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
12876    }
12877    match directly_included_owner(analyzer, code_unit, &owner_candidates) {
12878        DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
12879            unit: owner,
12880            is_forward_declaration: false,
12881        }),
12882        DirectOwnerResolution::Ambiguous => None,
12883        DirectOwnerResolution::ForwardsOnly(forwards) => {
12884            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
12885                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
12886                    unit: owner,
12887                    is_forward_declaration: false,
12888                }),
12889                FullOwnerResolution::None => {
12890                    unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
12891                        unit,
12892                        is_forward_declaration: true,
12893                    })
12894                }
12895                FullOwnerResolution::Ambiguous => None,
12896            }
12897        }
12898        DirectOwnerResolution::None => {
12899            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
12900                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
12901                    unit: owner,
12902                    is_forward_declaration: false,
12903                }),
12904                FullOwnerResolution::Ambiguous => None,
12905                FullOwnerResolution::None => fallback
12906                    .filter(|parent| {
12907                        parent.source() == code_unit.source()
12908                            && parent.fq() == &owner_fq
12909                            && (!parent.is_class()
12910                                || cpp_class_declaration_strength(analyzer, parent)
12911                                    == CppClassDeclarationStrength::Full)
12912                    })
12913                    .map(|unit| ResolvedTypeOwner {
12914                        unit,
12915                        is_forward_declaration: false,
12916                    }),
12917            }
12918        }
12919    }
12920}
12921
12922fn exact_structural_type_parent(
12923    analyzer: &CppGraphSource<'_>,
12924    code_unit: &CodeUnit,
12925) -> Option<CodeUnit> {
12926    if !code_unit.is_function() && !code_unit.is_field() {
12927        return None;
12928    }
12929    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
12930    let cpp = analyzer.cpp?;
12931    let parent = cpp.structural_parent_of(code_unit)?;
12932    (!parent.is_module()
12933        && parent.source() == code_unit.source()
12934        && parent.package_name() == code_unit.package_name()
12935        && parent.short_name() == encoded_owner)
12936        .then_some(parent)
12937}
12938
12939fn same_source_owner(
12940    analyzer: &CppGraphSource<'_>,
12941    code_unit: &CodeUnit,
12942    owner_candidates: &[CodeUnit],
12943) -> DirectOwnerResolution {
12944    let candidates = owner_candidates
12945        .iter()
12946        .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
12947        .cloned()
12948        .collect::<Vec<_>>();
12949    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
12950    classify_direct_owner_candidates(analyzer, candidates.into_iter())
12951}
12952
12953fn visible_full_cpp_owner(
12954    analyzer: &CppGraphSource<'_>,
12955    code_unit: &CodeUnit,
12956    owner_candidates: &[CodeUnit],
12957) -> FullOwnerResolution {
12958    let Some(cpp) = analyzer.cpp else {
12959        return FullOwnerResolution::None;
12960    };
12961    let mut visible_files = HashSet::default();
12962    collect_include_closure(
12963        analyzer,
12964        cpp.include_target_index(),
12965        code_unit.source(),
12966        &mut visible_files,
12967        None,
12968    );
12969    let candidates = owner_candidates
12970        .iter()
12971        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
12972        .cloned()
12973        .collect::<Vec<_>>();
12974    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
12975    let mut full_definition = None;
12976    for candidate in candidates {
12977        match cpp_class_declaration_strength(analyzer, &candidate) {
12978            CppClassDeclarationStrength::Full if full_definition.is_some() => {
12979                return FullOwnerResolution::Ambiguous;
12980            }
12981            CppClassDeclarationStrength::Full => full_definition = Some(candidate),
12982            CppClassDeclarationStrength::Forward => {}
12983            CppClassDeclarationStrength::Unknown => return FullOwnerResolution::Ambiguous,
12984        }
12985    }
12986    full_definition.map_or(FullOwnerResolution::None, FullOwnerResolution::Unique)
12987}
12988
12989pub enum DirectOwnerResolution {
12990    None,
12991    ForwardsOnly(Vec<CodeUnit>),
12992    UniqueFull(CodeUnit),
12993    Ambiguous,
12994}
12995
12996enum FullOwnerResolution {
12997    None,
12998    Unique(CodeUnit),
12999    Ambiguous,
13000}
13001
13002#[derive(Clone, Copy, PartialEq, Eq)]
13003pub enum CppClassDeclarationStrength {
13004    Full,
13005    Forward,
13006    Unknown,
13007}
13008
13009fn directly_included_owner(
13010    analyzer: &CppGraphSource<'_>,
13011    code_unit: &CodeUnit,
13012    owner_candidates: &[CodeUnit],
13013) -> DirectOwnerResolution {
13014    let Some(cpp) = analyzer.cpp else {
13015        return DirectOwnerResolution::None;
13016    };
13017    let imports = analyzer.import_statements(code_unit.source());
13018    let direct_includes: HashSet<ProjectFile> = cpp_include_paths(&imports)
13019        .into_iter()
13020        .flat_map(|include| {
13021            resolve_include_targets_with_index(
13022                code_unit.source(),
13023                &include,
13024                cpp.include_target_index(),
13025            )
13026        })
13027        .collect();
13028    let candidates = owner_candidates
13029        .iter()
13030        .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
13031        .cloned()
13032        .collect::<Vec<_>>();
13033    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
13034    classify_direct_owner_candidates(analyzer, candidates.into_iter())
13035}
13036
13037fn prefer_member_declaring_owners(
13038    analyzer: &CppGraphSource<'_>,
13039    member: &CodeUnit,
13040    candidates: Vec<CodeUnit>,
13041) -> Vec<CodeUnit> {
13042    let matching = candidates
13043        .iter()
13044        .filter(|owner| owner_declares_member(analyzer, owner, member))
13045        .cloned()
13046        .collect::<Vec<_>>();
13047    if matching.is_empty() {
13048        candidates
13049    } else {
13050        matching
13051    }
13052}
13053
13054fn owner_declares_member(
13055    analyzer: &CppGraphSource<'_>,
13056    owner: &CodeUnit,
13057    member: &CodeUnit,
13058) -> bool {
13059    analyzer.direct_children(owner).into_iter().any(|child| {
13060        child.kind() == member.kind()
13061            && child.identifier() == member.identifier()
13062            && child.signature() == member.signature()
13063    })
13064}
13065
13066fn classify_direct_owner_candidates(
13067    analyzer: &CppGraphSource<'_>,
13068    candidates: impl Iterator<Item = CodeUnit>,
13069) -> DirectOwnerResolution {
13070    collapse_owner_candidates(candidates.map(|candidate| {
13071        let strength = cpp_class_declaration_strength(analyzer, &candidate);
13072        (candidate, strength)
13073    }))
13074}
13075
13076pub fn collapse_owner_candidates(
13077    candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
13078) -> DirectOwnerResolution {
13079    let mut full_definition = None;
13080    let mut forwards = Vec::new();
13081    for (candidate, strength) in candidates {
13082        match strength {
13083            CppClassDeclarationStrength::Full if full_definition.is_some() => {
13084                return DirectOwnerResolution::Ambiguous;
13085            }
13086            CppClassDeclarationStrength::Full => full_definition = Some(candidate),
13087            CppClassDeclarationStrength::Forward => forwards.push(candidate),
13088            CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
13089        }
13090    }
13091    if let Some(owner) = full_definition {
13092        DirectOwnerResolution::UniqueFull(owner)
13093    } else if !forwards.is_empty() {
13094        DirectOwnerResolution::ForwardsOnly(forwards)
13095    } else {
13096        DirectOwnerResolution::None
13097    }
13098}
13099
13100#[cfg(any(test, feature = "test-support"))]
13101pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
13102    unique_logical_forward_owner(forwards)
13103}
13104
13105fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
13106    let first = forwards.pop()?;
13107    forwards
13108        .iter()
13109        .all(|forward| same_logical_symbol(forward, &first))
13110        .then_some(first)
13111}
13112
13113pub fn cpp_class_declaration_strength(
13114    analyzer: &CppGraphSource<'_>,
13115    candidate: &CodeUnit,
13116) -> CppClassDeclarationStrength {
13117    if let Some(prepared) = analyzer
13118        .cpp
13119        .and_then(|cpp| cpp.prepared_syntax(analyzer.token, candidate.source()))
13120    {
13121        return cpp_class_declaration_strength_in_tree(
13122            analyzer,
13123            candidate,
13124            prepared.source(),
13125            prepared.tree().root_node(),
13126        );
13127    }
13128    let Some(source) = analyzer.indexed_source(candidate.source()) else {
13129        return CppClassDeclarationStrength::Unknown;
13130    };
13131    #[cfg(any(test, feature = "test-support"))]
13132    if let Some(cpp) = analyzer.cpp {
13133        cpp.record_cpp_class_strength_parse_for_test();
13134    }
13135    let mut parser = Parser::new();
13136    if parser
13137        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13138        .is_err()
13139    {
13140        return CppClassDeclarationStrength::Unknown;
13141    }
13142    let Some(tree) = parser.parse(&source, None) else {
13143        return CppClassDeclarationStrength::Unknown;
13144    };
13145    cpp_class_declaration_strength_in_tree(analyzer, candidate, &source, tree.root_node())
13146}
13147
13148fn cpp_class_declaration_strength_in_tree(
13149    analyzer: &CppGraphSource<'_>,
13150    candidate: &CodeUnit,
13151    source: &str,
13152    root: Node<'_>,
13153) -> CppClassDeclarationStrength {
13154    let ranges = analyzer.ranges(candidate);
13155    let mut saw_forward = false;
13156    for range in ranges {
13157        let mut stack = vec![root];
13158        while let Some(node) = stack.pop() {
13159            if node.start_byte() == range.start_byte
13160                && recovered_fragmented_plain_class_has_body(
13161                    node,
13162                    source,
13163                    candidate.identifier(),
13164                    &range,
13165                )
13166            {
13167                return CppClassDeclarationStrength::Full;
13168            }
13169            if node.start_byte() > range.start_byte || node.end_byte() < range.start_byte {
13170                continue;
13171            }
13172            if node.start_byte() == range.start_byte && node.end_byte() == range.end_byte {
13173                if matches!(
13174                    node.kind(),
13175                    "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
13176                ) {
13177                    if cpp_class_node_has_body(node) {
13178                        return CppClassDeclarationStrength::Full;
13179                    }
13180                    saw_forward = true;
13181                } else if let Some(has_body) =
13182                    recovered_exported_class_has_body(node, source, candidate.identifier())
13183                {
13184                    if has_body {
13185                        return CppClassDeclarationStrength::Full;
13186                    }
13187                    saw_forward = true;
13188                }
13189            }
13190            let mut cursor = node.walk();
13191            stack.extend(node.named_children(&mut cursor));
13192        }
13193    }
13194    if saw_forward {
13195        CppClassDeclarationStrength::Forward
13196    } else {
13197        CppClassDeclarationStrength::Unknown
13198    }
13199}
13200
13201fn cpp_class_node_has_body(node: Node<'_>) -> bool {
13202    node.child_by_field_name("body").is_some() || {
13203        let mut cursor = node.walk();
13204        node.named_children(&mut cursor).any(|child| {
13205            matches!(
13206                child.kind(),
13207                "declaration_list" | "field_declaration_list" | "enumerator_list"
13208            )
13209        })
13210    }
13211}
13212
13213pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
13214    if !code_unit.owner_is_type_scope() {
13215        return None;
13216    }
13217    let owner_fq = code_unit.fq().parent()?;
13218    ctx.analyzer
13219        .workspace_definitions()
13220        .exact(&owner_fq)
13221        .into_iter()
13222        .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
13223}
13224
13225pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
13226    left.kind() == right.kind()
13227        && left.fq_name() == right.fq_name()
13228        && left.signature() == right.signature()
13229        && left.source() == right.source()
13230}
13231
13232pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
13233    same_symbol(left, right) || same_logical_symbol(left, right)
13234}
13235
13236pub fn same_visible_global_field_symbol(
13237    analyzer: &CppGraphSource<'_>,
13238    internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
13239    left: &CodeUnit,
13240    right: &CodeUnit,
13241) -> bool {
13242    if same_symbol(left, right) {
13243        return true;
13244    }
13245    if !same_logical_symbol(left, right) {
13246        return false;
13247    }
13248    if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
13249        || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
13250    {
13251        left.source() == right.source()
13252    } else {
13253        true
13254    }
13255}
13256
13257fn cpp_global_field_has_internal_linkage_cached(
13258    analyzer: &CppGraphSource<'_>,
13259    cache: &mut HashMap<CodeUnit, bool>,
13260    candidate: &CodeUnit,
13261) -> bool {
13262    if let Some(internal) = cache.get(candidate) {
13263        return *internal;
13264    }
13265    #[cfg(any(test, feature = "test-support"))]
13266    note_cpp_global_field_internal_linkage_classification_for_test();
13267    let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
13268    cache.insert(candidate.clone(), internal);
13269    internal
13270}
13271
13272#[cfg(any(test, feature = "test-support"))]
13273thread_local! {
13274    static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
13275}
13276
13277#[cfg(any(test, feature = "test-support"))]
13278fn note_cpp_global_field_internal_linkage_classification_for_test() {
13279    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
13280        count.set(count.get() + 1);
13281    });
13282}
13283
13284#[cfg(any(test, feature = "test-support"))]
13285pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
13286    body: impl FnOnce() -> T,
13287) -> (T, usize) {
13288    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
13289        count.set(0);
13290        let result = body();
13291        let observed = count.get();
13292        count.set(0);
13293        (result, observed)
13294    })
13295}
13296
13297pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
13298    left.kind() == right.kind()
13299        && left.fq_name() == right.fq_name()
13300        && left.signature() == right.signature()
13301}
13302
13303pub fn cpp_global_field_has_internal_linkage(
13304    analyzer: &CppGraphSource<'_>,
13305    candidate: &CodeUnit,
13306) -> bool {
13307    if !candidate.is_field() || candidate.short_name().contains('.') {
13308        return false;
13309    }
13310    let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
13311        return false;
13312    };
13313    match local_linkage {
13314        CppFieldLinkage::Internal => true,
13315        CppFieldLinkage::External => false,
13316        CppFieldLinkage::InternalUnlessExternalPeer => {
13317            !cpp_global_field_linkage_peers(analyzer, candidate)
13318                .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
13319                .any(|linkage| matches!(linkage, CppFieldLinkage::External))
13320        }
13321    }
13322}
13323
13324fn cpp_global_field_linkage_peers<'a>(
13325    analyzer: &CppGraphSource<'a>,
13326    candidate: &'a CodeUnit,
13327) -> impl Iterator<Item = CodeUnit> + 'a {
13328    let name = candidate.fq().clone();
13329    analyzer
13330        .workspace_definitions()
13331        .exact(&name)
13332        .into_iter()
13333        .filter(move |peer| {
13334            if peer == candidate {
13335                return false;
13336            }
13337            #[cfg(any(test, feature = "test-support"))]
13338            note_cpp_global_field_linkage_peer_inspection_for_test();
13339            same_logical_symbol(peer, candidate)
13340        })
13341}
13342
13343#[cfg(any(test, feature = "test-support"))]
13344thread_local! {
13345    static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
13346}
13347
13348#[cfg(any(test, feature = "test-support"))]
13349fn note_cpp_global_field_linkage_peer_inspection_for_test() {
13350    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
13351        count.set(count.get() + 1);
13352    });
13353}
13354
13355#[cfg(any(test, feature = "test-support"))]
13356pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
13357    body: impl FnOnce() -> T,
13358) -> (T, usize) {
13359    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
13360        count.set(0);
13361        let result = body();
13362        let observed = count.get();
13363        count.set(0);
13364        (result, observed)
13365    })
13366}
13367
13368fn cpp_global_field_declaration_linkage(
13369    analyzer: &CppGraphSource<'_>,
13370    candidate: &CodeUnit,
13371) -> Option<CppFieldLinkage> {
13372    if let Some(linkage) = analyzer.cpp_field_linkage(candidate) {
13373        return Some(linkage);
13374    }
13375    let cpp = analyzer.cpp?;
13376    if let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) {
13377        return cpp_global_field_declaration_linkage_in_tree(
13378            analyzer,
13379            candidate,
13380            prepared.source(),
13381            prepared.tree().root_node(),
13382        );
13383    }
13384    let source = analyzer.indexed_source(candidate.source())?;
13385    let mut parser = Parser::new();
13386    if parser
13387        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13388        .is_err()
13389    {
13390        return None;
13391    }
13392    let tree = parser.parse(&source, None)?;
13393    cpp_global_field_declaration_linkage_in_tree(analyzer, candidate, &source, tree.root_node())
13394}
13395
13396fn cpp_global_field_declaration_linkage_in_tree(
13397    analyzer: &CppGraphSource<'_>,
13398    candidate: &CodeUnit,
13399    source: &str,
13400    root: Node<'_>,
13401) -> Option<CppFieldLinkage> {
13402    analyzer.ranges(candidate).iter().find_map(|range| {
13403        node_for_exact_range(root, range)
13404            .and_then(enclosing_cpp_field_declaration)
13405            .map(|declaration| {
13406                // One question about one declaration; see `ParentIndex::unindexed`.
13407                cpp_field_declaration_linkage(declaration, source, &ParentIndex::unindexed())
13408            })
13409    })
13410}
13411
13412fn enclosing_cpp_field_declaration(mut node: Node<'_>) -> Option<Node<'_>> {
13413    loop {
13414        if matches!(node.kind(), "declaration" | "field_declaration") {
13415            return Some(node);
13416        }
13417        node = node.parent()?;
13418    }
13419}
13420
13421#[cfg(test)]
13422mod tests {
13423    use super::*;
13424
13425    #[test]
13426    fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
13427        let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
13428        assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
13429        assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
13430        assert!(indexed_namespace_path_is_recoverable(
13431            &["cache".to_string()],
13432            &indexed,
13433            1,
13434        ));
13435    }
13436
13437    #[test]
13438    fn sort_lookup_units_totally_orders_every_identity_field() {
13439        let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
13440        let base = CodeUnit::with_signature(
13441            file.clone(),
13442            CodeUnitType::Function,
13443            "scope",
13444            "value",
13445            Some("()".to_string()),
13446            false,
13447        );
13448        let different_kind = CodeUnit::with_signature(
13449            file.clone(),
13450            CodeUnitType::Field,
13451            "scope",
13452            "value",
13453            Some("()".to_string()),
13454            false,
13455        );
13456        let synthetic = base.with_synthetic(true);
13457
13458        let interner = segment_interner();
13459        let mut member_fq = FqName::new();
13460        member_fq.push(interner.intern("scope", SegmentKind::Package));
13461        member_fq.push(interner.intern("value", SegmentKind::Member));
13462        let different_package_boundary = CodeUnit::from_fq(
13463            file.clone(),
13464            CodeUnitType::Function,
13465            member_fq,
13466            0,
13467            Some("()".to_string()),
13468            false,
13469        );
13470
13471        let mut unknown_fq = FqName::new();
13472        unknown_fq.push(interner.intern("scope", SegmentKind::Package));
13473        unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
13474        let different_segment_kind = CodeUnit::from_fq(
13475            file,
13476            CodeUnitType::Function,
13477            unknown_fq,
13478            1,
13479            Some("()".to_string()),
13480            false,
13481        );
13482
13483        let input = vec![
13484            base,
13485            different_kind,
13486            synthetic,
13487            different_package_boundary,
13488            different_segment_kind,
13489        ];
13490        let mut expected = input.clone();
13491        sort_lookup_units(&mut expected);
13492        assert!(expected.windows(2).all(|pair| {
13493            let mut ordered = pair.to_vec();
13494            sort_lookup_units(&mut ordered);
13495            ordered == pair && pair[0] != pair[1]
13496        }));
13497
13498        let mut reversed = input.clone();
13499        reversed.reverse();
13500        sort_lookup_units(&mut reversed);
13501        assert_eq!(reversed, expected);
13502
13503        let mut rotated = input;
13504        rotated.rotate_left(2);
13505        sort_lookup_units(&mut rotated);
13506        assert_eq!(rotated, expected);
13507    }
13508
13509    #[test]
13510    fn displaced_preprocessor_terminator_bounds_the_real_guard() {
13511        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";
13512        let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
13513        let parse = |source: &str| {
13514            let mut parser = Parser::new();
13515            parser
13516                .set_language(&tree_sitter_cpp::LANGUAGE.into())
13517                .expect("C++ grammar");
13518            parser.parse(source, None).expect("fixture tree")
13519        };
13520
13521        let tree = parse(damaged);
13522        let root = tree.root_node();
13523        let target = damaged.find("target").expect("target byte");
13524        let declaration = root
13525            .descendant_for_byte_range(target, target + "target".len())
13526            .and_then(|mut node| {
13527                loop {
13528                    if node.kind() == "declaration" {
13529                        break Some(node);
13530                    }
13531                    node = node.parent()?;
13532                }
13533            })
13534            .expect("declaration after the displaced terminator");
13535        let conditional = declaration
13536            .parent()
13537            .filter(|node| node.kind() == "preproc_ifdef")
13538            .expect("damaged inner conditional");
13539        let outer = conditional
13540            .parent()
13541            .filter(|node| node.kind() == "preproc_ifdef")
13542            .expect("ordinary outer include guard");
13543        let terminator = cpp_displaced_preprocessor_terminator(conditional)
13544            .expect("structured displaced #endif");
13545        assert_eq!(node_text(terminator, damaged), "#endif");
13546        assert!(terminator.end_byte() <= declaration.start_byte());
13547        assert!(!preprocessor_conditional_contains_descendant(
13548            conditional,
13549            declaration
13550        ));
13551        assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
13552        assert!(preprocessor_conditional_contains_descendant(
13553            outer,
13554            declaration
13555        ));
13556
13557        let tree = parse(guarded);
13558        let conditional = tree
13559            .root_node()
13560            .named_child(0)
13561            .filter(|node| node.kind() == "preproc_ifdef")
13562            .expect("ordinary conditional");
13563        let declaration = conditional
13564            .named_children(&mut conditional.walk())
13565            .find(|node| node.kind() == "declaration")
13566            .expect("guarded declaration");
13567        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
13568        assert!(preprocessor_conditional_contains_descendant(
13569            conditional,
13570            declaration
13571        ));
13572
13573        let damaged_alternative = format!(
13574            "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
13575            "UNUSED(value)\n".repeat(64)
13576        );
13577        let tree = parse(&damaged_alternative);
13578        let conditional = tree
13579            .root_node()
13580            .named_child(0)
13581            .filter(|node| node.kind() == "preproc_ifdef")
13582            .expect("outer conditional with an alternative");
13583        assert!(conditional.has_error());
13584        assert!(conditional.child_by_field_name("alternative").is_some());
13585        assert!(
13586            conditional
13587                .child(conditional.child_count() - 1)
13588                .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
13589        );
13590        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
13591
13592        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";
13593        let tree = parse(split_declaration);
13594        let root = tree.root_node();
13595        let conditional = root
13596            .named_children(&mut root.walk())
13597            .find(|node| node.kind() == "preproc_ifdef" && node.start_position().row == 3)
13598            .expect("split declaration conditional");
13599        let target = split_declaration
13600            .find("static int target")
13601            .expect("target byte");
13602        let boundary =
13603            cpp_displaced_preprocessor_boundary(conditional).expect("split declaration boundary");
13604        assert!(boundary.end_byte <= target, "{boundary:?}");
13605        assert_eq!(boundary.end_line, 9, "{boundary:?}");
13606        let target_node = root
13607            .descendant_for_byte_range(target, target + "static".len())
13608            .expect("target node");
13609        assert!(!preprocessor_conditional_contains_descendant(
13610            conditional,
13611            target_node
13612        ));
13613    }
13614
13615    #[test]
13616    fn fragmented_reference_guard_is_recovered() {
13617        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";
13618        let mut parser = Parser::new();
13619        parser
13620            .set_language(&tree_sitter_cpp::LANGUAGE.into())
13621            .expect("C++ grammar");
13622        let tree = parser.parse(source, None).expect("fixture tree");
13623        let start = source.rfind("helper").expect("reference byte");
13624        let node = tree
13625            .root_node()
13626            .descendant_for_byte_range(start, start + "helper".len())
13627            .expect("reference node");
13628        let mut expected = HashSet::default();
13629        expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
13630            vec![
13631                BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
13632                BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
13633            ],
13634        )));
13635        assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
13636    }
13637
13638    #[test]
13639    fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
13640        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";
13641        let mut parser = Parser::new();
13642        parser
13643            .set_language(&tree_sitter_cpp::LANGUAGE.into())
13644            .expect("C++ grammar");
13645        let tree = parser.parse(source, None).expect("fixture tree");
13646        let root = tree.root_node();
13647        let definition_start = source.find("target(void)").expect("definition");
13648        let reference_start = source.rfind("target()").expect("reference");
13649        let definition = root
13650            .descendant_for_byte_range(definition_start, definition_start + "target".len())
13651            .expect("definition node");
13652        let reference = root
13653            .descendant_for_byte_range(reference_start, reference_start + "target".len())
13654            .expect("reference node");
13655        let required =
13656            preprocessor_guard_environment(definition, source).expect("definition guard");
13657        let active = preprocessor_guard_environment(reference, source).expect("reference guard");
13658        assert!(guard_requirements_hold_at_reference(
13659            &required,
13660            Some(&active)
13661        ));
13662    }
13663
13664    #[test]
13665    fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
13666        let source = "g_autoptr(FuChunkArray) self = make_array();";
13667        let mut parser = Parser::new();
13668        parser
13669            .set_language(&tree_sitter_cpp::LANGUAGE.into())
13670            .expect("C++ grammar");
13671        let tree = parser.parse(source, None).expect("fixture tree");
13672        let statement = tree.root_node().named_child(0).expect("statement");
13673        let binding =
13674            recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
13675        assert_eq!(binding.name, "self");
13676        assert_eq!(binding.type_name, "FuChunkArray");
13677        assert_eq!(binding.pointer_depth, 1);
13678
13679        let near_miss = "holder(FuChunkArray) self = make_array();";
13680        let tree = parser.parse(near_miss, None).expect("near-miss tree");
13681        let statement = tree.root_node().named_child(0).expect("statement");
13682        assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
13683    }
13684
13685    #[test]
13686    fn boolean_guard_normalization_proves_equivalence_and_implication() {
13687        let windows = BooleanGuardExpression::Defined("WIN32".to_string());
13688        let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
13689        let negated_windows_branch =
13690            BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
13691        let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
13692        assert_eq!(negated_windows_branch, portable);
13693
13694        let missing_a = BooleanGuardExpression::Undefined("A".to_string());
13695        let missing_b = BooleanGuardExpression::Undefined("B".to_string());
13696        let missing_c = BooleanGuardExpression::Undefined("C".to_string());
13697        let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
13698        let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
13699        assert!(fallback_branch.implies(&fallback_declaration));
13700        assert!(!fallback_declaration.implies(&fallback_branch));
13701    }
13702
13703    #[test]
13704    fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
13705        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";
13706        let mut parser = Parser::new();
13707        parser
13708            .set_language(&tree_sitter_cpp::LANGUAGE.into())
13709            .expect("C++ grammar");
13710        let tree = parser.parse(source, None).expect("fixture tree");
13711        let root = tree.root_node();
13712        let call = |marker: &str| {
13713            let start = source.find(marker).expect("call marker");
13714            let mut node = root
13715                .descendant_for_byte_range(start, start + "helper".len())
13716                .expect("call name node");
13717            loop {
13718                if node.kind() == "call_expression" {
13719                    break node;
13720                }
13721                node = node.parent().expect("call expression ancestor");
13722            }
13723        };
13724        let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
13725        let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
13726        let keyword_call = call("helper(NULL, template); /* bound */");
13727        let keyword_arguments = keyword_call
13728            .child_by_field_name("arguments")
13729            .expect("keyword argument list");
13730        assert_eq!(
13731            recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
13732            1
13733        );
13734        assert_eq!(
13735            recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
13736            0
13737        );
13738
13739        let unbound_call = call("helper(NULL, template); /* unbound */");
13740        let unbound_arguments = unbound_call
13741            .child_by_field_name("arguments")
13742            .expect("unbound argument list");
13743        assert_eq!(
13744            recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
13745            0
13746        );
13747    }
13748
13749    fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
13750        let mut parser = Parser::new();
13751        parser
13752            .set_language(&tree_sitter_cpp::LANGUAGE.into())
13753            .expect("C++ grammar");
13754        let tree = parser.parse(source, None).expect("C++ fixture tree");
13755        let mut stack = vec![tree.root_node()];
13756        while let Some(node) = stack.pop() {
13757            if node.kind() == "enum_specifier" {
13758                return flattened_macro_namespace_components(node, source);
13759            }
13760            let mut cursor = node.walk();
13761            let children = node.named_children(&mut cursor).collect::<Vec<_>>();
13762            stack.extend(children.into_iter().rev());
13763        }
13764        None
13765    }
13766
13767    #[test]
13768    fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
13769        let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
13770namespace detail
13771{
13772enum class value_t { null };
13773}
13774NLOHMANN_JSON_NAMESPACE_END
13775NLOHMANN_JSON_NAMESPACE_BEGIN
13776namespace next
13777{
13778struct next_type {};
13779}
13780NLOHMANN_JSON_NAMESPACE_END
13781"#;
13782        assert_eq!(
13783            first_enum_flattened_namespace(complete),
13784            Some(vec!["detail".to_string()])
13785        );
13786
13787        let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
13788        assert_eq!(
13789            first_enum_flattened_namespace(&stale_end),
13790            Some(vec!["detail".to_string()]),
13791            "a stale end marker before the begin marker must not replace the intended namespace"
13792        );
13793
13794        let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
13795namespace detail
13796{
13797enum class value_t { null };
13798}
13799struct next_type {};
13800"#;
13801        assert_eq!(first_enum_flattened_namespace(incomplete), None);
13802    }
13803}