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::tree_walk::node_for_exact_range;
28use brokk_bifrost_core::analyzer::usages::common::same_node;
29use brokk_bifrost_core::analyzer::usages::local_inference::LocalInferenceEngine;
30use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
31use brokk_bifrost_core::cancellation::CancellationToken;
32use brokk_bifrost_core::hash::{HashMap, HashSet};
33use std::borrow::Cow;
34#[cfg(any(test, feature = "test-support"))]
35use std::cell::Cell;
36use std::cell::OnceCell;
37use std::cmp::Ordering as CmpOrdering;
38use std::collections::BTreeSet;
39use std::hash::Hash;
40#[cfg(any(test, feature = "test-support"))]
41use std::sync::atomic::{AtomicUsize, Ordering};
42use std::sync::{Arc, Mutex, OnceLock, RwLock};
43use std::thread::ThreadId;
44use tree_sitter::{Node, Parser, Tree};
45
46#[derive(Clone, Copy, PartialEq, Eq)]
47pub enum TargetKind {
48    Type,
49    Constructor,
50    FreeFunction,
51    Method,
52    GlobalField,
53    MemberField,
54    Macro,
55}
56
57pub enum LexicalTypeResolution {
58    Resolved {
59        unit: CodeUnit,
60        components: Vec<String>,
61        candidates: Vec<CodeUnit>,
62    },
63    Ambiguous,
64    Missing,
65}
66
67#[derive(Clone, Copy)]
68enum TypeCandidateResolution<'a> {
69    Canonical,
70    PreserveAlias,
71    PreserveTarget(&'a CodeUnit),
72}
73
74/// Why a name did not reduce to one indexed type declaration.
75///
76/// The two answers are not interchangeable. `Ambiguous` means the index holds
77/// several declarations and the caller must choose; `Unresolvable` means the
78/// index holds none, which is a boundary the workspace cannot see past. A
79/// `using`/`typedef` alias to a template parameter or to a standard-library
80/// type is unresolvable, and reporting it as ambiguity produced an `ambiguous`
81/// answer with an empty candidate list (#1828).
82#[derive(Clone, Copy, Debug, PartialEq, Eq)]
83enum TypeCandidateFailure {
84    Ambiguous,
85    Unresolvable,
86}
87
88impl TypeCandidateFailure {
89    fn lexical_resolution(self) -> LexicalTypeResolution {
90        match self {
91            Self::Ambiguous => LexicalTypeResolution::Ambiguous,
92            Self::Unresolvable => LexicalTypeResolution::Missing,
93        }
94    }
95}
96
97pub enum LexicalCallableValueResolution {
98    Type(CodeUnit),
99    FreeFunction(CodeUnit),
100    Ambiguous,
101    Missing,
102}
103
104pub enum UsingEnumMemberResolution {
105    Resolved { owner: CodeUnit, member: CodeUnit },
106    Ambiguous,
107    Missing,
108}
109
110pub enum NamespaceValueResolution {
111    Resolved,
112    Ambiguous,
113    Missing,
114}
115
116#[derive(Clone, Debug, PartialEq, Eq)]
117pub enum OrdinaryMacroReferenceResolution {
118    Resolved(CodeUnit),
119    Ambiguous,
120    Missing,
121}
122
123#[derive(Clone, Debug, PartialEq, Eq)]
124pub enum RecoveredCReferenceRanges {
125    Complete(Vec<Range>),
126    LimitExceeded,
127}
128
129pub fn resolve_namespace_value(
130    analyzer: &CppGraphSource<'_>,
131    visibility: &VisibilityIndex<'_>,
132    file: &ProjectFile,
133    namespace: &str,
134    name: &str,
135    before_byte: usize,
136) -> NamespaceValueResolution {
137    let mut matches = Vec::new();
138    for candidate in visibility.visible_identifier_candidates(file, name) {
139        if type_owner_of(analyzer, candidate).is_some()
140            || candidate.package_name() != namespace
141            || (candidate.source() == file
142                && !analyzer
143                    .ranges(candidate)
144                    .iter()
145                    .any(|range| range.start_byte < before_byte))
146            || matches
147                .iter()
148                .any(|existing| same_visible_symbol(existing, candidate))
149        {
150            continue;
151        }
152        matches.push(candidate.clone());
153        if matches.len() > 1 {
154            return NamespaceValueResolution::Ambiguous;
155        }
156    }
157    matches
158        .pop()
159        .map(|_| NamespaceValueResolution::Resolved)
160        .unwrap_or(NamespaceValueResolution::Missing)
161}
162
163pub(crate) struct ScopedUsingEnumOwners {
164    scopes: Vec<Vec<CodeUnit>>,
165}
166
167/// Same-file class and namespace imports collected by the targeted scanner's AST prepass.
168/// Cross-file and inherited class imports are deliberately not inferred without persisted
169/// evidence; a missing imported enumerator therefore remains unproven rather than being
170/// misresolved.
171pub(crate) struct SemanticUsingEnumOwners {
172    class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
173    namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
174}
175
176pub(crate) enum SemanticUsingEnumMemberResolution {
177    Class(UsingEnumMemberResolution),
178    Namespace(UsingEnumMemberResolution),
179    Missing,
180}
181
182impl SemanticUsingEnumOwners {
183    pub(crate) fn new() -> Self {
184        Self {
185            class_imports: HashMap::default(),
186            namespace_imports: HashMap::default(),
187        }
188    }
189
190    pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
191        let imports = self.class_imports.entry(class).or_default();
192        if !imports
193            .iter()
194            .any(|existing| same_visible_symbol(existing, &enum_owner))
195        {
196            imports.push(enum_owner);
197        }
198    }
199
200    pub fn import_namespace(
201        &mut self,
202        namespace: Vec<String>,
203        declaration_byte: usize,
204        enum_owner: CodeUnit,
205    ) {
206        let imports = self.namespace_imports.entry(namespace).or_default();
207        if !imports
208            .iter()
209            .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
210        {
211            imports.push((declaration_byte, enum_owner));
212        }
213    }
214
215    pub fn resolve_member(
216        &self,
217        visibility: &VisibilityIndex<'_>,
218        file: &ProjectFile,
219        class: Option<&CodeUnit>,
220        namespace: &[String],
221        before_byte: usize,
222        name: &str,
223    ) -> SemanticUsingEnumMemberResolution {
224        if let Some(class) = class
225            && let Some((_, imports)) = self
226                .class_imports
227                .iter()
228                .find(|(owner, _)| same_visible_symbol(owner, class))
229        {
230            let resolution =
231                resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
232            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
233                return SemanticUsingEnumMemberResolution::Class(resolution);
234            }
235        }
236        for prefix_len in (0..=namespace.len()).rev() {
237            let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
238                continue;
239            };
240            let owners = imports
241                .iter()
242                .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
243                .map(|(_, owner)| owner);
244            let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
245            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
246                return SemanticUsingEnumMemberResolution::Namespace(resolution);
247            }
248        }
249        SemanticUsingEnumMemberResolution::Missing
250    }
251}
252
253fn resolve_using_enum_member_for_owners<'a>(
254    visibility: &VisibilityIndex<'_>,
255    file: &ProjectFile,
256    owners: impl IntoIterator<Item = &'a CodeUnit>,
257    name: &str,
258) -> UsingEnumMemberResolution {
259    let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
260    for owner in owners {
261        for member in visibility.visible_members_for_owner_name(file, owner, name) {
262            if !member.is_field()
263                || matches.iter().any(|(existing_owner, existing_member)| {
264                    same_visible_symbol(existing_owner, owner)
265                        && same_visible_symbol(existing_member, member)
266                })
267            {
268                continue;
269            }
270            matches.push((owner.clone(), member.clone()));
271        }
272    }
273    match matches.len() {
274        0 => UsingEnumMemberResolution::Missing,
275        1 => {
276            let (owner, member) = matches.pop().expect("one using-enum match");
277            UsingEnumMemberResolution::Resolved { owner, member }
278        }
279        _ => UsingEnumMemberResolution::Ambiguous,
280    }
281}
282
283impl ScopedUsingEnumOwners {
284    pub(crate) fn new() -> Self {
285        Self {
286            scopes: vec![Vec::new()],
287        }
288    }
289
290    pub fn enter_scope(&mut self) {
291        self.scopes.push(Vec::new());
292    }
293
294    pub fn exit_scope(&mut self) {
295        if self.scopes.len() > 1 {
296            self.scopes.pop();
297        }
298    }
299
300    pub fn import(&mut self, owner: CodeUnit) {
301        let scope = self
302            .scopes
303            .last_mut()
304            .expect("using-enum scope stack is never empty");
305        if !scope
306            .iter()
307            .any(|existing| same_visible_symbol(existing, &owner))
308        {
309            scope.push(owner);
310        }
311    }
312
313    pub fn resolve_member(
314        &self,
315        visibility: &VisibilityIndex<'_>,
316        file: &ProjectFile,
317        name: &str,
318    ) -> UsingEnumMemberResolution {
319        for scope in self.scopes.iter().rev() {
320            let resolution =
321                resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
322            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
323                return resolution;
324            }
325        }
326        UsingEnumMemberResolution::Missing
327    }
328}
329
330#[derive(Clone)]
331pub struct TargetSpec {
332    pub target: CodeUnit,
333    pub kind: TargetKind,
334    pub owner: Option<CodeUnit>,
335    pub member_name: String,
336    pub callable_arity: Option<CallableArity>,
337    pub activated_callable_arities: Vec<ActivatedCallableArity>,
338    pub param_types: Option<Vec<String>>,
339    pub enum_owner_kind: EnumOwnerKind,
340    pub owner_is_forward_declaration: bool,
341}
342
343#[derive(Clone, Copy)]
344pub struct ActivatedCallableArity {
345    pub activation_byte: usize,
346    pub arity: CallableArity,
347}
348
349#[derive(Debug, PartialEq, Eq, Hash)]
350pub struct TypeScanKey {
351    target: LogicalSymbolKey,
352    member_name: String,
353}
354
355#[derive(Clone, Debug, PartialEq, Eq, Hash)]
356struct LogicalSymbolKey {
357    kind: CodeUnitType,
358    fq_name: String,
359    signature: Option<String>,
360}
361
362struct ResolvedTypeOwner {
363    unit: CodeUnit,
364    is_forward_declaration: bool,
365}
366
367#[derive(Clone, Copy, PartialEq, Eq)]
368pub enum EnumOwnerKind {
369    Scoped,
370    Unscoped,
371    NonEnum,
372}
373
374impl TargetSpec {
375    pub fn type_scan_key(&self) -> Option<TypeScanKey> {
376        (self.kind == TargetKind::Type).then(|| TypeScanKey {
377            target: logical_symbol_key(&self.target),
378            member_name: self.member_name.clone(),
379        })
380    }
381
382    pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
383        if target.is_class() {
384            return Some(Self::new(
385                target.clone(),
386                TargetKind::Type,
387                Some(target.clone()),
388                target.identifier().to_string(),
389                None,
390                None,
391            ));
392        }
393
394        if target.is_field() {
395            // A namespace (module) is not a receiver: a namespace-scoped constant such as
396            // `example::DefaultPrefix` is referenced unqualified from inside the namespace and
397            // qualified from outside, exactly like a global. Treating a module owner as a
398            // member-field owner makes the receiver/owner-context match reject every valid
399            // reference, so resolve it as a global field instead.
400            let owner = type_owner_of(analyzer, target);
401            let kind = if owner.is_some() {
402                TargetKind::MemberField
403            } else {
404                TargetKind::GlobalField
405            };
406            let enum_owner_kind = owner
407                .as_ref()
408                .map(|owner| classify_enum_owner(analyzer, owner))
409                .unwrap_or(EnumOwnerKind::NonEnum);
410            let mut spec = Self::new(
411                target.clone(),
412                kind,
413                owner,
414                target.identifier().to_string(),
415                None,
416                None,
417            );
418            spec.enum_owner_kind = enum_owner_kind;
419            return Some(spec);
420        }
421
422        if target.is_function() {
423            // Free functions declared inside a namespace have a module owner; that namespace is
424            // not a call receiver, so resolve them as free functions rather than methods.
425            let owner_resolution = target_type_owner_resolution(analyzer, target);
426            let owner_is_forward_declaration = owner_resolution
427                .as_ref()
428                .is_some_and(|owner| owner.is_forward_declaration);
429            let owner = owner_resolution.map(|owner| owner.unit);
430            let kind = if owner.as_ref().is_some_and(|owner| {
431                target.identifier() == owner.identifier()
432                    || analyzer
433                        .cpp
434                        .and_then(|cpp| cpp.template_metadata(owner))
435                        .is_some_and(|metadata| metadata.primary_name == target.identifier())
436            }) {
437                TargetKind::Constructor
438            } else if owner.is_some() {
439                TargetKind::Method
440            } else {
441                TargetKind::FreeFunction
442            };
443            let mut spec = Self::new(
444                target.clone(),
445                kind,
446                owner,
447                target.identifier().to_string(),
448                Some(cpp_callable_arity(analyzer, target)),
449                cpp_callable_parameter_types(analyzer, target),
450            );
451            spec.owner_is_forward_declaration = owner_is_forward_declaration;
452            return Some(spec);
453        }
454
455        if target.is_macro() {
456            return Some(Self::new(
457                target.clone(),
458                TargetKind::Macro,
459                None,
460                target.identifier().to_string(),
461                None,
462                None,
463            ));
464        }
465
466        None
467    }
468
469    pub fn with_visible_callable_arities<'a>(
470        &'a self,
471        analyzer: &CppGraphSource<'_>,
472        cpp: &dyn CppSource,
473        visibility: &VisibilityIndex<'_>,
474        file: &ProjectFile,
475        prepared: &PreparedSyntaxTree,
476    ) -> Cow<'a, Self> {
477        let macro_parameter_arity =
478            visibility.callable_parameter_macro_arity(&self.target, self.target.signature());
479        let activated_callable_arities =
480            visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
481        if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
482            return Cow::Borrowed(self);
483        }
484        let mut effective = self.clone();
485        if let Some(macro_parameter_arity) = macro_parameter_arity {
486            effective.callable_arity = Some(macro_parameter_arity);
487        }
488        effective.activated_callable_arities = activated_callable_arities;
489        Cow::Owned(effective)
490    }
491
492    pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
493        let base = self.callable_arity?;
494        Some(
495            self.activated_callable_arities
496                .iter()
497                .filter(|candidate| candidate.activation_byte <= byte)
498                .fold(base, |arity, candidate| {
499                    merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
500                }),
501        )
502    }
503
504    pub fn new(
505        target: CodeUnit,
506        kind: TargetKind,
507        owner: Option<CodeUnit>,
508        member_name: String,
509        callable_arity: Option<CallableArity>,
510        param_types: Option<Vec<String>>,
511    ) -> Self {
512        Self {
513            target,
514            kind,
515            owner,
516            member_name,
517            callable_arity,
518            activated_callable_arities: Vec::new(),
519            param_types,
520            enum_owner_kind: EnumOwnerKind::NonEnum,
521            owner_is_forward_declaration: false,
522        }
523    }
524}
525
526fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
527    LogicalSymbolKey {
528        kind: unit.kind(),
529        fq_name: unit.fq_name(),
530        signature: unit.signature().map(str::to_string),
531    }
532}
533
534fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
535    let classify = |source: &str| {
536        let source = source.trim_start();
537        if source.starts_with("enum class ") || source.starts_with("enum struct ") {
538            Some(EnumOwnerKind::Scoped)
539        } else if source.starts_with("enum ") {
540            Some(EnumOwnerKind::Unscoped)
541        } else {
542            None
543        }
544    };
545    owner
546        .signature()
547        .and_then(classify)
548        .or_else(|| {
549            analyzer
550                .get_source(owner, false)
551                .as_deref()
552                .and_then(classify)
553        })
554        .unwrap_or(EnumOwnerKind::NonEnum)
555}
556
557#[derive(Clone, PartialEq, Eq, Hash)]
558pub struct CppScanBinding {
559    pub unit: Option<CodeUnit>,
560    pub type_name: Option<String>,
561    pub indirection: i32,
562}
563
564impl CppScanBinding {
565    pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
566        Self {
567            type_name: Some(cpp_name_for(&unit)),
568            unit: Some(unit),
569            indirection,
570        }
571    }
572
573    pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
574        Self {
575            type_name: Some(type_name),
576            unit,
577            indirection,
578        }
579    }
580
581    pub fn as_arg_type(&self) -> Option<CppArgType> {
582        let name = self
583            .type_name
584            .clone()
585            .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
586        Some(CppArgType {
587            name,
588            unit: self.unit.clone(),
589            indirection: self.indirection,
590            pointee_const: false,
591        })
592    }
593}
594
595type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
596type VisibleParserAliasTargetNamesCell = Arc<OnceLock<HashMap<String, HashSet<String>>>>;
597pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
598pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
599type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
600pub type MacroEnvironmentCursorCell = Arc<Mutex<MacroEnvironmentCursor>>;
601type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
602type MacroLocalBindingTemplateCache =
603    HashMap<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>;
604
605#[derive(Clone, Default)]
606pub struct MacroEnvironment {
607    bindings: HashMap<String, MacroBinding>,
608    known_undefined_names: HashSet<String>,
609    /// Names the translation unit's compile command proves defined (#2011):
610    /// the `-D`s that survive command ordering, intersected across every
611    /// configuration naming the TU. Seeded once at TU start. An explicit
612    /// `#undef` seen later lands in `known_undefined_names` and wins.
613    build_proven_defines: HashSet<String>,
614    unknown_names: bool,
615    applied_pragma_once_files: HashSet<ProjectFile>,
616    maybe_applied_pragma_once_files: HashSet<ProjectFile>,
617}
618
619#[derive(Default)]
620pub struct MacroEnvironmentCursor {
621    frontier: usize,
622    environment: Arc<MacroEnvironment>,
623}
624
625impl MacroEnvironment {
626    fn binding(&self, name: &str) -> Option<&MacroBinding> {
627        self.bindings.get(name)
628    }
629
630    fn may_bind(&self, name: &str) -> bool {
631        self.bindings.contains_key(name) || self.unknown_names
632    }
633
634    fn insert(&mut self, name: String, binding: MacroBinding) {
635        self.known_undefined_names.remove(&name);
636        self.bindings.insert(name, binding);
637    }
638
639    fn remove(&mut self, name: &str) {
640        self.bindings.remove(name);
641        self.known_undefined_names.insert(name.to_string());
642    }
643
644    fn remove_known_undefined(&mut self, name: &str) {
645        self.known_undefined_names.remove(name);
646    }
647
648    fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
649        for binding in self.bindings.values_mut() {
650            *binding = MacroBinding::uncertain_from(binding, source, byte);
651        }
652        self.known_undefined_names.clear();
653        // An untracked include could `#undef` a command-line define, so the
654        // may-hold filter must stop treating the build facts as decisive from
655        // here on. The additive proof path keeps its facts: they still hold at
656        // the include chain's activation point.
657        self.build_proven_defines.clear();
658        self.unknown_names = true;
659    }
660
661    fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
662        guards.iter().all(|guard| self.guard_may_hold(guard))
663    }
664
665    fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
666        let Some(expression) = guard.as_boolean_expression() else {
667            return true;
668        };
669        self.boolean_guard_may_hold(&expression)
670    }
671
672    fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
673        match expression {
674            BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
675            BooleanGuardExpression::Undefined(name) => {
676                self.bindings
677                    .get(name)
678                    .is_none_or(|binding| !binding.is_exact())
679                    && (!self.build_proven_defines.contains(name)
680                        || self.known_undefined_names.contains(name))
681            }
682            BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
683            BooleanGuardExpression::Opaque(_)
684            | BooleanGuardExpression::NegatedOpaque(_)
685            | BooleanGuardExpression::Constant(true) => true,
686            BooleanGuardExpression::Constant(false) => false,
687            BooleanGuardExpression::All(expressions) => expressions
688                .iter()
689                .all(|expression| self.boolean_guard_may_hold(expression)),
690            BooleanGuardExpression::Any(expressions) => expressions
691                .iter()
692                .any(|expression| self.boolean_guard_may_hold(expression)),
693        }
694    }
695}
696
697#[derive(Clone)]
698pub enum EffectiveUsingTarget {
699    Ordinary {
700        name: String,
701        target_components: Vec<String>,
702        global: bool,
703    },
704    Namespace {
705        namespace_components: Vec<String>,
706        global: bool,
707    },
708}
709
710#[derive(Clone)]
711pub struct OrdinaryTypeImport {
712    pub target: EffectiveUsingTarget,
713    pub source: ProjectFile,
714    pub declaration_byte: usize,
715    pub scope_start: usize,
716    pub scope_end: usize,
717    pub scope_depth: usize,
718    pub block_scope: bool,
719    pub lexical_depth: usize,
720    pub declaration_namespace: Vec<String>,
721    pub namespace_scope: Option<Vec<String>>,
722    pub resolved_target_components: Option<Vec<String>>,
723    pub required_guards: HashSet<PreprocessorGuard>,
724}
725
726#[derive(Clone)]
727pub struct ConditionalIncludeProjection {
728    pub activation_byte: usize,
729    pub required_guards: HashSet<PreprocessorGuard>,
730}
731
732#[derive(Default)]
733pub struct SourceUsingIndex {
734    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
735    pub directives: Vec<OrdinaryTypeImport>,
736}
737
738#[derive(Default)]
739pub struct ProjectUsingIndex {
740    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
741    pub directives: Vec<OrdinaryTypeImport>,
742}
743
744type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
745
746pub struct EffectiveUsingIndex {
747    projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
748}
749
750impl EffectiveUsingIndex {
751    fn new(_root: ProjectFile) -> Self {
752        Self {
753            projected_by_name: Mutex::new(HashMap::default()),
754        }
755    }
756
757    pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
758        self.projected_by_name
759            .lock()
760            .expect("C++ effective-using projection cache poisoned")
761            .entry(name.to_string())
762            .or_default()
763            .clone()
764    }
765}
766
767pub enum OrdinaryTypeImportResolution {
768    Resolved {
769        target: CodeUnit,
770        target_components: Vec<String>,
771        lexical_depth: usize,
772        is_direct: bool,
773    },
774    Ambiguous {
775        lexical_depth: usize,
776    },
777    Missing,
778}
779
780type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
781type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
782type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
783type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
784type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
785type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
786type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
787
788/// One callable declaration's inputs to [`VisibilityIndex::same_logical_callable`],
789/// read from its declaration syntax rather than from its persisted signature
790/// string: the comparable shape of each parameter, and the trailing identity
791/// suffix that shape does not carry.
792struct ExtractedComparable {
793    shapes: Vec<CppComparableSlot>,
794    suffix: String,
795}
796
797/// How many alias hops [`VisibilityIndex::same_logical_callable`] follows
798/// before giving up on a written type name. A visited set already stops a
799/// cycle; this stops an adversarially long chain from costing a lookup per hop.
800const MAX_COMPARABLE_ALIAS_HOPS: usize = 32;
801
802/// Per-query C++ visibility facts.
803///
804/// The analyzer is *borrowed*, never cloned: `TreeSitterAnalyzer::clone` gives
805/// the clone a fresh, empty `QueryReadCache` on purpose (clones cross
806/// generations and overlays, where another generation's hydrated states would
807/// be wrong). An index that owned a clone would therefore see an inactive read
808/// cache for every `prepared_syntax` call it makes, re-reading and re-parsing
809/// the same source from the store once per candidate instead of once per query
810/// — the #1175 blow-up, where one scan re-parsed a 4.8 MB generated header
811/// tens of thousands of times.
812pub struct VisibilityIndex<'a> {
813    cpp: &'a dyn CppSource,
814    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
815    visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
816    global_field_internal_linkage: HashMap<CodeUnit, bool>,
817    visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
818    alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
819    visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
820    visible_parser_alias_target_names:
821        Mutex<HashMap<ProjectFile, VisibleParserAliasTargetNamesCell>>,
822    ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
823    project_using_index: OnceLock<ProjectUsingIndex>,
824    callable_reference_specs:
825        Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
826    include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
827    compile_proven_guard_cells: Mutex<HashMap<ProjectFile, Arc<HashSet<PreprocessorGuard>>>>,
828    conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
829    #[cfg(any(test, feature = "test-support"))]
830    conditional_include_projection_index_build_count: AtomicUsize,
831    #[cfg(any(test, feature = "test-support"))]
832    conditional_include_projection_state_count: AtomicUsize,
833    #[cfg(any(test, feature = "test-support"))]
834    include_activation_build_count: AtomicUsize,
835    #[cfg(any(test, feature = "test-support"))]
836    using_donor_activation_count: AtomicUsize,
837    #[cfg(any(test, feature = "test-support"))]
838    using_namespace_lookup_count: AtomicUsize,
839    #[cfg(any(test, feature = "test-support"))]
840    using_name_candidate_inspection_count: AtomicUsize,
841    #[cfg(any(test, feature = "test-support"))]
842    callable_reference_spec_build_count: AtomicUsize,
843    #[cfg(any(test, feature = "test-support"))]
844    alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
845    #[cfg(any(test, feature = "test-support"))]
846    visible_parser_alias_name_set_build_count: AtomicUsize,
847    #[cfg(any(test, feature = "test-support"))]
848    visible_parser_alias_target_names_build_count: AtomicUsize,
849    field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
850    structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
851    callable_comparables: Mutex<HashMap<CodeUnit, Option<Arc<ExtractedComparable>>>>,
852    indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
853    indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
854    precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
855    macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
856    pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
857    // A forward cursor is useful only while its caller visits one source in byte order. The
858    // authoritative differential shares this index across target workers, whose frontiers can
859    // interleave arbitrarily, so sharing one cursor per file would serialize the include replay
860    // and repeatedly reset it. Keep one bounded cursor per participating worker instead; the
861    // immutable event and parse caches above remain shared.
862    pub macro_environment_cursors:
863        Mutex<HashMap<(ProjectFile, ThreadId), MacroEnvironmentCursorCell>>,
864    macro_replacements: Mutex<MacroReplacementCache>,
865    macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
866    callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
867    #[cfg(any(test, feature = "test-support"))]
868    pub macro_replacement_parse_count: AtomicUsize,
869    #[cfg(any(test, feature = "test-support"))]
870    pub macro_event_application_count: AtomicUsize,
871    #[cfg(any(test, feature = "test-support"))]
872    pub macro_environment_copy_count: AtomicUsize,
873    cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
874    cpp_template_families: HashMap<String, Vec<CodeUnit>>,
875    #[cfg(any(test, feature = "test-support"))]
876    qualified_candidate_inspections: AtomicUsize,
877    #[cfg(any(test, feature = "test-support"))]
878    target_preserving_type_resolution_count: AtomicUsize,
879}
880
881#[derive(Clone, Debug, PartialEq, Eq, Hash)]
882pub enum PreprocessorGuard {
883    Defined(String),
884    Undefined(String),
885    Boolean(BooleanGuardExpression),
886    Expression(String),
887    NegatedExpression(String),
888    Constant(bool),
889}
890
891#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
892pub enum BooleanGuardExpression {
893    Defined(String),
894    Undefined(String),
895    Truthy(String),
896    Falsy(String),
897    Opaque(String),
898    NegatedOpaque(String),
899    All(Vec<BooleanGuardExpression>),
900    Any(Vec<BooleanGuardExpression>),
901    Constant(bool),
902}
903
904impl BooleanGuardExpression {
905    fn negated(&self) -> Self {
906        match self {
907            Self::Defined(name) => Self::Undefined(name.clone()),
908            Self::Undefined(name) => Self::Defined(name.clone()),
909            Self::Truthy(name) => Self::Falsy(name.clone()),
910            Self::Falsy(name) => Self::Truthy(name.clone()),
911            Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
912            Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
913            Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
914            Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
915            Self::Constant(value) => Self::Constant(!value),
916        }
917    }
918
919    fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
920        Self::normalized(expressions, true)
921    }
922
923    fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
924        Self::normalized(expressions, false)
925    }
926
927    fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
928        let mut normalized = Vec::new();
929        for expression in expressions {
930            match expression {
931                Self::All(nested) if conjunction => normalized.extend(nested),
932                Self::Any(nested) if !conjunction => normalized.extend(nested),
933                Self::Constant(value) if value == conjunction => {}
934                Self::Constant(value) => return Self::Constant(value),
935                expression => normalized.push(expression),
936            }
937        }
938        normalized.sort_unstable();
939        normalized.dedup();
940        match normalized.len() {
941            0 => Self::Constant(conjunction),
942            1 => normalized.pop().expect("one Boolean guard expression"),
943            _ if conjunction => Self::All(normalized),
944            _ => Self::Any(normalized),
945        }
946    }
947
948    fn implies(&self, required: &Self) -> bool {
949        if self == required
950            || matches!(self, Self::Constant(false))
951            || matches!(required, Self::Constant(true))
952        {
953            return true;
954        }
955        match self {
956            Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
957            Self::All(active) => match required {
958                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
959                _ => active.iter().any(|expression| expression.implies(required)),
960            },
961            _ => match required {
962                Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
963                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
964                _ => false,
965            },
966        }
967    }
968
969    pub fn heap_size(&self) -> usize {
970        match self {
971            Self::Defined(value)
972            | Self::Undefined(value)
973            | Self::Truthy(value)
974            | Self::Falsy(value)
975            | Self::Opaque(value)
976            | Self::NegatedOpaque(value) => value.len(),
977            Self::All(expressions) | Self::Any(expressions) => {
978                expressions
979                    .iter()
980                    .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
981                        size.saturating_add(std::mem::size_of::<Self>())
982                            .saturating_add(expression.heap_size())
983                    })
984            }
985            Self::Constant(_) => 0,
986        }
987    }
988}
989
990impl PreprocessorGuard {
991    fn as_boolean_expression(&self) -> Option<BooleanGuardExpression> {
992        match self {
993            Self::Defined(name) => Some(BooleanGuardExpression::Defined(name.clone())),
994            Self::Undefined(name) => Some(BooleanGuardExpression::Undefined(name.clone())),
995            Self::Boolean(expression) => Some(expression.clone()),
996            Self::Constant(value) => Some(BooleanGuardExpression::Constant(*value)),
997            Self::Expression(_) | Self::NegatedExpression(_) => None,
998        }
999    }
1000
1001    fn negated(&self) -> Self {
1002        match self {
1003            Self::Defined(name) => Self::Undefined(name.clone()),
1004            Self::Undefined(name) => Self::Defined(name.clone()),
1005            Self::Boolean(expression) => Self::Boolean(expression.negated()),
1006            Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1007            Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1008            Self::Constant(value) => Self::Constant(!value),
1009        }
1010    }
1011
1012    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1013        match self {
1014            Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1015            // The expression has already been isolated structurally by
1016            // tree-sitter, but its full preprocessor semantics are outside the
1017            // analyzer's guard model. Any macro mutation can therefore change
1018            // its truth value.
1019            Self::Boolean(_) | Self::Expression(_) | Self::NegatedExpression(_) => true,
1020            Self::Constant(_) => false,
1021        }
1022    }
1023}
1024
1025#[derive(Clone, PartialEq, Eq)]
1026pub enum MacroDefinition {
1027    Object {
1028        replacement: String,
1029    },
1030    Function {
1031        parameters: Vec<String>,
1032        replacement: String,
1033    },
1034    Unsupported,
1035}
1036
1037#[derive(Clone, Debug, PartialEq, Eq)]
1038pub enum MacroIncludeProtection {
1039    MacroGuard(String),
1040    PragmaOnce,
1041    None,
1042}
1043
1044enum ParsedMacroReplacement {
1045    Parsed { source: String, tree: Tree },
1046    Unsupported,
1047}
1048
1049#[derive(Clone)]
1050enum MacroLocalBindingTypeTemplate {
1051    Parameter(usize),
1052    Fixed(String),
1053}
1054
1055#[derive(Clone)]
1056struct MacroLocalBindingTemplate {
1057    name: String,
1058    declared_type: MacroLocalBindingTypeTemplate,
1059    pointer_depth: i32,
1060}
1061
1062/// A local declaration contributed by one structurally known function-like macro.
1063///
1064/// `type_node` points into the invocation syntax when the replacement's type
1065/// is one of the macro parameters. Consumers can therefore use their normal
1066/// lexical type resolver without parsing replacement text themselves.
1067pub struct MacroLocalBinding<'tree> {
1068    pub name: String,
1069    pub type_name: String,
1070    pub type_node: Option<Node<'tree>>,
1071    pub pointer_depth: i32,
1072}
1073
1074#[derive(Clone, PartialEq, Eq)]
1075pub struct MacroBinding {
1076    source: ProjectFile,
1077    declaration_byte: usize,
1078    definition: MacroDefinition,
1079    exact: bool,
1080}
1081
1082impl MacroBinding {
1083    fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1084        Self {
1085            source: source.clone(),
1086            declaration_byte,
1087            definition: MacroDefinition::Unsupported,
1088            exact: false,
1089        }
1090    }
1091
1092    fn is_exact(&self) -> bool {
1093        self.exact
1094    }
1095
1096    fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1097        Self {
1098            source: source.clone(),
1099            declaration_byte,
1100            definition: current.definition.clone(),
1101            exact: false,
1102        }
1103    }
1104}
1105
1106#[derive(Clone)]
1107pub enum MacroEvent {
1108    Define {
1109        name: String,
1110        binding: MacroBinding,
1111        byte: usize,
1112        conditional: bool,
1113    },
1114    Undef {
1115        name: String,
1116        byte: usize,
1117        conditional: bool,
1118    },
1119    Include {
1120        targets: Vec<ProjectFile>,
1121        byte: usize,
1122        conditional: bool,
1123    },
1124    Invalidate {
1125        byte: usize,
1126    },
1127}
1128
1129impl MacroEvent {
1130    pub fn byte(&self) -> usize {
1131        match self {
1132            Self::Define { byte, .. }
1133            | Self::Undef { byte, .. }
1134            | Self::Include { byte, .. }
1135            | Self::Invalidate { byte } => *byte,
1136        }
1137    }
1138}
1139
1140#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1141pub enum CallArityEvidence {
1142    Exact(usize),
1143    Unknown,
1144}
1145
1146impl CallArityEvidence {
1147    pub fn exact(self) -> Option<usize> {
1148        match self {
1149            Self::Exact(arity) => Some(arity),
1150            Self::Unknown => None,
1151        }
1152    }
1153
1154    pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1155        self.exact().map(|arity| expected.accepts(arity))
1156    }
1157}
1158
1159#[derive(Clone)]
1160struct DeclaredFieldTypeFact {
1161    type_text: String,
1162    indirection: i32,
1163    template_arguments: Option<Vec<CppTemplateExpression>>,
1164}
1165
1166#[derive(Clone, PartialEq, Eq)]
1167enum StructuredAliasTarget {
1168    Builtin,
1169    Named {
1170        components: Vec<String>,
1171        global: bool,
1172        arguments: Option<Vec<CppTemplateExpression>>,
1173    },
1174}
1175
1176struct CppAlias {
1177    name: String,
1178    target: String,
1179    namespace: Option<String>,
1180}
1181
1182type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1183
1184/// Why template-argument resolution failed. Definition diagnostics render
1185/// each mode differently; graph scans only care that the resolution is
1186/// unproven and match `Err(_)`.
1187#[derive(Debug, Clone, PartialEq, Eq)]
1188pub enum CppTemplateResolutionError {
1189    /// A template alias expansion revisited `alias`.
1190    AliasCycle { alias: CodeUnit },
1191    /// The explicit arguments do not bind to the declared template parameters.
1192    ArgumentBinding,
1193    /// Bound arguments do not substitute into the alias target's arguments.
1194    Substitution,
1195    /// No visible primary template declaration could be selected and
1196    /// reconciled for the specialization family.
1197    PrimarySelection,
1198    /// More than one applicable specialization remains and none is strictly
1199    /// more specialized than every other candidate.
1200    AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1201}
1202
1203/// The ambiguity candidates, deduplicated to one representative per visible
1204/// symbol so a diagnostic lists each contender once.
1205fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1206    let mut distinct: Vec<CodeUnit> = Vec::new();
1207    for unit in units {
1208        if !distinct
1209            .iter()
1210            .any(|existing| same_visible_symbol(existing, unit))
1211        {
1212            distinct.push(unit.clone());
1213        }
1214    }
1215    distinct
1216}
1217
1218impl<'a> VisibilityIndex<'a> {
1219    pub fn cpp(&self) -> &'a dyn CppSource {
1220        self.cpp
1221    }
1222
1223    /// A [`VisibilityIndex`] over a caller-supplied visible-declaration map,
1224    /// bypassing the include-closure walk [`Self::build`] performs.
1225    ///
1226    /// The resolver's own unit tests drive the type-resolution paths against a
1227    /// hand-written visibility table; they live in `brokk-bifrost-analysis`
1228    /// because they need a real `CppAnalyzer`, so the struct literal they used
1229    /// to write inline is here instead of thirty-three public fields.
1230    #[cfg(any(test, feature = "test-support"))]
1231    pub fn from_visible_files_for_test(
1232        cpp: &'a dyn CppSource,
1233        visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1234    ) -> Self {
1235        let visible_source_files_by_root = visible_by_file
1236            .iter()
1237            .map(|(file, visible)| {
1238                (
1239                    file.clone(),
1240                    visible
1241                        .iter()
1242                        .map(|unit| unit.source().clone())
1243                        .chain(std::iter::once(file.clone()))
1244                        .collect(),
1245                )
1246            })
1247            .collect();
1248        let mut global_field_internal_linkage = HashMap::default();
1249        Self {
1250            cpp,
1251            visible_by_identifier: build_visible_identifier_index(
1252                &CppGraphSource::from_source(cpp),
1253                &visible_by_file,
1254                &visible_source_files_by_root,
1255                &mut global_field_internal_linkage,
1256            ),
1257            global_field_internal_linkage,
1258            visible_by_file,
1259            visible_source_files_by_root,
1260            alias_cells: Mutex::new(HashMap::default()),
1261            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1262            visible_parser_alias_target_names: Mutex::new(HashMap::default()),
1263            ordinary_type_import_cells: Mutex::new(HashMap::default()),
1264            project_using_index: OnceLock::new(),
1265            callable_reference_specs: Mutex::new(HashMap::default()),
1266            include_activation_cells: Mutex::new(HashMap::default()),
1267            compile_proven_guard_cells: Mutex::new(HashMap::default()),
1268            conditional_include_projection_cells: Mutex::new(HashMap::default()),
1269            conditional_include_projection_index_build_count: AtomicUsize::new(0),
1270            conditional_include_projection_state_count: AtomicUsize::new(0),
1271            include_activation_build_count: AtomicUsize::new(0),
1272            using_donor_activation_count: AtomicUsize::new(0),
1273            using_namespace_lookup_count: AtomicUsize::new(0),
1274            using_name_candidate_inspection_count: AtomicUsize::new(0),
1275            callable_reference_spec_build_count: AtomicUsize::new(0),
1276            alias_source_parse_counts: Mutex::new(HashMap::default()),
1277            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1278            visible_parser_alias_target_names_build_count: AtomicUsize::new(0),
1279            field_type_facts: Mutex::new(HashMap::default()),
1280            structured_alias_targets: Mutex::new(HashMap::default()),
1281            callable_comparables: Mutex::new(HashMap::default()),
1282            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1283            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1284            precise_parent_cache: Mutex::new(HashMap::default()),
1285            macro_event_cells: Mutex::new(HashMap::default()),
1286            macro_include_protection_cells: Mutex::new(HashMap::default()),
1287            macro_environment_cursors: Mutex::new(HashMap::default()),
1288            macro_replacements: Mutex::new(HashMap::default()),
1289            macro_local_binding_templates: Mutex::new(HashMap::default()),
1290            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1291            macro_replacement_parse_count: AtomicUsize::new(0),
1292            macro_event_application_count: AtomicUsize::new(0),
1293            macro_environment_copy_count: AtomicUsize::new(0),
1294            cpp_template_metadata: HashMap::default(),
1295            cpp_template_families: HashMap::default(),
1296            qualified_candidate_inspections: AtomicUsize::new(0),
1297            target_preserving_type_resolution_count: AtomicUsize::new(0),
1298        }
1299    }
1300
1301    /// The index's own C++ source, in the dispatching-analyzer shape.
1302    ///
1303    /// Four resolution paths reach the workspace through the C++ analyzer they
1304    /// already hold rather than through the analyzer the query was issued
1305    /// against; before the move they passed `&CppAnalyzer` straight into a
1306    /// `&dyn IAnalyzer` parameter. See [`CppGraphSource::from_source`].
1307    fn cpp_source(&self) -> CppGraphSource<'a> {
1308        CppGraphSource::from_source(self.cpp)
1309    }
1310
1311    pub fn build(
1312        cpp: &'a dyn CppSource,
1313        analyzer: &CppGraphSource<'_>,
1314        roots: &HashSet<ProjectFile>,
1315    ) -> Self {
1316        Self::build_with_cancellation(cpp, analyzer, roots, None)
1317    }
1318
1319    pub fn build_with_cancellation(
1320        cpp: &'a dyn CppSource,
1321        analyzer: &CppGraphSource<'_>,
1322        roots: &HashSet<ProjectFile>,
1323        cancellation: Option<&CancellationToken>,
1324    ) -> Self {
1325        let include_targets = cpp.include_target_index();
1326        let VisibilityData {
1327            mut visible_by_file,
1328            visible_source_files_by_root,
1329        } = build_visibility_data(
1330            roots,
1331            cancellation,
1332            |file| {
1333                let imports = analyzer.import_statements(file);
1334                cpp_include_paths(&imports)
1335                    .into_iter()
1336                    .flat_map(|include| {
1337                        resolve_include_targets_with_index(file, &include, include_targets)
1338                    })
1339                    .collect()
1340            },
1341            |root| analyzer.reference_uses_c_semantics(root),
1342            |file, c_semantics| analyzer.declarations_in_reading(file, c_semantics),
1343        );
1344        extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
1345        let mut global_field_internal_linkage = HashMap::default();
1346        let visible_by_identifier = build_visible_identifier_index(
1347            analyzer,
1348            &visible_by_file,
1349            &visible_source_files_by_root,
1350            &mut global_field_internal_linkage,
1351        );
1352        let mut cpp_template_metadata = HashMap::default();
1353        for unit in visible_by_file
1354            .values()
1355            .flatten()
1356            .filter(|unit| unit.is_class())
1357        {
1358            if cpp_template_metadata.contains_key(unit) {
1359                continue;
1360            }
1361            if let Some(metadata) = cpp.template_metadata(unit) {
1362                cpp_template_metadata.insert(unit.clone(), metadata);
1363            }
1364        }
1365        let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
1366        for (unit, metadata) in &cpp_template_metadata {
1367            cpp_template_families
1368                .entry(metadata.primary_fq_name.clone())
1369                .or_default()
1370                .push(unit.clone());
1371        }
1372        // `cpp_template_metadata` is hash-keyed on `CodeUnit`, so the push
1373        // order above is a function of those hashes. Two mirrored headers can
1374        // declare one specialization; `select_template_specialization` treats
1375        // them as interchangeable and returns the family's first entry, so an
1376        // unsorted family made the reported declaration depend on the
1377        // workspace's absolute path and on unrelated files (#1836). Order the
1378        // family exactly as `build_visible_identifier_index` orders its
1379        // per-identifier candidate lists.
1380        for family in cpp_template_families.values_mut() {
1381            sort_lookup_units(family);
1382        }
1383        Self {
1384            cpp,
1385            visible_by_file,
1386            visible_by_identifier,
1387            global_field_internal_linkage,
1388            visible_source_files_by_root,
1389            alias_cells: Mutex::new(HashMap::default()),
1390            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1391            visible_parser_alias_target_names: Mutex::new(HashMap::default()),
1392            ordinary_type_import_cells: Mutex::new(HashMap::default()),
1393            project_using_index: OnceLock::new(),
1394            callable_reference_specs: Mutex::new(HashMap::default()),
1395            include_activation_cells: Mutex::new(HashMap::default()),
1396            compile_proven_guard_cells: Mutex::new(HashMap::default()),
1397            conditional_include_projection_cells: Mutex::new(HashMap::default()),
1398            #[cfg(any(test, feature = "test-support"))]
1399            conditional_include_projection_index_build_count: AtomicUsize::new(0),
1400            #[cfg(any(test, feature = "test-support"))]
1401            conditional_include_projection_state_count: AtomicUsize::new(0),
1402            #[cfg(any(test, feature = "test-support"))]
1403            include_activation_build_count: AtomicUsize::new(0),
1404            #[cfg(any(test, feature = "test-support"))]
1405            using_donor_activation_count: AtomicUsize::new(0),
1406            #[cfg(any(test, feature = "test-support"))]
1407            using_namespace_lookup_count: AtomicUsize::new(0),
1408            #[cfg(any(test, feature = "test-support"))]
1409            using_name_candidate_inspection_count: AtomicUsize::new(0),
1410            #[cfg(any(test, feature = "test-support"))]
1411            callable_reference_spec_build_count: AtomicUsize::new(0),
1412            #[cfg(any(test, feature = "test-support"))]
1413            alias_source_parse_counts: Mutex::new(HashMap::default()),
1414            #[cfg(any(test, feature = "test-support"))]
1415            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1416            #[cfg(any(test, feature = "test-support"))]
1417            visible_parser_alias_target_names_build_count: AtomicUsize::new(0),
1418            field_type_facts: Mutex::new(HashMap::default()),
1419            structured_alias_targets: Mutex::new(HashMap::default()),
1420            callable_comparables: Mutex::new(HashMap::default()),
1421            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1422            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1423            precise_parent_cache: Mutex::new(HashMap::default()),
1424            macro_event_cells: Mutex::new(HashMap::default()),
1425            macro_include_protection_cells: Mutex::new(HashMap::default()),
1426            macro_environment_cursors: Mutex::new(HashMap::default()),
1427            macro_replacements: Mutex::new(HashMap::default()),
1428            macro_local_binding_templates: Mutex::new(HashMap::default()),
1429            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1430            #[cfg(any(test, feature = "test-support"))]
1431            macro_replacement_parse_count: AtomicUsize::new(0),
1432            #[cfg(any(test, feature = "test-support"))]
1433            macro_event_application_count: AtomicUsize::new(0),
1434            #[cfg(any(test, feature = "test-support"))]
1435            macro_environment_copy_count: AtomicUsize::new(0),
1436            cpp_template_metadata,
1437            cpp_template_families,
1438            #[cfg(any(test, feature = "test-support"))]
1439            qualified_candidate_inspections: AtomicUsize::new(0),
1440            #[cfg(any(test, feature = "test-support"))]
1441            target_preserving_type_resolution_count: AtomicUsize::new(0),
1442        }
1443    }
1444
1445    pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
1446        if file == target.source() {
1447            return true;
1448        }
1449        if self.global_field_has_internal_linkage(target) {
1450            return self
1451                .visible_source_files_by_root
1452                .get(file)
1453                .is_some_and(|sources| sources.contains(target.source()));
1454        }
1455        self.visible_by_file
1456            .get(file)
1457            .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
1458    }
1459
1460    fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
1461        self.global_field_internal_linkage
1462            .get(unit)
1463            .copied()
1464            .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
1465    }
1466
1467    pub fn call_arity_evidence(
1468        &self,
1469        file: &ProjectFile,
1470        call: Node<'_>,
1471        source: &str,
1472    ) -> CallArityEvidence {
1473        let Some(arguments) = call
1474            .child_by_field_name("arguments")
1475            .or_else(|| call.child_by_field_name("parameters"))
1476            .or_else(|| call.child_by_field_name("value"))
1477            .or_else(|| first_named_child_of_kind(call, "argument_list"))
1478            .or_else(|| first_named_child_of_kind(call, "initializer_list"))
1479        else {
1480            return CallArityEvidence::Exact(0);
1481        };
1482        let recovered_c_keyword_arguments =
1483            recovered_c_keyword_argument_count(file, call, arguments, source);
1484        let arguments = argument_children(arguments).collect::<Vec<_>>();
1485        if arguments
1486            .iter()
1487            .all(|argument| !argument_shape_may_change_arity(*argument))
1488        {
1489            return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
1490        }
1491        let environment = self.macro_environment(file, call.start_byte());
1492        let mut stack = Vec::new();
1493        let mut total = recovered_c_keyword_arguments;
1494        for argument in arguments {
1495            if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
1496                return CallArityEvidence::Unknown;
1497            }
1498            let CallArityEvidence::Exact(spread) =
1499                self.argument_arity_evidence(argument, source, &environment, &mut stack)
1500            else {
1501                return CallArityEvidence::Unknown;
1502            };
1503            total += spread;
1504        }
1505        CallArityEvidence::Exact(total)
1506    }
1507
1508    fn argument_arity_evidence(
1509        &self,
1510        argument: Node<'_>,
1511        source: &str,
1512        environment: &MacroEnvironment,
1513        stack: &mut Vec<(ProjectFile, usize)>,
1514    ) -> CallArityEvidence {
1515        let (name, invocation_arguments, function_like) = match argument.kind() {
1516            "identifier" => (node_text(argument, source), None, false),
1517            "call_expression" => {
1518                let Some(function) = argument.child_by_field_name("function") else {
1519                    return CallArityEvidence::Exact(1);
1520                };
1521                if function.kind() != "identifier" {
1522                    return CallArityEvidence::Exact(1);
1523                }
1524                let Some(arguments) = argument.child_by_field_name("arguments") else {
1525                    return CallArityEvidence::Exact(1);
1526                };
1527                (node_text(function, source), Some(arguments), true)
1528            }
1529            _ => return CallArityEvidence::Exact(1),
1530        };
1531        let Some(binding) = environment.binding(name) else {
1532            return if environment.unknown_names {
1533                CallArityEvidence::Unknown
1534            } else {
1535                CallArityEvidence::Exact(1)
1536            };
1537        };
1538        if !binding.is_exact() {
1539            return CallArityEvidence::Unknown;
1540        }
1541        match (&binding.definition, invocation_arguments, function_like) {
1542            (MacroDefinition::Object { replacement }, None, false) => self
1543                .replacement_arity_evidence(
1544                    replacement,
1545                    &[],
1546                    &[],
1547                    source,
1548                    environment,
1549                    stack,
1550                    binding,
1551                ),
1552            (
1553                MacroDefinition::Function {
1554                    parameters,
1555                    replacement,
1556                },
1557                Some(arguments),
1558                true,
1559            ) => {
1560                let actuals = argument_children(arguments).collect::<Vec<_>>();
1561                if actuals.len() != parameters.len() {
1562                    CallArityEvidence::Unknown
1563                } else {
1564                    self.replacement_arity_evidence(
1565                        replacement,
1566                        parameters,
1567                        &actuals,
1568                        source,
1569                        environment,
1570                        stack,
1571                        binding,
1572                    )
1573                }
1574            }
1575            (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
1576            _ => CallArityEvidence::Unknown,
1577        }
1578    }
1579
1580    #[allow(clippy::too_many_arguments)]
1581    fn replacement_arity_evidence(
1582        &self,
1583        replacement: &str,
1584        parameters: &[String],
1585        actuals: &[Node<'_>],
1586        actual_source: &str,
1587        environment: &MacroEnvironment,
1588        stack: &mut Vec<(ProjectFile, usize)>,
1589        binding: &MacroBinding,
1590    ) -> CallArityEvidence {
1591        let identity = (binding.source.clone(), binding.declaration_byte);
1592        if stack.contains(&identity) || replacement.trim().is_empty() {
1593            return CallArityEvidence::Unknown;
1594        }
1595        stack.push(identity);
1596        let parsed = self.parsed_macro_replacement(binding, replacement);
1597        let evidence = (|| {
1598            let ParsedMacroReplacement::Parsed {
1599                source: sentinel,
1600                tree,
1601            } = parsed.as_ref()
1602            else {
1603                return None;
1604            };
1605            let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
1606            let arguments = call.child_by_field_name("arguments")?;
1607            let mut total = 0usize;
1608            for argument in argument_children(arguments) {
1609                if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
1610                    return None;
1611                }
1612                if argument.kind() == "identifier"
1613                    && let Some(parameter_index) = parameters
1614                        .iter()
1615                        .position(|parameter| parameter == node_text(argument, sentinel))
1616                {
1617                    if !macro_expansion_shape_is_safe(
1618                        actuals[parameter_index],
1619                        actual_source,
1620                        &[],
1621                        environment,
1622                    ) {
1623                        return None;
1624                    }
1625                    let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
1626                        actuals[parameter_index],
1627                        actual_source,
1628                        environment,
1629                        stack,
1630                    ) else {
1631                        return None;
1632                    };
1633                    total += spread;
1634                    continue;
1635                }
1636                let CallArityEvidence::Exact(spread) =
1637                    self.argument_arity_evidence(argument, sentinel, environment, stack)
1638                else {
1639                    return None;
1640                };
1641                total += spread;
1642            }
1643            Some(CallArityEvidence::Exact(total))
1644        })()
1645        .unwrap_or(CallArityEvidence::Unknown);
1646        stack.pop();
1647        evidence
1648    }
1649
1650    fn parsed_macro_replacement(
1651        &self,
1652        binding: &MacroBinding,
1653        replacement: &str,
1654    ) -> Arc<ParsedMacroReplacement> {
1655        let key = (binding.source.clone(), binding.declaration_byte);
1656        let mut cache = self
1657            .macro_replacements
1658            .lock()
1659            .expect("C++ macro replacement cache poisoned");
1660        if let Some(parsed) = cache.get(&key) {
1661            return Arc::clone(parsed);
1662        }
1663        #[cfg(any(test, feature = "test-support"))]
1664        self.macro_replacement_parse_count
1665            .fetch_add(1, Ordering::Relaxed);
1666        let source =
1667            format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
1668        let mut parser = Parser::new();
1669        let parsed = parser
1670            .set_language(&tree_sitter_cpp::LANGUAGE.into())
1671            .ok()
1672            .and_then(|()| parser.parse(&source, None))
1673            .filter(|tree| !tree.root_node().has_error())
1674            .map_or(ParsedMacroReplacement::Unsupported, |tree| {
1675                ParsedMacroReplacement::Parsed { source, tree }
1676            });
1677        let parsed = Arc::new(parsed);
1678        cache.insert(key, Arc::clone(&parsed));
1679        parsed
1680    }
1681
1682    /// Recover a typed local declared by an active C function-like macro.
1683    ///
1684    /// This is intentionally narrower than macro expansion. The replacement
1685    /// must parse as one declaration, and the invocation must bind every
1686    /// formal parameter to one structured argument. That is sufficient for
1687    /// declaration macros such as `THIS(StorageAzure)`. An unavailable include
1688    /// can make the binding provisional without erasing its last known
1689    /// definition; an explicit conflicting definition still replaces it with
1690    /// Unsupported. Malformed and statement-producing macros also fail closed.
1691    pub fn function_macro_local_binding<'tree>(
1692        &self,
1693        file: &ProjectFile,
1694        statement: Node<'tree>,
1695        source: &str,
1696    ) -> Option<MacroLocalBinding<'tree>> {
1697        if !is_c_source_file(file) {
1698            return None;
1699        }
1700        let call = match statement.kind() {
1701            "call_expression" => statement,
1702            "expression_statement" if statement.named_child_count() == 1 => {
1703                statement.named_child(0)?
1704            }
1705            _ => return None,
1706        };
1707        if call.kind() != "call_expression" {
1708            return None;
1709        }
1710        let function = call.child_by_field_name("function")?;
1711        if function.kind() != "identifier" {
1712            return None;
1713        }
1714        let arguments = call.child_by_field_name("arguments")?;
1715        let actuals = argument_children(arguments).collect::<Vec<_>>();
1716        let environment = self.macro_environment(file, call.start_byte());
1717        let function_name = node_text(function, source);
1718        let binding = environment.binding(function_name)?;
1719        let MacroDefinition::Function {
1720            parameters,
1721            replacement,
1722        } = &binding.definition
1723        else {
1724            return None;
1725        };
1726        if actuals.len() != parameters.len() {
1727            return None;
1728        }
1729        let template = self.macro_local_binding_template(binding, parameters, replacement)?;
1730        let (type_name, type_node) = match &template.declared_type {
1731            MacroLocalBindingTypeTemplate::Parameter(index) => {
1732                let actual = *actuals.get(*index)?;
1733                if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
1734                    return None;
1735                }
1736                (node_text(actual, source).trim().to_string(), Some(actual))
1737            }
1738            MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
1739        };
1740        if type_name.is_empty() {
1741            return None;
1742        }
1743        Some(MacroLocalBinding {
1744            name: template.name.clone(),
1745            type_name,
1746            type_node,
1747            pointer_depth: template.pointer_depth,
1748        })
1749    }
1750
1751    fn macro_local_binding_template(
1752        &self,
1753        binding: &MacroBinding,
1754        parameters: &[String],
1755        replacement: &str,
1756    ) -> Option<Arc<MacroLocalBindingTemplate>> {
1757        let key = (binding.source.clone(), binding.declaration_byte);
1758        let mut cache = self
1759            .macro_local_binding_templates
1760            .lock()
1761            .expect("C++ macro local-binding cache poisoned");
1762        if let Some(template) = cache.get(&key) {
1763            return template.clone();
1764        }
1765        let sentinel = format!("void __bifrost_macro_local() {{ {replacement}; }}");
1766        let template = (|| {
1767            let mut parser = Parser::new();
1768            parser
1769                .set_language(&tree_sitter_cpp::LANGUAGE.into())
1770                .ok()?;
1771            let tree = parser.parse(&sentinel, None)?;
1772            if tree.root_node().has_error() {
1773                return None;
1774            }
1775            let function = first_descendant_of_kind(tree.root_node(), "function_definition")?;
1776            let body = function.child_by_field_name("body")?;
1777            if body.named_child_count() != 1 {
1778                return None;
1779            }
1780            let declaration = body.named_child(0)?;
1781            if declaration.kind() != "declaration" {
1782                return None;
1783            }
1784            let type_node = declaration
1785                .child_by_field_name("type")
1786                .or_else(|| first_type_child(declaration))?;
1787            let declarator = declaration.child_by_field_name("declarator").or_else(|| {
1788                let mut cursor = declaration.walk();
1789                declaration.named_children(&mut cursor).find_map(|child| {
1790                    if child.kind() == "init_declarator" {
1791                        child.child_by_field_name("declarator")
1792                    } else {
1793                        is_declarator_node(child).then_some(child)
1794                    }
1795                })
1796            })?;
1797            let name = extract_variable_name(declarator, &sentinel)?;
1798            let pointer_depth =
1799                declared_name_indirection(declaration, type_node, &name, &sentinel)?;
1800            let type_text = node_text(type_node, &sentinel).trim();
1801            let declared_type = parameters
1802                .iter()
1803                .position(|parameter| parameter == type_text)
1804                .map(MacroLocalBindingTypeTemplate::Parameter)
1805                .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
1806            Some(Arc::new(MacroLocalBindingTemplate {
1807                name,
1808                declared_type,
1809                pointer_depth,
1810            }))
1811        })();
1812        cache.insert(key, template.clone());
1813        template
1814    }
1815
1816    fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
1817        let Some(value) = node.child_by_field_name("value") else {
1818            return MacroDefinition::Unsupported;
1819        };
1820        let replacement = node_text(value, source).to_string();
1821        if node.kind() == "preproc_def" {
1822            return MacroDefinition::Object { replacement };
1823        }
1824        let Some(parameters) = node.child_by_field_name("parameters") else {
1825            return MacroDefinition::Unsupported;
1826        };
1827        if (0..parameters.child_count()).any(|index| {
1828            parameters
1829                .child(index)
1830                .is_some_and(|child| child.kind() == "...")
1831        }) {
1832            return MacroDefinition::Unsupported;
1833        }
1834        let parameters = (0..parameters.named_child_count())
1835            .filter_map(|index| parameters.named_child(index))
1836            .map(|parameter| node_text(parameter, source).to_string())
1837            .collect();
1838        MacroDefinition::Function {
1839            parameters,
1840            replacement,
1841        }
1842    }
1843
1844    pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
1845        self.macro_event_cells
1846            .lock()
1847            .expect("C++ macro event cache poisoned")
1848            .entry(file.clone())
1849            .or_default()
1850            .clone()
1851    }
1852
1853    pub fn macro_environment_cursor_cell(&self, file: &ProjectFile) -> MacroEnvironmentCursorCell {
1854        let key = (file.clone(), std::thread::current().id());
1855        self.macro_environment_cursors
1856            .lock()
1857            .expect("C++ macro environment cursor cache poisoned")
1858            .entry(key)
1859            .or_default()
1860            .clone()
1861    }
1862
1863    pub fn macro_environment(
1864        &self,
1865        file: &ProjectFile,
1866        before_byte: usize,
1867    ) -> Arc<MacroEnvironment> {
1868        let cell = self.macro_event_cell(file);
1869        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
1870        let frontier = events.partition_point(|event| event.byte() < before_byte);
1871        let cursor_cell = self.macro_environment_cursor_cell(file);
1872        let mut cursor = cursor_cell
1873            .lock()
1874            .expect("C++ macro environment cursor poisoned");
1875        if frontier < cursor.frontier {
1876            *cursor = MacroEnvironmentCursor::default();
1877        }
1878        // Seed the TU's build-proven defines once, before any event applies
1879        // (#2011). They are facts of the whole compile, so they hold from the
1880        // first byte; a later explicit #undef event still overrides them
1881        // through `known_undefined_names`.
1882        if cursor.frontier == 0 {
1883            let proven = self.compile_proven_guards(file);
1884            if !proven.is_empty() && cursor.environment.build_proven_defines.len() != proven.len() {
1885                Arc::make_mut(&mut cursor.environment).build_proven_defines = proven
1886                    .iter()
1887                    .filter_map(|guard| match guard {
1888                        PreprocessorGuard::Defined(name) => Some(name.clone()),
1889                        _ => None,
1890                    })
1891                    .collect();
1892            }
1893        }
1894        if frontier > cursor.frontier {
1895            #[cfg(any(test, feature = "test-support"))]
1896            if Arc::strong_count(&cursor.environment) > 1 {
1897                self.macro_environment_copy_count
1898                    .fetch_add(1, Ordering::Relaxed);
1899            }
1900            let start = cursor.frontier;
1901            let environment = Arc::make_mut(&mut cursor.environment);
1902            let mut include_stack = HashSet::from_iter([file.clone()]);
1903            for event in &events[start..frontier] {
1904                self.apply_macro_event(file, event, environment, &mut include_stack);
1905            }
1906            cursor.frontier = frontier;
1907        }
1908        Arc::clone(&cursor.environment)
1909    }
1910
1911    /// Whether `name` is bound as a macro at `before_byte` in `file`,
1912    /// including a binding this environment cannot pin to one replacement
1913    /// (a conditional `#define`, or a function-like macro).
1914    ///
1915    /// [`Self::object_macro_replacement_at`] collapses every such binding to
1916    /// `None`, which is indistinguishable from "not a macro at all". A caller
1917    /// that must not read a macro token as an ordinary type name needs the two
1918    /// apart: an unexpandable macro is an unknown, a plain identifier is not.
1919    pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
1920        self.macro_environment(file, before_byte)
1921            .binding(name)
1922            .is_some()
1923    }
1924
1925    pub fn macro_name_may_be_bound_at(
1926        &self,
1927        file: &ProjectFile,
1928        name: &str,
1929        before_byte: usize,
1930    ) -> bool {
1931        self.macro_environment(file, before_byte).may_bind(name)
1932    }
1933
1934    /// Whether the active macro binding at this reference is the requested
1935    /// indexed definition. Name equality alone is not enough because two
1936    /// headers can define the same macro for different translation units.
1937    pub fn macro_binding_matches_target_at(
1938        &self,
1939        analyzer: &CppGraphSource<'_>,
1940        file: &ProjectFile,
1941        name: &str,
1942        before_byte: usize,
1943        target: &CodeUnit,
1944    ) -> bool {
1945        let environment = self.macro_environment(file, before_byte);
1946        let Some(binding) = environment.binding(name) else {
1947            return false;
1948        };
1949        // A normal header guard makes the replacement text conditional, but
1950        // it does not erase the definition site's source and byte identity.
1951        // Keep that identity even when expansion details are not exact.
1952        if binding.source != *target.source() {
1953            return false;
1954        }
1955        let Some(prepared) = self.cpp.prepared_syntax(target.source()) else {
1956            return false;
1957        };
1958        analyzer.ranges(target).iter().any(|range| {
1959            let Some(mut node) = node_for_exact_range(prepared.tree().root_node(), range) else {
1960                return false;
1961            };
1962            while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
1963                let Some(parent) = node.parent() else {
1964                    return false;
1965                };
1966                node = parent;
1967            }
1968            node.start_byte() == binding.declaration_byte
1969        })
1970    }
1971
1972    /// Resolve an ordinary expression-position macro token at its exact byte.
1973    ///
1974    /// Calls and preprocessor-condition tokens have separate resolution
1975    /// surfaces. Declaration names, macro parameters, and labels are not
1976    /// references. Keeping that role policy here makes forward and both
1977    /// inverse graph builders consume the same activation verdict (#2093).
1978    pub fn resolve_ordinary_macro_reference(
1979        &self,
1980        analyzer: &CppGraphSource<'_>,
1981        file: &ProjectFile,
1982        node: Node<'_>,
1983        source: &str,
1984    ) -> OrdinaryMacroReferenceResolution {
1985        if !is_ordinary_macro_reference_node(node) {
1986            return OrdinaryMacroReferenceResolution::Missing;
1987        }
1988        let name = node_text(node, source);
1989        if name.is_empty() {
1990            return OrdinaryMacroReferenceResolution::Missing;
1991        }
1992        let visible = self
1993            .visible_identifier_candidates(file, name)
1994            .filter(|candidate| candidate.is_macro())
1995            .cloned()
1996            .collect::<Vec<_>>();
1997        let mut exact = Vec::new();
1998        for candidate in &visible {
1999            if self.macro_binding_matches_target_at(
2000                analyzer,
2001                file,
2002                name,
2003                node.start_byte(),
2004                candidate,
2005            ) && !exact
2006                .iter()
2007                .any(|existing| same_visible_symbol(existing, candidate))
2008            {
2009                exact.push(candidate.clone());
2010            }
2011        }
2012        match exact.len() {
2013            1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
2014            2.. => OrdinaryMacroReferenceResolution::Ambiguous,
2015            0 if !visible.is_empty()
2016                && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
2017            {
2018                OrdinaryMacroReferenceResolution::Ambiguous
2019            }
2020            0 => OrdinaryMacroReferenceResolution::Missing,
2021        }
2022    }
2023
2024    /// Collect reference-capable C tokens beneath tree-sitter recovery nodes.
2025    ///
2026    /// The ordinary census deliberately skips every `ERROR` subtree. This
2027    /// separate, precision-only frontier admits only roles that retain enough
2028    /// structure for the C usage graph to interpret independently (#2089).
2029    /// Macro evidence comes from this visibility index at the exact byte; no
2030    /// source-text parsing or terminal-name fallback is used.
2031    pub fn recovered_c_reference_ranges(
2032        &self,
2033        file: &ProjectFile,
2034        root: Node<'_>,
2035        source: &str,
2036        limit: usize,
2037    ) -> RecoveredCReferenceRanges {
2038        if !is_c_source_file(file) {
2039            return RecoveredCReferenceRanges::Complete(Vec::new());
2040        }
2041        let mut ranges = Vec::new();
2042        let mut seen = HashSet::default();
2043        let mut stack = vec![(root, root.is_error())];
2044        while let Some((node, inside_error)) = stack.pop() {
2045            let inside_error = inside_error || node.is_error();
2046            if inside_error
2047                && recovered_c_reference_node(self, file, node, source)
2048                && seen.insert((node.start_byte(), node.end_byte()))
2049            {
2050                if ranges.len() == limit {
2051                    return RecoveredCReferenceRanges::LimitExceeded;
2052                }
2053                ranges.push(Range {
2054                    start_byte: node.start_byte(),
2055                    end_byte: node.end_byte(),
2056                    start_line: node.start_position().row,
2057                    end_line: node.end_position().row,
2058                });
2059            }
2060            let mut cursor = node.walk();
2061            for child in node.named_children(&mut cursor) {
2062                stack.push((child, inside_error));
2063            }
2064        }
2065        ranges.sort_unstable();
2066        RecoveredCReferenceRanges::Complete(ranges)
2067    }
2068
2069    /// Whether this target is an indexed macro visible from this file.
2070    ///
2071    /// An unresolved conditional can make more than one same-name macro a
2072    /// possible active binding. Each possible target can keep the site as an
2073    /// unproven hit. A macro in an unrelated translation unit stays excluded.
2074    pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2075        self.visible_identifier_candidates(file, target.identifier())
2076            .filter(|candidate| candidate.is_macro())
2077            .any(|candidate| {
2078                candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
2079            })
2080    }
2081
2082    pub fn object_macro_replacement_at(
2083        &self,
2084        file: &ProjectFile,
2085        name: &str,
2086        before_byte: usize,
2087    ) -> Option<String> {
2088        let environment = self.macro_environment(file, before_byte);
2089        let binding = environment.binding(name)?;
2090        if !binding.exact {
2091            return None;
2092        }
2093        match &binding.definition {
2094            MacroDefinition::Object { replacement } => Some(replacement.clone()),
2095            MacroDefinition::Function { .. } | MacroDefinition::Unsupported => None,
2096        }
2097    }
2098
2099    fn apply_macro_events(
2100        &self,
2101        file: &ProjectFile,
2102        before_byte: Option<usize>,
2103        environment: &mut MacroEnvironment,
2104        include_stack: &mut HashSet<ProjectFile>,
2105    ) {
2106        if !include_stack.insert(file.clone()) {
2107            return;
2108        }
2109        if self.cpp.prepared_syntax(file).is_none() {
2110            environment.mark_unknown_names(file, before_byte.unwrap_or_default());
2111            include_stack.remove(file);
2112            return;
2113        }
2114        match self.macro_include_protection(file) {
2115            MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
2116                Some(binding) if binding.is_exact() => {
2117                    include_stack.remove(file);
2118                    return;
2119                }
2120                Some(_) | None if environment.unknown_names => {
2121                    let mut ambiguous_seen = HashSet::default();
2122                    self.mark_macro_events_ambiguous(
2123                        file,
2124                        environment,
2125                        &mut ambiguous_seen,
2126                        file,
2127                        before_byte.unwrap_or_default(),
2128                    );
2129                    include_stack.remove(file);
2130                    return;
2131                }
2132                Some(_) => {
2133                    let mut ambiguous_seen = HashSet::default();
2134                    self.mark_macro_events_ambiguous(
2135                        file,
2136                        environment,
2137                        &mut ambiguous_seen,
2138                        file,
2139                        before_byte.unwrap_or_default(),
2140                    );
2141                    include_stack.remove(file);
2142                    return;
2143                }
2144                None => {}
2145            },
2146            MacroIncludeProtection::PragmaOnce => {
2147                if !environment.applied_pragma_once_files.insert(file.clone()) {
2148                    include_stack.remove(file);
2149                    return;
2150                }
2151                if environment.maybe_applied_pragma_once_files.remove(file) {
2152                    // A prior conditional include may already have consumed the pragma-once
2153                    // header. This unconditional include guarantees it is consumed now, but
2154                    // cannot prove whether its events occur before or after intervening local
2155                    // macro changes, so preserve the union as ambiguous.
2156                    let mut ambiguous_seen = HashSet::default();
2157                    environment.applied_pragma_once_files.remove(file);
2158                    self.mark_macro_events_ambiguous(
2159                        file,
2160                        environment,
2161                        &mut ambiguous_seen,
2162                        file,
2163                        before_byte.unwrap_or_default(),
2164                    );
2165                    environment.maybe_applied_pragma_once_files.remove(file);
2166                    environment.applied_pragma_once_files.insert(file.clone());
2167                    include_stack.remove(file);
2168                    return;
2169                }
2170            }
2171            MacroIncludeProtection::None => {}
2172        }
2173        let cell = self.macro_event_cell(file);
2174        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2175        for event in events {
2176            if before_byte.is_some_and(|limit| event.byte() >= limit) {
2177                break;
2178            }
2179            self.apply_macro_event(file, event, environment, include_stack);
2180        }
2181        include_stack.remove(file);
2182    }
2183
2184    fn apply_macro_event(
2185        &self,
2186        file: &ProjectFile,
2187        event: &MacroEvent,
2188        environment: &mut MacroEnvironment,
2189        include_stack: &mut HashSet<ProjectFile>,
2190    ) {
2191        #[cfg(any(test, feature = "test-support"))]
2192        self.macro_event_application_count
2193            .fetch_add(1, Ordering::Relaxed);
2194        match event {
2195            MacroEvent::Define {
2196                name,
2197                binding,
2198                conditional,
2199                byte,
2200            } => {
2201                if *conditional {
2202                    Self::merge_conditional_macro_definition(
2203                        environment,
2204                        name,
2205                        binding,
2206                        file,
2207                        *byte,
2208                    );
2209                } else {
2210                    environment.insert(name.clone(), binding.clone());
2211                }
2212            }
2213            MacroEvent::Undef {
2214                name,
2215                conditional,
2216                byte,
2217            } => {
2218                if *conditional {
2219                    if environment.binding(name).is_some() {
2220                        environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
2221                    }
2222                } else {
2223                    environment.remove(name);
2224                }
2225            }
2226            MacroEvent::Include {
2227                targets,
2228                conditional,
2229                byte,
2230            } => {
2231                if targets.is_empty() {
2232                    environment.mark_unknown_names(file, *byte);
2233                    return;
2234                }
2235                if *conditional || targets.len() > 1 {
2236                    let mut ambiguous_seen = HashSet::default();
2237                    for target in targets {
2238                        self.mark_macro_events_ambiguous(
2239                            target,
2240                            environment,
2241                            &mut ambiguous_seen,
2242                            file,
2243                            *byte,
2244                        );
2245                    }
2246                } else if let Some(target) = targets.first() {
2247                    self.apply_macro_events(target, None, environment, include_stack);
2248                }
2249            }
2250            MacroEvent::Invalidate { byte } => {
2251                for binding in environment.bindings.values_mut() {
2252                    *binding = MacroBinding::uncertain_from(binding, file, *byte);
2253                }
2254            }
2255        }
2256    }
2257
2258    fn mark_macro_events_ambiguous(
2259        &self,
2260        file: &ProjectFile,
2261        environment: &mut MacroEnvironment,
2262        include_stack: &mut HashSet<ProjectFile>,
2263        conditional_file: &ProjectFile,
2264        conditional_byte: usize,
2265    ) {
2266        if !include_stack.insert(file.clone()) {
2267            return;
2268        }
2269        if self.cpp.prepared_syntax(file).is_none() {
2270            environment.mark_unknown_names(conditional_file, conditional_byte);
2271            return;
2272        }
2273        match self.macro_include_protection(file) {
2274            MacroIncludeProtection::MacroGuard(guard) => {
2275                if environment
2276                    .binding(&guard)
2277                    .is_some_and(MacroBinding::is_exact)
2278                {
2279                    return;
2280                }
2281            }
2282            MacroIncludeProtection::PragmaOnce => {
2283                if environment.applied_pragma_once_files.contains(file) {
2284                    return;
2285                }
2286                environment
2287                    .maybe_applied_pragma_once_files
2288                    .insert(file.clone());
2289            }
2290            MacroIncludeProtection::None => {}
2291        }
2292        let cell = self.macro_event_cell(file);
2293        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2294        for event in events {
2295            #[cfg(any(test, feature = "test-support"))]
2296            self.macro_event_application_count
2297                .fetch_add(1, Ordering::Relaxed);
2298            match event {
2299                MacroEvent::Define { name, binding, .. } => {
2300                    Self::merge_conditional_macro_definition(
2301                        environment,
2302                        name,
2303                        binding,
2304                        conditional_file,
2305                        conditional_byte,
2306                    );
2307                }
2308                MacroEvent::Undef { name, .. } => {
2309                    if environment.binding(name).is_some() {
2310                        environment.insert(
2311                            name.clone(),
2312                            MacroBinding::ambiguous(conditional_file, conditional_byte),
2313                        );
2314                    } else {
2315                        environment.remove_known_undefined(name);
2316                    }
2317                }
2318                MacroEvent::Include { targets, .. } => {
2319                    if targets.is_empty() {
2320                        environment.mark_unknown_names(conditional_file, conditional_byte);
2321                        continue;
2322                    }
2323                    for target in targets {
2324                        self.mark_macro_events_ambiguous(
2325                            target,
2326                            environment,
2327                            include_stack,
2328                            conditional_file,
2329                            conditional_byte,
2330                        );
2331                    }
2332                }
2333                MacroEvent::Invalidate { .. } => {
2334                    for binding in environment.bindings.values_mut() {
2335                        *binding = MacroBinding::uncertain_from(
2336                            binding,
2337                            conditional_file,
2338                            conditional_byte,
2339                        );
2340                    }
2341                }
2342            }
2343        }
2344    }
2345
2346    fn merge_conditional_macro_definition(
2347        environment: &mut MacroEnvironment,
2348        name: &str,
2349        possible_binding: &MacroBinding,
2350        conditional_file: &ProjectFile,
2351        conditional_byte: usize,
2352    ) {
2353        // A conditional include can revisit an already-active guarded header.
2354        // If the possible branch defines the exact same macro, both outcomes
2355        // leave the binding unchanged; degrading it to Unknown would discard
2356        // proof because of an unrelated unresolved macro name (#2092).
2357        if environment.binding(name).is_some_and(|current| {
2358            current.definition != MacroDefinition::Unsupported
2359                && current.definition == possible_binding.definition
2360        }) {
2361            return;
2362        }
2363        environment.insert(
2364            name.to_string(),
2365            MacroBinding::ambiguous(conditional_file, conditional_byte),
2366        );
2367    }
2368
2369    pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
2370        let cell = self
2371            .macro_include_protection_cells
2372            .lock()
2373            .expect("C++ include protection cache poisoned")
2374            .entry(file.clone())
2375            .or_default()
2376            .clone();
2377        cell.get_or_init(|| {
2378            self.cpp
2379                .prepared_syntax(file)
2380                .map_or(MacroIncludeProtection::None, |prepared| {
2381                    top_level_macro_include_protection(
2382                        prepared.tree().root_node(),
2383                        prepared.source(),
2384                    )
2385                })
2386        })
2387        .clone()
2388    }
2389
2390    fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
2391        let Some(prepared) = self.cpp.prepared_syntax(file) else {
2392            return Vec::new();
2393        };
2394        let source = prepared.source();
2395        let mut events = Vec::new();
2396        let mut stack = vec![prepared.tree().root_node()];
2397        while let Some(node) = stack.pop() {
2398            let conditional = has_preprocessor_conditional_ancestor(node, source);
2399            match node.kind() {
2400                "preproc_def" | "preproc_function_def" => {
2401                    let Some(name) = node.child_by_field_name("name") else {
2402                        continue;
2403                    };
2404                    let name = node_text(name, source).to_string();
2405                    events.push(MacroEvent::Define {
2406                        name,
2407                        binding: MacroBinding {
2408                            source: file.clone(),
2409                            declaration_byte: node.start_byte(),
2410                            definition: Self::decode_macro_definition(node, source),
2411                            exact: true,
2412                        },
2413                        byte: node.start_byte(),
2414                        conditional,
2415                    });
2416                    continue;
2417                }
2418                "preproc_include" => {
2419                    let Some(path) = node.child_by_field_name("path") else {
2420                        events.push(MacroEvent::Include {
2421                            targets: Vec::new(),
2422                            byte: node.start_byte(),
2423                            conditional,
2424                        });
2425                        continue;
2426                    };
2427                    let targets =
2428                        structured_include_path(path, source).map_or_else(Vec::new, |path| {
2429                            resolve_include_targets_with_index(
2430                                file,
2431                                path,
2432                                self.cpp.include_target_index(),
2433                            )
2434                        });
2435                    // An unresolved angle-bracket include crosses into an external system
2436                    // boundary that is absent from the source index. It must not poison all
2437                    // later local macro evidence. Quoted/project-local and computed includes,
2438                    // by contrast, may hide indexed macro state and therefore fail closed.
2439                    if targets.is_empty() && path.kind() == "system_lib_string" {
2440                        continue;
2441                    }
2442                    events.push(MacroEvent::Include {
2443                        targets,
2444                        byte: node.start_byte(),
2445                        conditional,
2446                    });
2447                    continue;
2448                }
2449                "preproc_call" => {
2450                    let Some(directive) = node.child_by_field_name("directive") else {
2451                        continue;
2452                    };
2453                    if node_text(directive, source) != "#undef" {
2454                        continue;
2455                    }
2456                    let name = node
2457                        .child_by_field_name("argument")
2458                        .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
2459                    if let Some(name) = name {
2460                        events.push(MacroEvent::Undef {
2461                            name,
2462                            byte: node.start_byte(),
2463                            conditional,
2464                        });
2465                    } else {
2466                        events.push(MacroEvent::Invalidate {
2467                            byte: node.start_byte(),
2468                        });
2469                    }
2470                    continue;
2471                }
2472                _ => {}
2473            }
2474            for index in (0..node.named_child_count()).rev() {
2475                if let Some(child) = node.named_child(index) {
2476                    stack.push(child);
2477                }
2478            }
2479        }
2480        events.sort_by_key(MacroEvent::byte);
2481        events
2482    }
2483
2484    pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
2485        self.ordinary_type_import_cells
2486            .lock()
2487            .expect("C++ ordinary type import cache poisoned")
2488            .entry(file.clone())
2489            .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
2490            .clone()
2491    }
2492
2493    pub fn project_using_index(
2494        &self,
2495        build: impl FnOnce() -> ProjectUsingIndex,
2496    ) -> &ProjectUsingIndex {
2497        self.project_using_index.get_or_init(build)
2498    }
2499
2500    pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
2501        let mut files = self
2502            .visible_source_files_by_root
2503            .values()
2504            .flatten()
2505            .cloned()
2506            .collect::<HashSet<_>>()
2507            .into_iter()
2508            .collect::<Vec<_>>();
2509        files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
2510        files
2511    }
2512
2513    pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
2514        self.visible_source_files_by_root
2515            .get(root)
2516            .is_some_and(|files| files.contains(source))
2517    }
2518
2519    fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
2520        let cached = self
2521            .visible_parser_alias_name_sets
2522            .read()
2523            .expect("visible parser alias-name cache poisoned")
2524            .get(file)
2525            .cloned();
2526        let cell = if let Some(cached) = cached {
2527            cached
2528        } else {
2529            let mut cells = self
2530                .visible_parser_alias_name_sets
2531                .write()
2532                .expect("visible parser alias-name cache poisoned");
2533            Arc::clone(
2534                cells
2535                    .entry(file.clone())
2536                    .or_insert_with(|| Arc::new(OnceLock::new())),
2537            )
2538        };
2539        cell.get_or_init(|| {
2540            #[cfg(any(test, feature = "test-support"))]
2541            self.visible_parser_alias_name_set_build_count
2542                .fetch_add(1, Ordering::Relaxed);
2543            let mut names = HashSet::default();
2544            let visible_files = self
2545                .visible_source_files_by_root
2546                .get(file)
2547                .cloned()
2548                .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
2549            for visible_file in visible_files {
2550                let aliases = {
2551                    let mut cells = self.alias_cells.lock().expect("alias cell map lock");
2552                    Arc::clone(
2553                        cells
2554                            .entry(visible_file.clone())
2555                            .or_insert_with(|| Arc::new(OnceLock::new())),
2556                    )
2557                };
2558                for alias in aliases
2559                    .get_or_init(|| {
2560                        #[cfg(any(test, feature = "test-support"))]
2561                        {
2562                            *self
2563                                .alias_source_parse_counts
2564                                .lock()
2565                                .expect("alias source parse count lock")
2566                                .entry(visible_file.clone())
2567                                .or_default() += 1;
2568                        }
2569                        aliases_from_prepared_source(self.cpp, &visible_file).into_boxed_slice()
2570                    })
2571                    .iter()
2572                {
2573                    names.insert(alias.name.clone());
2574                }
2575            }
2576            names
2577        })
2578        .contains(name)
2579    }
2580
2581    fn visible_parser_alias_names_for_target(
2582        &self,
2583        file: &ProjectFile,
2584        target: &CodeUnit,
2585    ) -> HashSet<String> {
2586        let cell = {
2587            let mut cells = self
2588                .visible_parser_alias_target_names
2589                .lock()
2590                .expect("visible parser alias-target cache poisoned");
2591            Arc::clone(
2592                cells
2593                    .entry(file.clone())
2594                    .or_insert_with(|| Arc::new(OnceLock::new())),
2595            )
2596        };
2597        let target_name = cpp_name_for(target);
2598        cell.get_or_init(|| {
2599            #[cfg(any(test, feature = "test-support"))]
2600            self.visible_parser_alias_target_names_build_count
2601                .fetch_add(1, Ordering::Relaxed);
2602            let visible_files = self
2603                .visible_source_files_by_root
2604                .get(file)
2605                .cloned()
2606                .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
2607            let mut names_by_target = HashMap::<String, HashSet<String>>::default();
2608            for visible_file in visible_files {
2609                let aliases = {
2610                    let mut cells = self.alias_cells.lock().expect("alias cell map lock");
2611                    Arc::clone(
2612                        cells
2613                            .entry(visible_file.clone())
2614                            .or_insert_with(|| Arc::new(OnceLock::new())),
2615                    )
2616                };
2617                for alias in aliases
2618                    .get_or_init(|| {
2619                        #[cfg(any(test, feature = "test-support"))]
2620                        {
2621                            *self
2622                                .alias_source_parse_counts
2623                                .lock()
2624                                .expect("alias source parse count lock")
2625                                .entry(visible_file.clone())
2626                                .or_default() += 1;
2627                        }
2628                        aliases_from_prepared_source(self.cpp, &visible_file).into_boxed_slice()
2629                    })
2630                    .iter()
2631                {
2632                    for target_name in parser_alias_target_names(alias) {
2633                        names_by_target
2634                            .entry(target_name)
2635                            .or_default()
2636                            .insert(alias.name.clone());
2637                    }
2638                }
2639            }
2640            names_by_target
2641        })
2642        .get(&target_name)
2643        .cloned()
2644        .unwrap_or_default()
2645    }
2646
2647    fn callable_arities_for_target(
2648        &self,
2649        analyzer: &CppGraphSource<'_>,
2650        cpp: &dyn CppSource,
2651        file: &ProjectFile,
2652        prepared: &PreparedSyntaxTree,
2653        spec: &TargetSpec,
2654    ) -> Vec<ActivatedCallableArity> {
2655        let Some(signature) = spec.target.signature() else {
2656            return Vec::new();
2657        };
2658        let Some(candidates) = self
2659            .visible_by_identifier
2660            .get(file)
2661            .and_then(|by_name| by_name.get(&spec.member_name))
2662        else {
2663            return Vec::new();
2664        };
2665        let differing_candidates = candidates
2666            .iter()
2667            .filter(|candidate| {
2668                candidate.is_function()
2669                    && candidate.fq_name() == spec.target.fq_name()
2670                    && candidate.signature() == Some(signature)
2671            })
2672            .filter_map(|candidate| {
2673                analyzer
2674                    .signature_metadata(candidate)
2675                    .into_iter()
2676                    .find_map(|metadata| metadata.callable_arity())
2677                    .filter(|arity| Some(*arity) != spec.callable_arity)
2678                    .map(|arity| (candidate, arity))
2679            })
2680            .collect::<Vec<_>>();
2681        if differing_candidates.is_empty() {
2682            return Vec::new();
2683        }
2684        let mut arities = Vec::with_capacity(differing_candidates.len());
2685        // The activation ranges here describe the whole file rather than one
2686        // reference, so there is no reference guard environment to consult.
2687        let reference = CallableReferenceContext {
2688            file,
2689            position: None,
2690        };
2691        for (candidate, candidate_arity) in differing_candidates {
2692            let declaration_activation = if candidate.source() == file {
2693                callable_declaration_activation_in_file(analyzer, prepared, candidate, &reference)
2694            } else {
2695                cpp.prepared_syntax(candidate.source()).and_then(|syntax| {
2696                    callable_declaration_activation_in_file(
2697                        analyzer,
2698                        syntax.as_ref(),
2699                        candidate,
2700                        &reference,
2701                    )
2702                })
2703            };
2704            let Some(declaration_activation) = declaration_activation else {
2705                continue;
2706            };
2707            let activation_byte = if candidate.source() == file {
2708                Some(declaration_activation)
2709            } else {
2710                self.include_activation_for_source(cpp, file, prepared, candidate.source())
2711            };
2712            if let Some(activation_byte) = activation_byte {
2713                arities.push(ActivatedCallableArity {
2714                    activation_byte,
2715                    arity: candidate_arity,
2716                });
2717            }
2718        }
2719        arities
2720    }
2721
2722    fn callable_parameter_macro_arity(
2723        &self,
2724        target: &CodeUnit,
2725        signature: Option<&str>,
2726    ) -> Option<CallableArity> {
2727        let parameter_types = cpp_signature_param_types(signature?)?;
2728        let [macro_name] = parameter_types.as_slice() else {
2729            return None;
2730        };
2731        if macro_name.is_empty()
2732            || !macro_name
2733                .chars()
2734                .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
2735        {
2736            return None;
2737        }
2738        let cache_key = (target.source().clone(), macro_name.clone());
2739        if let Some(cached) = self
2740            .callable_parameter_macro_arities
2741            .lock()
2742            .expect("C++ callable parameter-macro arity cache poisoned")
2743            .get(&cache_key)
2744            .copied()
2745        {
2746            return cached;
2747        }
2748        let mut visible_files = HashSet::default();
2749        collect_include_closure(
2750            &self.cpp_source(),
2751            self.cpp.include_target_index(),
2752            target.source(),
2753            &mut visible_files,
2754            None,
2755        );
2756        let mut arities = Vec::new();
2757        for visible_file in visible_files {
2758            let cell = self.macro_event_cell(&visible_file);
2759            for event in
2760                cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
2761            {
2762                let MacroEvent::Define { name, binding, .. } = event else {
2763                    continue;
2764                };
2765                if name != macro_name {
2766                    continue;
2767                }
2768                let MacroDefinition::Object { replacement } = &binding.definition else {
2769                    continue;
2770                };
2771                let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
2772                    continue;
2773                };
2774                if !arities.contains(&arity) {
2775                    arities.push(arity);
2776                }
2777            }
2778        }
2779        let resolved = (|| {
2780            let required = arities
2781                .iter()
2782                .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
2783                .min()?;
2784            let total = arities.iter().map(|arity| arity.total()).max()?;
2785            let repeated = arities
2786                .iter()
2787                .any(|arity| arity.accepts(arity.total().saturating_add(1)));
2788            // Preprocessor conditions can leave more than one object-like parameter
2789            // bundle active in the target header's include closure. Preserve their
2790            // conservative callable envelope instead of choosing whichever definition
2791            // happened to be visited first.
2792            Some(CallableArity::new(required, total, repeated))
2793        })();
2794        self.callable_parameter_macro_arities
2795            .lock()
2796            .expect("C++ callable parameter-macro arity cache poisoned")
2797            .insert(cache_key, resolved);
2798        resolved
2799    }
2800
2801    pub fn include_activation_for_source(
2802        &self,
2803        cpp: &dyn CppSource,
2804        file: &ProjectFile,
2805        prepared: &PreparedSyntaxTree,
2806        donor_source: &ProjectFile,
2807    ) -> Option<usize> {
2808        let key = (file.clone(), donor_source.clone());
2809        if let Some(cached) = self
2810            .include_activation_cells
2811            .lock()
2812            .expect("C++ include activation cache poisoned")
2813            .get(&key)
2814            .copied()
2815        {
2816            return cached;
2817        }
2818        #[cfg(any(test, feature = "test-support"))]
2819        self.include_activation_build_count
2820            .fetch_add(1, Ordering::Relaxed);
2821        let activation = find_include_activation(cpp, file, prepared, donor_source);
2822        let mut cells = self
2823            .include_activation_cells
2824            .lock()
2825            .expect("C++ include activation cache poisoned");
2826        *cells.entry(key).or_insert(activation)
2827    }
2828
2829    pub fn conditional_include_projections_for_source(
2830        &self,
2831        file: &ProjectFile,
2832        prepared: &PreparedSyntaxTree,
2833        donor_source: &ProjectFile,
2834    ) -> Arc<[ConditionalIncludeProjection]> {
2835        static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
2836        let cell = self
2837            .conditional_include_projection_cells
2838            .lock()
2839            .expect("C++ conditional include projection cache poisoned")
2840            .entry(file.clone())
2841            .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
2842            .clone();
2843        let index = cell.get_or_build_pool_independent(|| {
2844            #[cfg(any(test, feature = "test-support"))]
2845            self.conditional_include_projection_index_build_count
2846                .fetch_add(1, Ordering::Relaxed);
2847            find_conditional_include_projection_index(self.cpp, file, prepared, &|| {
2848                #[cfg(any(test, feature = "test-support"))]
2849                self.conditional_include_projection_state_count
2850                    .fetch_add(1, Ordering::Relaxed);
2851            })
2852        });
2853        index
2854            .get(donor_source)
2855            .cloned()
2856            .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
2857    }
2858
2859    #[cfg(any(test, feature = "test-support"))]
2860    pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
2861        (
2862            self.conditional_include_projection_index_build_count
2863                .load(Ordering::Relaxed),
2864            self.conditional_include_projection_state_count
2865                .load(Ordering::Relaxed),
2866        )
2867    }
2868
2869    #[cfg(any(test, feature = "test-support"))]
2870    pub fn include_activation_build_count_for_test(&self) -> usize {
2871        self.include_activation_build_count.load(Ordering::Relaxed)
2872    }
2873
2874    #[cfg(any(test, feature = "test-support"))]
2875    pub fn note_using_donor_activation_for_test(&self) {
2876        self.using_donor_activation_count
2877            .fetch_add(1, Ordering::Relaxed);
2878    }
2879
2880    #[cfg(not(any(test, feature = "test-support")))]
2881    pub fn note_using_donor_activation_for_test(&self) {}
2882
2883    #[cfg(any(test, feature = "test-support"))]
2884    pub fn note_using_namespace_lookup_for_test(&self) {
2885        self.using_namespace_lookup_count
2886            .fetch_add(1, Ordering::Relaxed);
2887    }
2888
2889    #[cfg(not(any(test, feature = "test-support")))]
2890    pub fn note_using_namespace_lookup_for_test(&self) {}
2891
2892    #[cfg(any(test, feature = "test-support"))]
2893    pub fn note_using_name_candidate_inspection_for_test(&self) {
2894        self.using_name_candidate_inspection_count
2895            .fetch_add(1, Ordering::Relaxed);
2896    }
2897
2898    #[cfg(not(any(test, feature = "test-support")))]
2899    pub fn note_using_name_candidate_inspection_for_test(&self) {}
2900
2901    #[cfg(any(test, feature = "test-support"))]
2902    pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
2903        (
2904            self.using_donor_activation_count.load(Ordering::Relaxed),
2905            self.using_namespace_lookup_count.load(Ordering::Relaxed),
2906            self.callable_reference_spec_build_count
2907                .load(Ordering::Relaxed),
2908            self.using_name_candidate_inspection_count
2909                .load(Ordering::Relaxed),
2910        )
2911    }
2912
2913    pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2914        file == target.source()
2915            || self
2916                .visible_by_file
2917                .get(file)
2918                .is_some_and(|visible| visible.contains(target))
2919    }
2920
2921    /// Whether some declaration of `declaration`'s logical symbol is visible at
2922    /// `reference_byte` in `file`.
2923    ///
2924    /// The question is asked of the *logical* symbol, not of the physical unit:
2925    /// an out-of-line body in a `.cpp` nobody includes is never itself visible,
2926    /// and it does not have to be - what makes the call legal is the header
2927    /// declaration that the reference file does include. Reading that relation
2928    /// through `same_logical_callable` rather than through signature strings is
2929    /// the same #2010 correction the gates make, and it matters here because
2930    /// the body and the declaration are exactly the pair that spells one
2931    /// parameter type two ways.
2932    pub fn declaration_visible_at(
2933        &self,
2934        analyzer: &CppGraphSource<'_>,
2935        file: &ProjectFile,
2936        declaration: &CodeUnit,
2937        reference_byte: usize,
2938    ) -> bool {
2939        let reference_guards = OnceCell::new();
2940        self.visible_identifier_candidates(file, declaration.identifier())
2941            .filter(|candidate| {
2942                self.same_logical_callable(analyzer, candidate, declaration)
2943                    || flattened_macro_namespace_declaration_matches(
2944                        analyzer,
2945                        self.cpp,
2946                        file,
2947                        candidate,
2948                        declaration,
2949                        reference_byte,
2950                    )
2951            })
2952            .any(|candidate| {
2953                self.physical_declaration_visible_at(
2954                    analyzer,
2955                    file,
2956                    candidate,
2957                    reference_byte,
2958                    &reference_guards,
2959                )
2960            })
2961    }
2962
2963    pub fn callable_arity_at_reference(
2964        &self,
2965        analyzer: &CppGraphSource<'_>,
2966        file: &ProjectFile,
2967        candidate: &CodeUnit,
2968        reference_byte: usize,
2969    ) -> Option<CallableArity> {
2970        let key = (file.clone(), logical_symbol_key(candidate));
2971        let cell = self
2972            .callable_reference_specs
2973            .lock()
2974            .expect("C++ callable reference-spec cache poisoned")
2975            .entry(key)
2976            .or_default()
2977            .clone();
2978        let spec = cell.get_or_init(|| {
2979            let prepared = self.cpp.prepared_syntax(file)?;
2980            let spec = TargetSpec::from_target(analyzer, candidate)?;
2981            let spec = spec
2982                .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
2983                .into_owned();
2984            #[cfg(any(test, feature = "test-support"))]
2985            self.callable_reference_spec_build_count
2986                .fetch_add(1, Ordering::Relaxed);
2987            Some(spec)
2988        });
2989        spec.as_ref()?.callable_arity_at(reference_byte)
2990    }
2991
2992    fn physical_declaration_visible_at(
2993        &self,
2994        analyzer: &CppGraphSource<'_>,
2995        file: &ProjectFile,
2996        declaration: &CodeUnit,
2997        reference_byte: usize,
2998        reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
2999    ) -> bool {
3000        let Some(prepared) = self.cpp.prepared_syntax(file) else {
3001            return false;
3002        };
3003        let reference = CallableReferenceContext {
3004            file,
3005            position: Some(CallableReferencePosition {
3006                prepared: prepared.as_ref(),
3007                byte: reference_byte,
3008                guards: reference_guards,
3009            }),
3010        };
3011        if declaration.source() == file {
3012            return callable_declaration_activation_in_file(
3013                analyzer,
3014                prepared.as_ref(),
3015                declaration,
3016                &reference,
3017            )
3018            .or_else(|| {
3019                self.exhaustive_guard_family_activation(
3020                    analyzer,
3021                    prepared.as_ref(),
3022                    declaration,
3023                    &reference,
3024                )
3025            })
3026            .is_some_and(|activation| activation < reference_byte);
3027        }
3028        let Some(donor_syntax) = self.cpp.prepared_syntax(declaration.source()) else {
3029            return false;
3030        };
3031        if callable_declaration_activation_in_file(
3032            analyzer,
3033            donor_syntax.as_ref(),
3034            declaration,
3035            &reference,
3036        )
3037        .or_else(|| {
3038            self.exhaustive_guard_family_activation(
3039                analyzer,
3040                donor_syntax.as_ref(),
3041                declaration,
3042                &reference,
3043            )
3044        })
3045        .is_none()
3046        {
3047            return false;
3048        }
3049        declaration_guard_requirements(analyzer, self.cpp, declaration)
3050            .into_iter()
3051            .any(|(_, declaration_guards)| {
3052                self.foreign_declaration_reachable_at_reference(
3053                    file,
3054                    prepared.as_ref(),
3055                    declaration.source(),
3056                    &declaration_guards,
3057                    reference.guards(),
3058                    reference_byte,
3059                )
3060            })
3061    }
3062
3063    pub fn external_type_candidate_visible_at(
3064        &self,
3065        file: &ProjectFile,
3066        candidate: &CodeUnit,
3067        reference_byte: usize,
3068    ) -> bool {
3069        if candidate.source() == file {
3070            return true;
3071        }
3072        let Some(prepared) = self.cpp.prepared_syntax(file) else {
3073            return false;
3074        };
3075        self.visible_identifier_candidates(file, candidate.identifier())
3076            .filter(|peer| same_logical_symbol(candidate, peer))
3077            .any(|peer| {
3078                peer.source() == file
3079                    || self
3080                        .include_activation_for_source(
3081                            self.cpp,
3082                            file,
3083                            prepared.as_ref(),
3084                            peer.source(),
3085                        )
3086                        .is_some_and(|activation| activation <= reference_byte)
3087            })
3088    }
3089
3090    pub fn external_type_declaration_visible_at(
3091        &self,
3092        file: &ProjectFile,
3093        candidate: &CodeUnit,
3094        reference_byte: usize,
3095    ) -> bool {
3096        if candidate.source() == file {
3097            return true;
3098        }
3099        let Some(prepared) = self.cpp.prepared_syntax(file) else {
3100            return false;
3101        };
3102        self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
3103            .is_some_and(|activation| activation <= reference_byte)
3104    }
3105
3106    /// The preprocessor facts the build proves for a reference sited in
3107    /// `file` (#2011).
3108    ///
3109    /// Every `-D` that survives its command's `-D`/`-U` ordering is a positive
3110    /// `Defined` fact, and a fact holds only when every compile configuration
3111    /// that governs the file agrees on it (intersection). The facts are
3112    /// strictly additive to the reference's active guard set: they can prove a
3113    /// required guard, but the guard check itself is never weakened and no
3114    /// implication is ever inferred from source text.
3115    ///
3116    /// A file with its own database entry answers from that entry alone
3117    /// (phase 1). A header takes its context from the translation units whose
3118    /// include closure reaches it, intersected across all of them (phase 2):
3119    /// the header is compiled once per including TU, so a fact holds for a
3120    /// header-sited reference only when every one of those compilations
3121    /// proves it. A reaching TU the database does not cover proves nothing,
3122    /// which empties the intersection. A file nothing covers or reaches has
3123    /// no facts and every check runs on source structure alone.
3124    pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
3125        if let Some(cached) = self
3126            .compile_proven_guard_cells
3127            .lock()
3128            .expect("C++ compile-proven guard cache poisoned")
3129            .get(file)
3130        {
3131            return Arc::clone(cached);
3132        }
3133        let names = match context_fact_names(self.cpp.compile_contexts_for(file)) {
3134            Some(names) => names,
3135            None => {
3136                let mut translation_units = self.cpp.reaching_translation_units(file).into_iter();
3137                let seed = translation_units.next().and_then(|translation_unit| {
3138                    context_fact_names(self.cpp.compile_contexts_for(&translation_unit))
3139                });
3140                match seed {
3141                    None => HashSet::default(),
3142                    Some(mut names) => {
3143                        for translation_unit in translation_units {
3144                            let Some(reached) = context_fact_names(
3145                                self.cpp.compile_contexts_for(&translation_unit),
3146                            ) else {
3147                                names.clear();
3148                                break;
3149                            };
3150                            names.retain(|name| reached.contains(name));
3151                            if names.is_empty() {
3152                                break;
3153                            }
3154                        }
3155                        names
3156                    }
3157                }
3158            }
3159        };
3160        let proven = Arc::new(
3161            names
3162                .into_iter()
3163                .map(PreprocessorGuard::Defined)
3164                .collect::<HashSet<_>>(),
3165        );
3166        self.compile_proven_guard_cells
3167            .lock()
3168            .expect("C++ compile-proven guard cache poisoned")
3169            .insert(file.clone(), Arc::clone(&proven));
3170        proven
3171    }
3172
3173    /// Whether no compile data covers the compilations of `file`: it has no
3174    /// database entry of its own, and either nothing reaches it or some
3175    /// translation unit that reaches it has no entry. This is the state a
3176    /// regenerated `compile_commands.json` could decide; data that is present
3177    /// for every governing compilation but does not prove a guard is a
3178    /// decided conservative miss, not this state.
3179    fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
3180        if !self.cpp.compile_contexts_for(file).is_empty() {
3181            return false;
3182        }
3183        let translation_units = self.cpp.reaching_translation_units(file);
3184        translation_units.is_empty()
3185            || translation_units
3186                .iter()
3187                .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
3188    }
3189
3190    /// Whether a lookup miss for `identifier` in `file` is explainable by
3191    /// missing compile context (#2011): some same-name declaration is
3192    /// reachable through a conditional include whose required guards neither
3193    /// contradict the reference's active guards nor follow from them, and the
3194    /// translation unit has no compile-commands entry that could decide the
3195    /// question. Callers surface this as an explicit "requires compile
3196    /// context" incompleteness instead of an indistinguishable miss.
3197    ///
3198    /// A structurally disproven declaration (contradicting guards) and a TU
3199    /// whose compile context exists but does not prove the guard both answer
3200    /// `false`: those misses are decided, not incomplete.
3201    pub fn miss_requires_compile_context(
3202        &self,
3203        file: &ProjectFile,
3204        identifier: &str,
3205        reference: Node<'_>,
3206    ) -> bool {
3207        if !self.compile_context_is_absent(file) {
3208            return false;
3209        }
3210        let Some(prepared) = self.cpp.prepared_syntax(file) else {
3211            return false;
3212        };
3213        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
3214        let reference_byte = reference.start_byte();
3215        let mut sources = self
3216            .visible_identifier_candidates(file, identifier)
3217            .map(CodeUnit::source)
3218            .filter(|source| *source != file)
3219            .collect::<Vec<_>>();
3220        sources.sort();
3221        sources.dedup();
3222        sources.into_iter().any(|declaration_source| {
3223            self.conditional_include_projections_for_source(
3224                file,
3225                prepared.as_ref(),
3226                declaration_source,
3227            )
3228            .iter()
3229            .any(|projection| {
3230                projection.activation_byte <= reference_byte
3231                    && !guard_requirements_hold_at_reference(
3232                        &projection.required_guards,
3233                        reference_guards.as_ref(),
3234                    )
3235                    && guards_compatible_at_reference(
3236                        &projection.required_guards,
3237                        reference_guards.as_ref(),
3238                    )
3239            })
3240        })
3241    }
3242
3243    /// Decide whether a declaration that lives in another file reaches a
3244    /// reference in `file`.
3245    ///
3246    /// An external header selects its declaration branch before the reference
3247    /// file is parsed. Require compatible reference guards, but do not test
3248    /// the header's guard expression for stability in the reference file: a
3249    /// `.c` translation unit can never satisfy the `#ifdef __cplusplus` that
3250    /// wraps every declaration of a portable C header, and demanding it would
3251    /// hide the whole header. Guards that the reference file imposes on its
3252    /// own `#include` still have to hold, and still have to be stable.
3253    fn foreign_declaration_reachable_at_reference(
3254        &self,
3255        file: &ProjectFile,
3256        prepared: &PreparedSyntaxTree,
3257        declaration_source: &ProjectFile,
3258        declaration_guards: &HashSet<PreprocessorGuard>,
3259        reference_guards: Option<&HashSet<PreprocessorGuard>>,
3260        reference_byte: usize,
3261    ) -> bool {
3262        // The translation unit's build-proven defines join the reference's
3263        // active guard set (#2011): a conditional include like the nng
3264        // `NNG_PLATFORM_POSIX` chain is provable only by the compile command.
3265        // A reference whose own environment is unknown stays unknown -- the
3266        // facts extend an environment, they never invent one.
3267        let proven = self.compile_proven_guards(file);
3268        let augmented;
3269        let reference_guards = match reference_guards {
3270            Some(active) if !proven.is_empty() => {
3271                augmented = active.union(&proven).cloned().collect();
3272                Some(&augmented)
3273            }
3274            other => other,
3275        };
3276        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
3277            return false;
3278        }
3279        if self
3280            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
3281            .is_some_and(|activation| activation <= reference_byte)
3282        {
3283            return true;
3284        }
3285        self.conditional_include_projections_for_source(file, prepared, declaration_source)
3286            .iter()
3287            .any(|projection| {
3288                projection.activation_byte <= reference_byte
3289                    && guard_requirements_hold_at_reference(
3290                        &projection.required_guards,
3291                        reference_guards,
3292                    )
3293                    && self.preprocessor_guards_stable_between(
3294                        file,
3295                        projection.activation_byte,
3296                        reference_byte,
3297                        &projection.required_guards,
3298                    )
3299            })
3300    }
3301
3302    pub fn external_type_candidate_visible_in_context(
3303        &self,
3304        analyzer: &CppGraphSource<'_>,
3305        file: &ProjectFile,
3306        candidate: &CodeUnit,
3307        reference: Node<'_>,
3308    ) -> bool {
3309        let Some(prepared) = self.cpp.prepared_syntax(file) else {
3310            return false;
3311        };
3312        let macro_environment = self.macro_environment(file, reference.start_byte());
3313        let reference_guards = preprocessor_guard_environment(reference, prepared.source())
3314            .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
3315
3316        let directly_visible = self
3317            .visible_identifier_candidates(file, candidate.identifier())
3318            .filter(|peer| same_logical_symbol(candidate, peer))
3319            .any(|peer| {
3320                declaration_guard_requirements(analyzer, self.cpp, peer)
3321                    .into_iter()
3322                    .any(|(declaration_byte, declaration_guards)| {
3323                        if peer.source() == file {
3324                            return declaration_byte < reference.start_byte()
3325                                && guard_requirements_hold_at_reference(
3326                                    &declaration_guards,
3327                                    reference_guards.as_ref(),
3328                                )
3329                                && self.preprocessor_guards_stable_between(
3330                                    file,
3331                                    declaration_byte,
3332                                    reference.start_byte(),
3333                                    &declaration_guards,
3334                                );
3335                        }
3336                        self.foreign_declaration_reachable_at_reference(
3337                            file,
3338                            prepared.as_ref(),
3339                            peer.source(),
3340                            &declaration_guards,
3341                            reference_guards.as_ref(),
3342                            reference.start_byte(),
3343                        )
3344                    })
3345            });
3346        let complementary = self
3347            .visible_identifier_candidates(file, candidate.identifier())
3348            .filter(|peer| {
3349                peer.kind() == candidate.kind()
3350                    && peer.fq_name() == candidate.fq_name()
3351                    && peer.source() == candidate.source()
3352            })
3353            .collect::<Vec<_>>();
3354        // A completed #if/#else family declares the shared source-level name
3355        // before this reference. A later macro mutation cannot revoke that
3356        // declaration. The family gate below rejects declarations split across
3357        // separate conditional blocks, where mutation can change coverage.
3358        let candidate_branch_compatible = reference_guards.as_ref().is_some_and(|active| {
3359            declaration_guard_requirements(analyzer, self.cpp, candidate)
3360                .iter()
3361                .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
3362        });
3363        let complementary_visible = candidate_branch_compatible
3364            && self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate)
3365            && if candidate.source() == file {
3366                declaration_guard_requirements(analyzer, self.cpp, candidate)
3367                    .iter()
3368                    .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
3369            } else {
3370                self.include_activation_for_source(
3371                    self.cpp,
3372                    file,
3373                    prepared.as_ref(),
3374                    candidate.source(),
3375                )
3376                .is_some_and(|activation| activation <= reference.start_byte())
3377            };
3378        directly_visible || complementary_visible
3379    }
3380
3381    pub fn is_exhaustive_same_fqn_type_declaration_family(
3382        &self,
3383        analyzer: &CppGraphSource<'_>,
3384        file: &ProjectFile,
3385        candidate: &CodeUnit,
3386    ) -> bool {
3387        let candidates = self
3388            .visible_identifier_candidates(file, candidate.identifier())
3389            .filter(|peer| {
3390                peer.kind() == candidate.kind()
3391                    && peer.fq_name() == candidate.fq_name()
3392                    && peer.source() == candidate.source()
3393            })
3394            .collect::<Vec<_>>();
3395        self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
3396    }
3397
3398    /// Prove a nested type alias used as a dependent member-pointer owner when
3399    /// its owning class has mutually-exclusive declarations.  A common C++11
3400    /// compatibility shape provides the owning class in one preprocessor
3401    /// branch and aliases it to a standard-library type in the other branch;
3402    /// the nested fallback alias is therefore not itself active in every
3403    /// branch even though the qualified owner API is.
3404    ///
3405    /// This is deliberately narrower than ordinary type visibility.  The
3406    /// caller has already recovered a member-pointer owner path from the CST;
3407    /// this helper additionally requires the target's structured parent to
3408    /// match that path, physical source visibility, and exact preprocessor
3409    /// guard agreement with the parent declaration.  Only then may the
3410    /// parent's direct/complementary same-FQN visibility stand in for the
3411    /// nested terminal's active-branch check.
3412    pub fn dependent_member_pointer_alias_visible_in_context(
3413        &self,
3414        analyzer: &CppGraphSource<'_>,
3415        file: &ProjectFile,
3416        candidate: &CodeUnit,
3417        owner_components: &[String],
3418        reference: Node<'_>,
3419    ) -> bool {
3420        if !analyzer
3421            .type_alias_provider()
3422            .is_some_and(|provider| provider.is_type_alias(candidate))
3423        {
3424            return false;
3425        }
3426        let Some((terminal, owner_prefix)) = owner_components.split_last() else {
3427            return false;
3428        };
3429        if terminal != candidate.identifier()
3430            || canonical_cpp_scope_components(candidate) != owner_components
3431        {
3432            return false;
3433        }
3434        let Some(expected_parent_fq_name) =
3435            brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
3436        else {
3437            return false;
3438        };
3439        let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
3440            return false;
3441        };
3442        if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
3443            || parent_anchor.source() != candidate.source()
3444            || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
3445        {
3446            return false;
3447        }
3448
3449        // The ordinary path already handles unguarded aliases (and preserves
3450        // same-file declaration ordering).  This fallback is only for a
3451        // physically visible declaration whose guard is the owning branch's
3452        // guard, so reject a same-file declaration that appears after the
3453        // reference before considering guard compatibility.
3454        if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
3455            || candidate.source() == file
3456                && !analyzer
3457                    .ranges(candidate)
3458                    .iter()
3459                    .any(|range| range.start_byte < reference.start_byte())
3460        {
3461            return false;
3462        }
3463
3464        let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
3465        if candidate_guards.is_empty() {
3466            return false;
3467        }
3468        let same_guard_sets =
3469            |left: &[(usize, HashSet<PreprocessorGuard>)],
3470             right: &[(usize, HashSet<PreprocessorGuard>)]| {
3471                left.iter().all(|(_, left_guards)| {
3472                    right
3473                        .iter()
3474                        .any(|(_, right_guards)| left_guards == right_guards)
3475                })
3476            };
3477        let parent_candidates = self
3478            .visible_identifier_candidates(file, parent_anchor.identifier())
3479            .filter(|peer| {
3480                peer.kind() == parent_anchor.kind()
3481                    && peer.fq_name() == expected_parent_fq_name.as_str()
3482                    && peer.source() == parent_anchor.source()
3483                    && canonical_cpp_scope_components(peer) == owner_prefix
3484            })
3485            .filter_map(|peer| {
3486                let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
3487                (candidate_guards.len() == parent_guards.len()
3488                    && same_guard_sets(&candidate_guards, &parent_guards)
3489                    && same_guard_sets(&parent_guards, &candidate_guards))
3490                .then(|| (peer.clone(), parent_guards))
3491            })
3492            .collect::<Vec<_>>();
3493        let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
3494            return false;
3495        };
3496
3497        let Some(prepared) = self.cpp.prepared_syntax(file) else {
3498            return false;
3499        };
3500        let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
3501        else {
3502            return false;
3503        };
3504        // An external header selects its declaration branch before the
3505        // reference file is parsed. Require compatible reference guards, but
3506        // do not test the header's guard expression for stability in the
3507        // reference file. Same-file aliases still require that stability.
3508        if !candidate_guards.iter().any(|(_, target_guards)| {
3509            guards_compatible_at_reference(target_guards, Some(&reference_guards))
3510                && (candidate.source() != file
3511                    || self.preprocessor_guards_stable_between(
3512                        file,
3513                        0,
3514                        reference.start_byte(),
3515                        target_guards,
3516                    ))
3517        }) {
3518            return false;
3519        }
3520
3521        self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
3522    }
3523
3524    /// Check a type candidate's preprocessor/import context without imposing
3525    /// ordinary declaration-before-reference ordering for same-file peers.
3526    ///
3527    /// C++ class scope makes member names visible throughout the complete
3528    /// class, including a trailing return type that appears before the member
3529    /// alias declaration in source order. Callers must first prove that the
3530    /// reference is inside the candidate's indexed class owner; this helper
3531    /// only relaxes the byte-order predicate while retaining guard and include
3532    /// activation checks.
3533    pub fn external_type_candidate_guard_compatible_in_context(
3534        &self,
3535        analyzer: &CppGraphSource<'_>,
3536        file: &ProjectFile,
3537        candidate: &CodeUnit,
3538        reference: Node<'_>,
3539    ) -> bool {
3540        let Some(prepared) = self.cpp.prepared_syntax(file) else {
3541            return false;
3542        };
3543        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
3544
3545        self.visible_identifier_candidates(file, candidate.identifier())
3546            .filter(|peer| same_logical_symbol(candidate, peer))
3547            .any(|peer| {
3548                declaration_guard_requirements(analyzer, self.cpp, peer)
3549                    .into_iter()
3550                    .any(|(declaration_byte, declaration_guards)| {
3551                        if peer.source() == file {
3552                            let (start, end) = if declaration_byte <= reference.start_byte() {
3553                                (declaration_byte, reference.start_byte())
3554                            } else {
3555                                (reference.start_byte(), declaration_byte)
3556                            };
3557                            return guard_requirements_hold_at_reference(
3558                                &declaration_guards,
3559                                reference_guards.as_ref(),
3560                            ) && self.preprocessor_guards_stable_between(
3561                                file,
3562                                start,
3563                                end,
3564                                &declaration_guards,
3565                            );
3566                        }
3567                        self.foreign_declaration_reachable_at_reference(
3568                            file,
3569                            prepared.as_ref(),
3570                            peer.source(),
3571                            &declaration_guards,
3572                            reference_guards.as_ref(),
3573                            reference.start_byte(),
3574                        )
3575                    })
3576            })
3577    }
3578
3579    pub fn type_candidate_may_be_visible_before_reference(
3580        &self,
3581        analyzer: &CppGraphSource<'_>,
3582        file: &ProjectFile,
3583        candidate: &CodeUnit,
3584        reference_byte: usize,
3585    ) -> bool {
3586        let Some(prepared) = self.cpp.prepared_syntax(file) else {
3587            return false;
3588        };
3589        let root = prepared.tree().root_node();
3590        let end_byte = reference_byte
3591            .saturating_add(1)
3592            .min(prepared.source().len());
3593        let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
3594            return false;
3595        };
3596        self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
3597    }
3598
3599    pub fn preprocessor_guards_stable_between(
3600        &self,
3601        file: &ProjectFile,
3602        start_byte: usize,
3603        end_byte: usize,
3604        guards: &HashSet<PreprocessorGuard>,
3605    ) -> bool {
3606        if guards.is_empty() || start_byte >= end_byte {
3607            return true;
3608        }
3609        let cell = self.macro_event_cell(file);
3610        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3611        let mut visited = HashSet::from_iter([file.clone()]);
3612        !events.iter().any(|event| {
3613            event.byte() >= start_byte
3614                && event.byte() < end_byte
3615                && self.macro_event_may_mutate_guards(event, guards, &mut visited)
3616        })
3617    }
3618
3619    fn macro_event_may_mutate_guards(
3620        &self,
3621        event: &MacroEvent,
3622        guards: &HashSet<PreprocessorGuard>,
3623        visited: &mut HashSet<ProjectFile>,
3624    ) -> bool {
3625        match event {
3626            MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
3627                guards.iter().any(|guard| guard.may_depend_on_macro(name))
3628            }
3629            MacroEvent::Include { targets, .. } => {
3630                targets.is_empty()
3631                    || targets
3632                        .iter()
3633                        .any(|target| self.source_may_mutate_guards(target, guards, visited))
3634            }
3635            MacroEvent::Invalidate { .. } => true,
3636        }
3637    }
3638
3639    fn source_may_mutate_guards(
3640        &self,
3641        file: &ProjectFile,
3642        guards: &HashSet<PreprocessorGuard>,
3643        visited: &mut HashSet<ProjectFile>,
3644    ) -> bool {
3645        if !visited.insert(file.clone()) {
3646            return false;
3647        }
3648        let cell = self.macro_event_cell(file);
3649        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3650        events
3651            .iter()
3652            .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
3653    }
3654
3655    pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
3656        let normalized = normalize_reference_name(raw_name)?;
3657        self.type_candidates(file, &normalized)
3658            .into_iter()
3659            .next()
3660            .cloned()
3661    }
3662
3663    /// Mirror forward navigation's visible-name fallback for a bare parameter
3664    /// type after lexical owner and inheritance lookup is exhausted.
3665    ///
3666    /// Generated or otherwise unindexed base classes can hide the alias that
3667    /// makes a parameter type valid C++. Accept the fallback only when every
3668    /// include-visible class or alias with that spelling canonicalizes to one
3669    /// logical type. A shadowing local type resolves lexically before this
3670    /// path, while distinct visible types keep the result ambiguous.
3671    pub fn unique_visible_parameter_type_fallback(
3672        &self,
3673        analyzer: &CppGraphSource<'_>,
3674        file: &ProjectFile,
3675        node: Node<'_>,
3676        source: &str,
3677    ) -> Option<CodeUnit> {
3678        if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
3679            return None;
3680        }
3681        let name = node_text(node, source);
3682        let candidates = self
3683            .visible_identifier_candidates(file, name)
3684            .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
3685            .filter(|candidate| {
3686                self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
3687            })
3688            .collect::<Vec<_>>();
3689        self.unique_canonical_type_candidate(analyzer, file, &candidates)
3690    }
3691
3692    pub fn resolve_type_node_result(
3693        &self,
3694        file: &ProjectFile,
3695        node: Node<'_>,
3696        source: &str,
3697    ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
3698        let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
3699            return Ok(None);
3700        };
3701        let Some(arguments) = cpp_template_reference_arguments(node, source) else {
3702            return Ok(Some(primary));
3703        };
3704        self.resolve_template_arguments(file, primary, &arguments)
3705            .map(Some)
3706    }
3707
3708    pub fn resolve_type_node_primary(
3709        &self,
3710        file: &ProjectFile,
3711        node: Node<'_>,
3712        source: &str,
3713    ) -> Option<CodeUnit> {
3714        let components = cpp_type_name_components(node, source)?;
3715        self.resolve_type(file, &components.join("::"))
3716    }
3717
3718    pub fn resolve_template_arguments(
3719        &self,
3720        file: &ProjectFile,
3721        primary: CodeUnit,
3722        arguments: &[CppTemplateExpression],
3723    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
3724        self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
3725    }
3726
3727    fn resolve_template_arguments_inner(
3728        &self,
3729        file: &ProjectFile,
3730        primary: CodeUnit,
3731        arguments: &[CppTemplateExpression],
3732        seen_aliases: &mut HashSet<CodeUnit>,
3733    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
3734        if let Some(metadata) = self.cpp_template_metadata.get(&primary)
3735            && let Some(alias_target) = &metadata.alias_target
3736        {
3737            if !seen_aliases.insert(primary.clone()) {
3738                return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
3739            }
3740            let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
3741                .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
3742            let target_name = alias_target.components.join("::");
3743            let target_primary = if alias_target.global {
3744                unique_logical_type_candidate(self.type_candidates(file, &target_name))
3745            } else {
3746                self.resolve_unique_type_for_declaration(file, &primary, &target_name)
3747            };
3748            let Some(target_primary) = target_primary else {
3749                // A dependent or external RHS cannot be canonicalized from the
3750                // indexed graph. Preserve the alias's direct identity instead
3751                // of inventing a target from its source spelling.
3752                return Ok(primary);
3753            };
3754            let Some(target_arguments) = &alias_target.arguments else {
3755                return Ok(target_primary);
3756            };
3757            let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
3758                .ok_or(CppTemplateResolutionError::Substitution)?;
3759            return self.resolve_template_arguments_inner(
3760                file,
3761                target_primary,
3762                &target_arguments,
3763                seen_aliases,
3764            );
3765        }
3766
3767        let primary_fq_name = self
3768            .cpp_template_metadata
3769            .get(&primary)
3770            .map(|metadata| metadata.primary_fq_name.clone())
3771            .unwrap_or_else(|| primary.fq_name());
3772        let has_specialization_metadata = self
3773            .cpp_template_families
3774            .get(&primary_fq_name)
3775            .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
3776        if !has_specialization_metadata {
3777            return Ok(primary);
3778        }
3779        self.select_template_specialization(file, &primary, arguments)
3780    }
3781
3782    fn select_template_specialization(
3783        &self,
3784        file: &ProjectFile,
3785        resolved: &CodeUnit,
3786        explicit_arguments: &[CppTemplateExpression],
3787    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
3788        let primary_fq_name = self
3789            .cpp_template_metadata
3790            .get(resolved)
3791            .map(|metadata| metadata.primary_fq_name.clone())
3792            .unwrap_or_else(|| resolved.fq_name());
3793        let family = self
3794            .cpp_template_families
3795            .get(&primary_fq_name)
3796            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
3797        let primary_candidates = family
3798            .iter()
3799            .filter_map(|unit| {
3800                let metadata = self.cpp_template_metadata.get(unit)?;
3801                (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
3802            })
3803            .collect::<Vec<_>>();
3804        let primary_unit = primary_candidates
3805            .iter()
3806            .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
3807            .or_else(|| {
3808                primary_candidates
3809                    .iter()
3810                    .map(|(unit, _)| *unit)
3811                    .min_by_key(|unit| {
3812                        (
3813                            unit.source().to_string(),
3814                            unit.signature().unwrap_or_default(),
3815                        )
3816                    })
3817            })
3818            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
3819        let primary_parameters =
3820            cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
3821                .ok_or(CppTemplateResolutionError::PrimarySelection)?;
3822        let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
3823            .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
3824
3825        let mut applicable = Vec::new();
3826        for unit in family {
3827            let Some(metadata) = self.cpp_template_metadata.get(unit) else {
3828                continue;
3829            };
3830            if metadata.is_primary() || !self.is_visible(file, unit) {
3831                continue;
3832            }
3833            if !cpp_specialization_matches(metadata, &expanded) {
3834                continue;
3835            }
3836            applicable.push((unit, metadata));
3837        }
3838        if applicable.is_empty() {
3839            return Ok(primary_unit.clone());
3840        }
3841
3842        // A scalar constraint count cannot represent C++ partial ordering:
3843        // e.g. `<T*, U>` and `<T, int>` are incomparable for `<int*, int>`.
3844        // Select only a logical candidate whose structural pattern is strictly
3845        // more specialized than every other distinct applicable candidate.
3846        let winners = applicable
3847            .iter()
3848            .filter(|(candidate, candidate_metadata)| {
3849                applicable.iter().all(|(other, other_metadata)| {
3850                    same_visible_symbol(candidate, other)
3851                        || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
3852                })
3853            })
3854            .copied()
3855            .collect::<Vec<_>>();
3856        let Some((selected, _)) = winners.first() else {
3857            // Mutually incomparable applicable candidates: every one of them
3858            // is a live contender.
3859            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
3860                candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
3861            });
3862        };
3863        if winners
3864            .iter()
3865            .any(|(unit, _)| !same_visible_symbol(unit, selected))
3866        {
3867            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
3868                candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
3869            });
3870        }
3871        Ok((*selected).clone())
3872    }
3873
3874    pub fn resolve_type_components_lexically(
3875        &self,
3876        analyzer: &CppGraphSource<'_>,
3877        file: &ProjectFile,
3878        components: &[String],
3879        global: bool,
3880        lexical_scope: &[String],
3881    ) -> LexicalTypeResolution {
3882        self.resolve_type_components_lexically_inner(
3883            analyzer,
3884            file,
3885            components,
3886            global,
3887            lexical_scope,
3888            TypeCandidateResolution::Canonical,
3889        )
3890    }
3891
3892    pub fn resolve_type_components_lexically_for_forward(
3893        &self,
3894        analyzer: &CppGraphSource<'_>,
3895        file: &ProjectFile,
3896        components: &[String],
3897        global: bool,
3898        lexical_scope: &[String],
3899    ) -> LexicalTypeResolution {
3900        self.resolve_type_components_lexically_inner(
3901            analyzer,
3902            file,
3903            components,
3904            global,
3905            lexical_scope,
3906            TypeCandidateResolution::PreserveAlias,
3907        )
3908    }
3909
3910    pub fn resolve_type_components_lexically_for_target(
3911        &self,
3912        analyzer: &CppGraphSource<'_>,
3913        file: &ProjectFile,
3914        components: &[String],
3915        global: bool,
3916        lexical_scope: &[String],
3917        target: &CodeUnit,
3918    ) -> LexicalTypeResolution {
3919        #[cfg(any(test, feature = "test-support"))]
3920        self.target_preserving_type_resolution_count
3921            .fetch_add(1, Ordering::Relaxed);
3922        self.resolve_type_components_lexically_inner(
3923            analyzer,
3924            file,
3925            components,
3926            global,
3927            lexical_scope,
3928            TypeCandidateResolution::PreserveTarget(target),
3929        )
3930    }
3931
3932    pub fn coarse_unqualified_type_reference_may_resolve(
3933        &self,
3934        file: &ProjectFile,
3935        name: &str,
3936    ) -> bool {
3937        if name.is_empty() {
3938            return true;
3939        }
3940        self.visible_identifier_candidates(file, name)
3941            .any(|candidate| candidate.kind() == CodeUnitType::Class || is_type_alias(candidate))
3942            || self.visible_parser_alias_name_is_visible(file, name)
3943    }
3944
3945    #[allow(clippy::too_many_arguments)]
3946    pub fn structured_type_reference_may_resolve_to_target(
3947        &self,
3948        analyzer: &CppGraphSource<'_>,
3949        file: &ProjectFile,
3950        components: &[String],
3951        global: bool,
3952        lexical_scope: &[String],
3953        target: &CodeUnit,
3954    ) -> bool {
3955        if components.is_empty() {
3956            return true;
3957        }
3958        let Some(terminal) = components.last() else {
3959            return true;
3960        };
3961        let parser_alias_visible = self.visible_parser_alias_name_is_visible(file, terminal);
3962        if parser_alias_visible
3963            && self.parser_alias_resolves_to_type(analyzer, file, terminal, target)
3964        {
3965            return true;
3966        }
3967        let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
3968            .map(|qualified| qualified.join("::"))
3969            .collect::<Vec<_>>();
3970        let target_name = cpp_name_for(target);
3971        if qualified_tiers
3972            .iter()
3973            .any(|qualified| qualified == &target_name)
3974        {
3975            return true;
3976        }
3977
3978        let mut saw_shape_candidate = parser_alias_visible;
3979        for candidate in self.visible_identifier_candidates(file, terminal) {
3980            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
3981            {
3982                continue;
3983            }
3984            let candidate_name = cpp_name_for(candidate);
3985            let shape_matches = if global || components.len() > 1 {
3986                qualified_tiers
3987                    .iter()
3988                    .any(|qualified| qualified == &candidate_name)
3989            } else {
3990                true
3991            };
3992            if !shape_matches {
3993                continue;
3994            }
3995            saw_shape_candidate = true;
3996            if same_visible_symbol(candidate, target)
3997                || self.compatible_primary_template_redeclarations(candidate, target)
3998                || (declared_type_alias(analyzer, candidate)
3999                    && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
4000            {
4001                return true;
4002            }
4003        }
4004
4005        !saw_shape_candidate
4006    }
4007
4008    pub fn target_preserving_reference_namespace(
4009        &self,
4010        analyzer: &CppGraphSource<'_>,
4011        file: &ProjectFile,
4012        identifier: &str,
4013        target: &CodeUnit,
4014    ) -> Option<Vec<String>> {
4015        let mut namespace = None;
4016        for candidate in self.visible_identifier_candidates(file, identifier) {
4017            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
4018            {
4019                continue;
4020            }
4021            if !(same_visible_symbol(candidate, target)
4022                || self.compatible_primary_template_redeclarations(candidate, target)
4023                || declared_type_alias(analyzer, candidate)
4024                    && self.structured_alias_primary_preserves_target(
4025                        analyzer, file, candidate, target,
4026                    ))
4027            {
4028                continue;
4029            }
4030            if namespace
4031                .as_ref()
4032                .is_some_and(|existing| existing != candidate.package_name())
4033            {
4034                return None;
4035            }
4036            namespace = Some(candidate.package_name().to_string());
4037        }
4038        let namespace = namespace?;
4039        Some(
4040            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4041                brokk_bifrost_core::analyzer::Language::Cpp,
4042                &namespace,
4043            ),
4044        )
4045    }
4046
4047    pub fn resolve_imported_type_candidate(
4048        &self,
4049        analyzer: &CppGraphSource<'_>,
4050        file: &ProjectFile,
4051        target: &CodeUnit,
4052        target_components: &[String],
4053        direct_target: Option<&CodeUnit>,
4054        preserve_alias: bool,
4055    ) -> LexicalTypeResolution {
4056        let candidates = [target];
4057        let resolution = if preserve_alias {
4058            TypeCandidateResolution::PreserveAlias
4059        } else {
4060            direct_target.map_or(
4061                TypeCandidateResolution::Canonical,
4062                TypeCandidateResolution::PreserveTarget,
4063            )
4064        };
4065        // One candidate goes in, so a failure here is never "choose one of
4066        // these": it is the alias chain leaving the index, which must answer
4067        // missing rather than ambiguous (#1828).
4068        match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
4069            Ok(unit) => LexicalTypeResolution::Resolved {
4070                unit,
4071                components: target_components.to_vec(),
4072                candidates: vec![target.clone()],
4073            },
4074            Err(failure) => failure.lexical_resolution(),
4075        }
4076    }
4077
4078    fn resolve_type_components_lexically_inner(
4079        &self,
4080        analyzer: &CppGraphSource<'_>,
4081        file: &ProjectFile,
4082        components: &[String],
4083        global: bool,
4084        lexical_scope: &[String],
4085        resolution: TypeCandidateResolution<'_>,
4086    ) -> LexicalTypeResolution {
4087        if components.is_empty() {
4088            return LexicalTypeResolution::Missing;
4089        }
4090        // A C++ class injects its own name into the class scope.  The indexed
4091        // FqName for that declaration is the class path itself (for example,
4092        // `n::raw_hash_set`), not a synthetic child named
4093        // `n::raw_hash_set::raw_hash_set`.  Ordinary lexical tiers append the
4094        // requested identifier to every scope component, so they cannot
4095        // represent that injected binding when the enclosing class is the
4096        // closest scope.  Recover the binding from the structured class path
4097        // before allowing lookup to fall through to an outer same-spelled
4098        // declaration.
4099        let mut injected = self.resolve_injected_class_name(
4100            analyzer,
4101            file,
4102            components,
4103            global,
4104            lexical_scope,
4105            resolution,
4106        );
4107        for qualified in lexical_component_tiers(components, global, lexical_scope) {
4108            let prefix_len = qualified.len().saturating_sub(components.len());
4109            if injected
4110                .as_ref()
4111                .is_some_and(|(owner_len, _)| prefix_len < *owner_len)
4112            {
4113                return injected
4114                    .take()
4115                    .expect("injected class resolution was just present")
4116                    .1;
4117            }
4118            let qualified_name = qualified.join("::");
4119            let candidates = self
4120                .type_candidates(file, &qualified_name)
4121                .into_iter()
4122                .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
4123                .collect::<Vec<_>>();
4124            if candidates.is_empty() {
4125                if !global && components.len() == 1 {
4126                    match self.resolve_inherited_type_for_lexical_scope(
4127                        analyzer,
4128                        file,
4129                        &qualified[..prefix_len],
4130                        &components[0],
4131                        resolution,
4132                    ) {
4133                        LexicalTypeResolution::Missing => {}
4134                        inherited => return inherited,
4135                    }
4136                }
4137                continue;
4138            }
4139            let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
4140                Ok(unit) => unit,
4141                Err(failure) => return failure.lexical_resolution(),
4142            };
4143            return LexicalTypeResolution::Resolved {
4144                unit,
4145                components: qualified,
4146                candidates: candidates.into_iter().cloned().collect(),
4147            };
4148        }
4149        LexicalTypeResolution::Missing
4150    }
4151
4152    fn resolve_injected_class_name(
4153        &self,
4154        analyzer: &CppGraphSource<'_>,
4155        file: &ProjectFile,
4156        components: &[String],
4157        global: bool,
4158        lexical_scope: &[String],
4159        resolution: TypeCandidateResolution<'_>,
4160    ) -> Option<(usize, LexicalTypeResolution)> {
4161        if global
4162            || components.len() != 1
4163            || file.rel_path().extension().is_some_and(|ext| ext == "c")
4164            || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
4165        {
4166            return None;
4167        }
4168        let name = components.first()?;
4169        let mut matches: Vec<&CodeUnit> = Vec::new();
4170        let mut owner_len = 0;
4171        for candidate in self.visible_identifier_candidates(file, name) {
4172            if !candidate.is_class()
4173                || declared_type_alias(analyzer, candidate)
4174                || candidate.identifier() != name
4175            {
4176                continue;
4177            }
4178            let candidate_scope = canonical_cpp_scope_components(candidate);
4179            if candidate_scope.len() > lexical_scope.len()
4180                || !lexical_scope.starts_with(&candidate_scope)
4181                || candidate_scope.last().is_none_or(|last| last != name)
4182            {
4183                continue;
4184            }
4185            if candidate_scope.len() > owner_len {
4186                owner_len = candidate_scope.len();
4187                matches.clear();
4188            }
4189            if candidate_scope.len() == owner_len
4190                && !matches
4191                    .iter()
4192                    .any(|existing| same_logical_symbol(existing, candidate))
4193            {
4194                matches.push(candidate);
4195            }
4196        }
4197        if matches.is_empty() {
4198            return None;
4199        }
4200        // A same-named class at the current lexical boundary is already
4201        // represented by the ordinary namespace/class tier.  The injected
4202        // recovery is only needed when lookup is occurring inside a nested
4203        // class, where the enclosing class name is injected across that
4204        // additional class boundary.  Keeping this boundary strict avoids
4205        // treating qualified receiver/static-qualifier context as an
4206        // injected-name reference.
4207        if owner_len >= lexical_scope.len() {
4208            return None;
4209        }
4210        let owner_components = lexical_scope[..owner_len].to_vec();
4211        let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
4212            Ok(unit) => LexicalTypeResolution::Resolved {
4213                unit,
4214                components: owner_components,
4215                candidates: matches.into_iter().cloned().collect(),
4216            },
4217            Err(failure) => failure.lexical_resolution(),
4218        };
4219        Some((owner_len, resolution))
4220    }
4221
4222    fn resolve_inherited_type_for_lexical_scope(
4223        &self,
4224        analyzer: &CppGraphSource<'_>,
4225        file: &ProjectFile,
4226        lexical_scope: &[String],
4227        name: &str,
4228        resolution: TypeCandidateResolution<'_>,
4229    ) -> LexicalTypeResolution {
4230        let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
4231            return LexicalTypeResolution::Missing;
4232        };
4233        let lexical_owner_name = lexical_scope.join("::");
4234        if lexical_owner_name.is_empty() {
4235            return LexicalTypeResolution::Missing;
4236        }
4237        let owner_candidates = self
4238            .type_candidates(file, &lexical_owner_name)
4239            .into_iter()
4240            .filter(|candidate| {
4241                canonical_cpp_name_matches(candidate, &lexical_owner_name)
4242                    && !declared_type_alias(analyzer, candidate)
4243            })
4244            .collect::<Vec<_>>();
4245        if owner_candidates.is_empty() {
4246            return LexicalTypeResolution::Missing;
4247        }
4248        // A visible forward declaration and the physical class definition share
4249        // one FQN, but only the definition owns hierarchy facts. When lookup is
4250        // physically inside that definition, do not let an earlier header
4251        // forward declaration erase its base edges (#2240).
4252        let physical_owner_candidates = owner_candidates
4253            .iter()
4254            .copied()
4255            .filter(|candidate| candidate.source() == file)
4256            .collect::<Vec<_>>();
4257        let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
4258            owner_candidates
4259        } else {
4260            physical_owner_candidates
4261        };
4262        let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
4263            return LexicalTypeResolution::Ambiguous;
4264        };
4265
4266        let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
4267        let mut visited_owners = HashSet::default();
4268        while !frontier.is_empty() {
4269            let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
4270            let mut next_frontier = Vec::new();
4271            for owner in frontier {
4272                if !visited_owners.insert(owner.fq_name()) {
4273                    continue;
4274                }
4275                let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
4276                let candidates = self
4277                    .type_candidates(file, &qualified_name)
4278                    .into_iter()
4279                    .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
4280                    .collect::<Vec<_>>();
4281                if candidates.is_empty() {
4282                    for ancestor in hierarchy.get_direct_ancestors(&owner) {
4283                        if !next_frontier
4284                            .iter()
4285                            .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
4286                        {
4287                            next_frontier.push(ancestor);
4288                        }
4289                    }
4290                    continue;
4291                }
4292                let unit =
4293                    match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
4294                        Ok(unit) => unit,
4295                        Err(failure) => return failure.lexical_resolution(),
4296                    };
4297                level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
4298            }
4299            if let Some((unit, candidates)) = level_matches.first().cloned() {
4300                let Some(first_declaration) = candidates.first() else {
4301                    return LexicalTypeResolution::Ambiguous;
4302                };
4303                if !level_matches.iter().all(|(_, declarations)| {
4304                    declarations
4305                        .iter()
4306                        .all(|declaration| same_logical_symbol(first_declaration, declaration))
4307                }) {
4308                    return LexicalTypeResolution::Ambiguous;
4309                }
4310                let mut components = lexical_scope.to_vec();
4311                components.push(name.to_string());
4312                return LexicalTypeResolution::Resolved {
4313                    unit,
4314                    components,
4315                    candidates,
4316                };
4317            }
4318            frontier = next_frontier;
4319        }
4320        LexicalTypeResolution::Missing
4321    }
4322
4323    /// Resolve a base class through its injected class name at the nearest
4324    /// inheritance tier. Distinct same-named bases at that tier are ambiguous.
4325    pub fn inherited_injected_class_owner(
4326        &self,
4327        analyzer: &CppGraphSource<'_>,
4328        file: &ProjectFile,
4329        enclosing_owner: &CodeUnit,
4330        injected_name: &str,
4331    ) -> Option<CodeUnit> {
4332        let hierarchy = analyzer.type_hierarchy_provider()?;
4333        let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
4334        let mut visited = HashSet::default();
4335        while !frontier.is_empty() {
4336            let mut level_matches = Vec::new();
4337            let mut next_frontier = Vec::new();
4338            for raw_owner in frontier {
4339                let owner = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)?;
4340                if !visited.insert(owner.clone()) {
4341                    continue;
4342                }
4343                if owner.identifier() == injected_name
4344                    && !level_matches
4345                        .iter()
4346                        .any(|existing| same_logical_symbol(existing, &owner))
4347                {
4348                    level_matches.push(owner.clone());
4349                }
4350                next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
4351            }
4352            if let Some(first) = level_matches.first() {
4353                return level_matches
4354                    .iter()
4355                    .all(|candidate| same_logical_symbol(candidate, first))
4356                    .then(|| first.clone());
4357            }
4358            frontier = next_frontier;
4359        }
4360        None
4361    }
4362
4363    /// The one type the candidates name under `resolution`, or why they do not
4364    /// name one. The two preserving modes only ever reject candidates that
4365    /// disagree with each other, which is ambiguity; canonicalization can also
4366    /// fail because the alias chain leaves the index (#1828).
4367    fn resolve_type_candidates(
4368        &self,
4369        analyzer: &CppGraphSource<'_>,
4370        file: &ProjectFile,
4371        candidates: &[&CodeUnit],
4372        resolution: TypeCandidateResolution<'_>,
4373    ) -> Result<CodeUnit, TypeCandidateFailure> {
4374        match resolution {
4375            TypeCandidateResolution::Canonical => {
4376                self.canonical_type_candidate_resolution(analyzer, file, candidates)
4377            }
4378            TypeCandidateResolution::PreserveAlias => {
4379                // A generated index can retain identical alias spellings from
4380                // mutually exclusive headers. When the reference file
4381                // physically reaches exactly one of those source declarations,
4382                // include closure is the structured evidence that selects it;
4383                // treating the two source spellings as an overload set makes a
4384                // reachable alias appear ambiguous (#1844).
4385                let same_fqn_alias_family = candidates.len() > 1
4386                    && candidates.iter().all(|candidate| {
4387                        declared_type_alias(analyzer, candidate)
4388                            && same_logical_symbol(candidates[0], candidate)
4389                    })
4390                    && candidates
4391                        .iter()
4392                        .any(|candidate| candidate.source() != candidates[0].source());
4393                if same_fqn_alias_family {
4394                    let physically_visible = candidates
4395                        .iter()
4396                        .copied()
4397                        .filter(|candidate| self.is_physically_visible(file, candidate))
4398                        .collect::<Vec<_>>();
4399                    // The family is one logical declaration only when the
4400                    // reachable spellings agree. Two same-FQN aliases whose
4401                    // written targets differ (`using Choice = Canonical;` in
4402                    // one header, `using Choice = ::Canonical;` in another)
4403                    // are a genuine conflict, and choosing the first indexed
4404                    // one silently binds the reference to an arbitrary owner
4405                    // (#2398). Collapse only a single reachable declaration
4406                    // or reachable declarations with one structured target;
4407                    // everything else stays ambiguous below.
4408                    let one_structured_target = physically_visible.len() > 1
4409                        && physically_visible.iter().skip(1).all(|candidate| {
4410                            let target = self.structured_alias_target(analyzer, candidate);
4411                            target.is_some()
4412                                && target
4413                                    == self.structured_alias_target(analyzer, physically_visible[0])
4414                        });
4415                    if physically_visible.len() == 1 || one_structured_target {
4416                        return Ok(physically_visible[0].clone());
4417                    }
4418                }
4419                unique_type_candidate_preserving_alias(analyzer, candidates)
4420                    .ok_or(TypeCandidateFailure::Ambiguous)
4421            }
4422            TypeCandidateResolution::PreserveTarget(target) => self
4423                .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
4424                .ok_or(TypeCandidateFailure::Ambiguous),
4425        }
4426    }
4427
4428    pub fn resolve_callable_value_components_lexically(
4429        &self,
4430        analyzer: &CppGraphSource<'_>,
4431        file: &ProjectFile,
4432        owner_components: &[String],
4433        member_name: &str,
4434        global: bool,
4435        lexical_scope: &[String],
4436    ) -> LexicalCallableValueResolution {
4437        if owner_components.is_empty() || member_name.is_empty() {
4438            return LexicalCallableValueResolution::Missing;
4439        }
4440        for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
4441            let owner_name = qualified_owner.join("::");
4442            let type_candidates = self
4443                .type_candidates(file, &owner_name)
4444                .into_iter()
4445                .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
4446                .collect::<Vec<_>>();
4447            let resolved_type = if type_candidates.is_empty() {
4448                None
4449            } else {
4450                let Some(unit) =
4451                    self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
4452                else {
4453                    return LexicalCallableValueResolution::Ambiguous;
4454                };
4455                Some(unit)
4456            };
4457
4458            let mut qualified_callable = qualified_owner;
4459            qualified_callable.push(member_name.to_string());
4460            let callable_name = qualified_callable.join("::");
4461            let free_function = self
4462                .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
4463                .into_iter()
4464                .find(|candidate| {
4465                    canonical_cpp_name_matches(candidate, &callable_name)
4466                        && type_owner_of(analyzer, candidate).is_none()
4467                })
4468                .cloned();
4469
4470            match (resolved_type, free_function) {
4471                (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
4472                (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
4473                (None, Some(function)) => {
4474                    return LexicalCallableValueResolution::FreeFunction(function);
4475                }
4476                (None, None) => {}
4477            }
4478        }
4479        LexicalCallableValueResolution::Missing
4480    }
4481
4482    fn resolve_type_for_declaration(
4483        &self,
4484        visible_from: &ProjectFile,
4485        declaration: &CodeUnit,
4486        raw_name: &str,
4487    ) -> Option<CodeUnit> {
4488        let normalized = normalize_reference_name(raw_name)?;
4489        if !normalized.contains("::")
4490            && let Some(namespace) = cpp_namespace_for(declaration)
4491        {
4492            for prefix in namespace_prefixes(&namespace) {
4493                let qualified = format!("{prefix}::{normalized}");
4494                if let Some(unit) = self
4495                    .type_candidates(visible_from, &qualified)
4496                    .into_iter()
4497                    .next()
4498                {
4499                    return Some(unit.clone());
4500                }
4501            }
4502        }
4503        self.resolve_type(visible_from, raw_name)
4504    }
4505
4506    fn resolve_unique_canonical_type_for_declaration(
4507        &self,
4508        analyzer: &CppGraphSource<'_>,
4509        visible_from: &ProjectFile,
4510        declaration: &CodeUnit,
4511        raw_name: &str,
4512    ) -> Option<CodeUnit> {
4513        let mut current =
4514            self.resolve_unique_type_for_declaration(visible_from, declaration, raw_name)?;
4515        let mut seen_aliases = HashSet::default();
4516        loop {
4517            let Some(target) = self.structured_alias_target(analyzer, &current) else {
4518                return current.is_class().then_some(current);
4519            };
4520            if matches!(target, StructuredAliasTarget::Builtin) {
4521                return current.is_class().then_some(current);
4522            }
4523            if !seen_aliases.insert(current.clone()) {
4524                return None;
4525            }
4526            current = self.resolve_structured_alias_target(visible_from, &current, &target)?;
4527        }
4528    }
4529
4530    pub fn canonical_type_unit(
4531        &self,
4532        analyzer: &CppGraphSource<'_>,
4533        visible_from: &ProjectFile,
4534        unit: &CodeUnit,
4535    ) -> Option<CodeUnit> {
4536        self.canonical_type_resolution(analyzer, visible_from, unit)
4537            .ok()
4538    }
4539
4540    /// Follow `unit`'s alias chain to the class it names, or report why the
4541    /// chain does not end at one indexed class.
4542    ///
4543    /// A chain that leaves the index - an alias to a template parameter, to a
4544    /// standard-library type, or to any other declaration the workspace does
4545    /// not hold - is `Unresolvable`, not `Ambiguous` (#1828). So is a cycle:
4546    /// there is still nothing to choose between.
4547    fn canonical_type_resolution(
4548        &self,
4549        analyzer: &CppGraphSource<'_>,
4550        visible_from: &ProjectFile,
4551        unit: &CodeUnit,
4552    ) -> Result<CodeUnit, TypeCandidateFailure> {
4553        let mut current = unit.clone();
4554        let mut seen_aliases = HashSet::default();
4555        loop {
4556            let Some(target) = self.structured_alias_target(analyzer, &current) else {
4557                return current
4558                    .is_class()
4559                    .then_some(current)
4560                    .ok_or(TypeCandidateFailure::Unresolvable);
4561            };
4562            if matches!(target, StructuredAliasTarget::Builtin) {
4563                return current
4564                    .is_class()
4565                    .then_some(current)
4566                    .ok_or(TypeCandidateFailure::Unresolvable);
4567            }
4568            if !seen_aliases.insert(current.clone()) {
4569                return Err(TypeCandidateFailure::Unresolvable);
4570            }
4571            current = self.structured_alias_target_resolution(visible_from, &current, &target)?;
4572        }
4573    }
4574
4575    pub fn canonical_visible_full_type_unit(
4576        &self,
4577        analyzer: &CppGraphSource<'_>,
4578        visible_from: &ProjectFile,
4579        unit: &CodeUnit,
4580    ) -> Option<CodeUnit> {
4581        let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
4582        if cpp_class_declaration_strength(analyzer, &canonical)
4583            != CppClassDeclarationStrength::Forward
4584        {
4585            return Some(canonical);
4586        }
4587        let mut full = Vec::new();
4588        for candidate in self
4589            .visible_identifier_candidates(visible_from, canonical.identifier())
4590            .filter(|candidate| {
4591                candidate.is_class()
4592                    && candidate.fq_name() == canonical.fq_name()
4593                    && cpp_class_declaration_strength(analyzer, candidate)
4594                        == CppClassDeclarationStrength::Full
4595            })
4596        {
4597            if !full.iter().any(|existing| same_symbol(existing, candidate)) {
4598                full.push(candidate.clone());
4599            }
4600        }
4601        match full.len() {
4602            0 => Some(canonical),
4603            1 => full.pop(),
4604            _ => None,
4605        }
4606    }
4607
4608    fn resolve_structured_alias_target(
4609        &self,
4610        visible_from: &ProjectFile,
4611        declaration: &CodeUnit,
4612        target: &StructuredAliasTarget,
4613    ) -> Option<CodeUnit> {
4614        self.structured_alias_target_resolution(visible_from, declaration, target)
4615            .ok()
4616    }
4617
4618    fn structured_alias_target_resolution(
4619        &self,
4620        visible_from: &ProjectFile,
4621        declaration: &CodeUnit,
4622        target: &StructuredAliasTarget,
4623    ) -> Result<CodeUnit, TypeCandidateFailure> {
4624        let primary =
4625            self.structured_alias_primary_resolution(visible_from, declaration, target)?;
4626        let StructuredAliasTarget::Named { arguments, .. } = target else {
4627            return Err(TypeCandidateFailure::Unresolvable);
4628        };
4629        match arguments {
4630            Some(arguments) => self
4631                .resolve_template_arguments(visible_from, primary, arguments)
4632                .map_err(|error| match error {
4633                    CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
4634                        TypeCandidateFailure::Ambiguous
4635                    }
4636                    _ => TypeCandidateFailure::Unresolvable,
4637                }),
4638            None => Ok(primary),
4639        }
4640    }
4641
4642    fn resolve_structured_alias_primary(
4643        &self,
4644        visible_from: &ProjectFile,
4645        declaration: &CodeUnit,
4646        target: &StructuredAliasTarget,
4647    ) -> Option<CodeUnit> {
4648        self.structured_alias_primary_resolution(visible_from, declaration, target)
4649            .ok()
4650    }
4651
4652    fn structured_alias_primary_resolution(
4653        &self,
4654        visible_from: &ProjectFile,
4655        declaration: &CodeUnit,
4656        target: &StructuredAliasTarget,
4657    ) -> Result<CodeUnit, TypeCandidateFailure> {
4658        let StructuredAliasTarget::Named {
4659            components, global, ..
4660        } = target
4661        else {
4662            return Err(TypeCandidateFailure::Unresolvable);
4663        };
4664        let qualified = components.join("::");
4665        let candidates = if *global {
4666            // `::A::B` anchors at the root scope, so a candidate whose
4667            // canonical path merely ends with the spelled components does not
4668            // qualify. Without this filter a global `::Canonical` target also
4669            // collects `alpha::Canonical`, the lookup reports a false
4670            // ambiguity, and the alias arm silently drops out of its
4671            // conflicting family instead of proving the conflict (#2398).
4672            let mut candidates = self.type_candidates(visible_from, &qualified);
4673            candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
4674            candidates
4675        } else {
4676            self.type_candidates_for_declaration(visible_from, declaration, &qualified)
4677        };
4678        logical_type_candidate(candidates)
4679    }
4680
4681    pub fn structured_alias_primary_preserves_target(
4682        &self,
4683        analyzer: &CppGraphSource<'_>,
4684        visible_from: &ProjectFile,
4685        candidate: &CodeUnit,
4686        target: &CodeUnit,
4687    ) -> bool {
4688        let mut current = candidate.clone();
4689        let mut seen = HashSet::default();
4690        let mut matched_target = false;
4691        loop {
4692            if same_visible_symbol(&current, target)
4693                || self.compatible_primary_template_redeclarations(&current, target)
4694            {
4695                matched_target = true;
4696            }
4697            if !seen.insert(current.clone()) {
4698                return false;
4699            }
4700            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
4701                return matched_target;
4702            };
4703            if matches!(alias_target, StructuredAliasTarget::Builtin) {
4704                return matched_target;
4705            };
4706            let Some(primary) =
4707                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
4708            else {
4709                // A dependent member target such as `Detector<T>::type`
4710                // cannot be reduced to an indexed primary, but a preceding
4711                // structured alias hop may already have proven the requested
4712                // alias identity. Cycles still resolve a primary and are
4713                // rejected by `seen` above.
4714                return matched_target;
4715            };
4716            current = primary;
4717        }
4718    }
4719
4720    pub fn structured_class_alias_resolves_to_target(
4721        &self,
4722        analyzer: &CppGraphSource<'_>,
4723        visible_from: &ProjectFile,
4724        alias: &CodeUnit,
4725        target: &CodeUnit,
4726    ) -> bool {
4727        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
4728            return false;
4729        };
4730        let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
4731            return false;
4732        };
4733        let StructuredAliasTarget::Named {
4734            components, global, ..
4735        } = &alias_target
4736        else {
4737            return false;
4738        };
4739        let lexical_scope = canonical_cpp_scope_components(&owner);
4740        match self.resolve_type_components_lexically_for_target(
4741            analyzer,
4742            visible_from,
4743            components,
4744            *global,
4745            &lexical_scope,
4746            target,
4747        ) {
4748            LexicalTypeResolution::Resolved {
4749                unit, candidates, ..
4750            } => {
4751                same_visible_symbol(&unit, target)
4752                    || self.same_template_member_identity(analyzer, &unit, target)
4753                    || candidates.iter().any(|candidate| {
4754                        same_visible_symbol(candidate, target)
4755                            || self.same_template_member_identity(analyzer, candidate, target)
4756                    })
4757            }
4758            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
4759                self.structured_alias_primary_preserves_target(
4760                    analyzer,
4761                    visible_from,
4762                    alias,
4763                    target,
4764                ) || self.flattened_macro_namespace_alias_target_matches(
4765                    analyzer,
4766                    visible_from,
4767                    alias,
4768                    &alias_target,
4769                    target,
4770                )
4771            }
4772        }
4773    }
4774
4775    /// Return true when a class-owned alias names the requested type as one
4776    /// structured qualifier in its target path.
4777    ///
4778    /// A dependent target such as `Primary<T>::Type` cannot resolve to one
4779    /// indexed class. Forward lookup can still retain `Primary` as its bounded
4780    /// canonical identity. Inverse lookup needs the same evidence when later
4781    /// references use only the alias spelling.
4782    pub fn structured_class_alias_path_preserves_target(
4783        &self,
4784        analyzer: &CppGraphSource<'_>,
4785        visible_from: &ProjectFile,
4786        alias: &CodeUnit,
4787        target: &CodeUnit,
4788    ) -> bool {
4789        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
4790            return false;
4791        };
4792        let Some(StructuredAliasTarget::Named {
4793            components, global, ..
4794        }) = self.structured_alias_target(analyzer, alias)
4795        else {
4796            return false;
4797        };
4798        let lexical_scope = canonical_cpp_scope_components(&owner);
4799        (1..components.len()).rev().any(|component_count| {
4800            matches!(
4801                self.resolve_type_components_lexically_for_target(
4802                    analyzer,
4803                    visible_from,
4804                    &components[..component_count],
4805                    global,
4806                    &lexical_scope,
4807                    target,
4808                ),
4809                LexicalTypeResolution::Resolved {
4810                    ref unit,
4811                    ref candidates,
4812                    ..
4813                } if same_visible_symbol(unit, target)
4814                    || self.same_template_member_identity(analyzer, unit, target)
4815                    || candidates.iter().any(|candidate| {
4816                        same_visible_symbol(candidate, target)
4817                            || self.same_template_member_identity(analyzer, candidate, target)
4818                    })
4819            )
4820        })
4821    }
4822
4823    fn flattened_macro_namespace_alias_target_matches(
4824        &self,
4825        analyzer: &CppGraphSource<'_>,
4826        visible_from: &ProjectFile,
4827        alias: &CodeUnit,
4828        alias_target: &StructuredAliasTarget,
4829        target: &CodeUnit,
4830    ) -> bool {
4831        let StructuredAliasTarget::Named {
4832            components,
4833            global: false,
4834            arguments: None,
4835        } = alias_target
4836        else {
4837            return false;
4838        };
4839        let Some((target_name, namespace_components)) = components.split_last() else {
4840            return false;
4841        };
4842        if namespace_components.is_empty()
4843            || target_name != target.identifier()
4844            || alias.source() != target.source()
4845            || alias.source() != visible_from
4846            || !target.is_class()
4847            || declared_type_alias(analyzer, target)
4848        {
4849            return false;
4850        }
4851        if self
4852            .resolve_structured_alias_target(visible_from, alias, alias_target)
4853            .is_some()
4854        {
4855            return false;
4856        }
4857
4858        let alias_ranges = analyzer.ranges(alias);
4859        let target_ranges = analyzer.ranges(target);
4860        if alias_ranges.is_empty() || target_ranges.is_empty() {
4861            return false;
4862        }
4863        let alias_start = alias_ranges
4864            .iter()
4865            .map(|range| range.start_byte)
4866            .min()
4867            .expect("non-empty alias ranges have a minimum");
4868        let Some(prepared) = self.cpp.prepared_syntax(target.source()) else {
4869            return false;
4870        };
4871        let root = prepared.tree().root_node();
4872        let has_matching_declaration = target_ranges
4873            .iter()
4874            .filter(|range| range.end_byte <= alias_start)
4875            .filter_map(|range| node_for_exact_range(root, range))
4876            .any(|node| {
4877                flattened_macro_namespace_components(node, prepared.source())
4878                    .is_some_and(|recovered| recovered == namespace_components)
4879            });
4880        if !has_matching_declaration {
4881            return false;
4882        }
4883
4884        let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
4885        let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
4886        guard_requirement_sets_match(&alias_guards, &target_guards)
4887    }
4888
4889    pub fn template_alias_arguments_preserve_target(
4890        &self,
4891        analyzer: &CppGraphSource<'_>,
4892        visible_from: &ProjectFile,
4893        alias: &CodeUnit,
4894        arguments: &[CppTemplateExpression],
4895        target: &CodeUnit,
4896    ) -> bool {
4897        let Some(metadata) = self.cpp_template_metadata.get(alias) else {
4898            return false;
4899        };
4900        if metadata.alias_target.is_none()
4901            || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
4902        {
4903            return false;
4904        }
4905        self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
4906    }
4907
4908    pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
4909        self.cpp_template_metadata
4910            .get(unit)
4911            .is_some_and(CppTemplateMetadata::is_primary)
4912    }
4913
4914    pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
4915        self.cpp_template_metadata
4916            .get(unit)
4917            .is_some_and(CppTemplateMetadata::is_specialization)
4918    }
4919
4920    pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
4921        same_visible_symbol(left, right)
4922            || self.compatible_primary_template_redeclarations(left, right)
4923    }
4924
4925    pub fn same_template_member_identity(
4926        &self,
4927        analyzer: &CppGraphSource<'_>,
4928        left: &CodeUnit,
4929        right: &CodeUnit,
4930    ) -> bool {
4931        if same_visible_symbol(left, right) {
4932            return true;
4933        }
4934        if left.kind() != right.kind()
4935            || left.identifier() != right.identifier()
4936            || left.signature() != right.signature()
4937        {
4938            return false;
4939        }
4940        let (Some(left_owner), Some(right_owner)) =
4941            (analyzer.parent_of(left), analyzer.parent_of(right))
4942        else {
4943            return false;
4944        };
4945        left_owner.is_class()
4946            && right_owner.is_class()
4947            && self.same_template_owner_identity(&left_owner, &right_owner)
4948    }
4949
4950    fn unique_canonical_type_candidate(
4951        &self,
4952        analyzer: &CppGraphSource<'_>,
4953        visible_from: &ProjectFile,
4954        candidates: &[&CodeUnit],
4955    ) -> Option<CodeUnit> {
4956        self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
4957            .ok()
4958    }
4959
4960    fn canonical_type_candidate_resolution(
4961        &self,
4962        analyzer: &CppGraphSource<'_>,
4963        visible_from: &ProjectFile,
4964        candidates: &[&CodeUnit],
4965    ) -> Result<CodeUnit, TypeCandidateFailure> {
4966        let mut canonical = Vec::new();
4967        for candidate in candidates {
4968            let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
4969            if canonical
4970                .iter()
4971                .any(|existing| same_visible_symbol(existing, &resolved))
4972            {
4973                continue;
4974            }
4975            if let Some(existing) = canonical.iter_mut().find(|existing| {
4976                self.compatible_primary_template_redeclarations(existing, &resolved)
4977            }) {
4978                // A forward declaration and its full primary-template
4979                // definition are one C++ type even when they live in
4980                // different headers and alpha-rename their parameters. The
4981                // target-preserving path already reconciles this family; do
4982                // the same for ordinary canonical lookup so an out-of-line
4983                // member's lexical owner is not made ambiguous by its own
4984                // forward declaration. Retain the strongest physical
4985                // declaration for later owner/range queries.
4986                if matches!(
4987                    (
4988                        cpp_class_declaration_strength(analyzer, existing),
4989                        cpp_class_declaration_strength(analyzer, &resolved),
4990                    ),
4991                    (
4992                        CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
4993                        CppClassDeclarationStrength::Full,
4994                    ) | (
4995                        CppClassDeclarationStrength::Unknown,
4996                        CppClassDeclarationStrength::Forward,
4997                    )
4998                ) {
4999                    *existing = resolved;
5000                }
5001                continue;
5002            }
5003            canonical.push(resolved);
5004            if canonical.len() > 1 {
5005                return Err(TypeCandidateFailure::Ambiguous);
5006            }
5007        }
5008        canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
5009    }
5010
5011    pub fn unique_type_candidate_preserving_target(
5012        &self,
5013        analyzer: &CppGraphSource<'_>,
5014        visible_from: &ProjectFile,
5015        candidates: &[&CodeUnit],
5016        target: &CodeUnit,
5017    ) -> Option<CodeUnit> {
5018        // C++ headers often expose one logical type through mutually exclusive
5019        // physical declarations, for example a class in the fallback branch
5020        // and a `using` alias to the standard-library type in the configured
5021        // branch. The index intentionally retains both declarations so forward
5022        // lookup can report each target. Preserve the requested target when
5023        // that is the only ambiguity: every candidate has the same type kind,
5024        // exact canonical FQN, and source file, and the requested declaration
5025        // itself is one of the physical candidates. Do not merge same-named
5026        // declarations from different files or namespaces; those remain
5027        // ambiguous and fail closed below.
5028        if self.alternate_same_fqn_type_declarations(analyzer, candidates, target) {
5029            return Some(target.clone());
5030        }
5031        let mut resolved_candidates = Vec::new();
5032        for candidate in candidates {
5033            // An ifdef branch that aliases an unindexed system type (for
5034            // example `typedef pthread_mutex_t k5_os_mutex`) cannot be
5035            // canonicalized. That branch does not name `target`. Dropping it
5036            // keeps the branch that does. Failing the whole family here would
5037            // deny every usage of the reachable spelling (#2368).
5038            let Some(resolved) =
5039                self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
5040            else {
5041                continue;
5042            };
5043            if resolved_candidates
5044                .iter()
5045                .any(|existing| same_visible_symbol(existing, &resolved))
5046            {
5047                continue;
5048            }
5049            resolved_candidates.push(resolved);
5050        }
5051        match resolved_candidates.as_slice() {
5052            [] => None,
5053            [single] => Some(single.clone()),
5054            // The branches disagree about what the name aliases. When they are
5055            // spellings of one entity (#1845) that disagreement is a build
5056            // configuration, not a choice between types, so it must not deny
5057            // the requested target its reference.
5058            _ => self
5059                .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
5060                .map(|_| target.clone()),
5061        }
5062    }
5063
5064    /// The declaration a same-file same-FQN family stands for when a reference
5065    /// names `target`, or `None` when the candidates are not one family or the
5066    /// family does not name `target`.
5067    ///
5068    /// A translation unit cannot hold two different types under one qualified
5069    /// name, so several same-kind declarations of one FQN in one file are
5070    /// alternate spellings of one entity - the configuration branches of an
5071    /// `#if` family, for example log4cxx's `logchar`, which aliases `char` in
5072    /// the UTF-8 branch and `UniChar` in the unichar branch. Their alias
5073    /// targets differ; canonicalizing each branch on its own and then demanding
5074    /// agreement reports an ambiguity that denies every declaration in the
5075    /// family its usages (#1845). The family names `target` when it declares
5076    /// it, or when one branch's alias chain reaches it.
5077    ///
5078    /// Declarations in different files or namespaces are distinct entities and
5079    /// are deliberately excluded: their disagreement is a real ambiguity.
5080    pub fn same_fqn_type_spelling_for_target<'b>(
5081        &self,
5082        analyzer: &CppGraphSource<'_>,
5083        visible_from: &ProjectFile,
5084        candidates: &[&'b CodeUnit],
5085        target: &CodeUnit,
5086    ) -> Option<&'b CodeUnit> {
5087        let [first, rest @ ..] = candidates else {
5088            return None;
5089        };
5090        if rest.is_empty()
5091            || !rest.iter().all(|candidate| {
5092                candidate.kind() == first.kind()
5093                    && candidate.fq_name() == first.fq_name()
5094                    && candidate.source() == first.source()
5095            })
5096        {
5097            return None;
5098        }
5099        candidates
5100            .iter()
5101            .copied()
5102            .find(|candidate| same_symbol(candidate, target))
5103            .or_else(|| {
5104                candidates.iter().copied().find(|candidate| {
5105                    self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
5106                        .is_some_and(|resolved| same_visible_symbol(&resolved, target))
5107                })
5108            })
5109    }
5110
5111    pub fn alternate_same_fqn_type_declarations(
5112        &self,
5113        analyzer: &CppGraphSource<'_>,
5114        candidates: &[&CodeUnit],
5115        target: &CodeUnit,
5116    ) -> bool {
5117        let Some(first) = candidates.first() else {
5118            return false;
5119        };
5120        let same_api = first.kind() == target.kind()
5121            && first.fq_name() == target.fq_name()
5122            && first.source() == target.source()
5123            && candidates.iter().all(|candidate| {
5124                candidate.kind() == target.kind()
5125                    && candidate.fq_name() == target.fq_name()
5126                    && candidate.source() == target.source()
5127            })
5128            && candidates
5129                .iter()
5130                .any(|candidate| same_symbol(candidate, target))
5131            && candidates
5132                .iter()
5133                .any(|candidate| !same_logical_symbol(candidate, target));
5134        if !same_api {
5135            return false;
5136        }
5137
5138        let requirements = candidates
5139            .iter()
5140            .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
5141            .collect::<Vec<_>>();
5142        requirements.len() > 1
5143            && requirements
5144                .iter()
5145                .all(|requirement| !requirement.is_empty())
5146            && requirements.iter().enumerate().all(|(index, left)| {
5147                requirements[index + 1..].iter().all(|right| {
5148                    left.iter().all(|(_, left_guards)| {
5149                        right.iter().all(|(_, right_guards)| {
5150                            merge_preprocessor_guards(left_guards, right_guards).is_none()
5151                        })
5152                    })
5153                })
5154            })
5155    }
5156
5157    fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
5158        let mut pending = vec![terms.to_vec()];
5159        while let Some(branch_terms) = pending.pop() {
5160            let mut normalized = Vec::new();
5161            let mut covers_branch = false;
5162            for term in branch_terms {
5163                if term.iter().any(|guard| term.contains(&guard.negated())) {
5164                    continue;
5165                }
5166                if term.is_empty() {
5167                    covers_branch = true;
5168                    break;
5169                }
5170                if !normalized.iter().any(|existing| existing == &term) {
5171                    normalized.push(term);
5172                }
5173            }
5174            if covers_branch {
5175                continue;
5176            }
5177            let Some(split_guard) = normalized
5178                .iter()
5179                .flat_map(|term| term.iter())
5180                .next()
5181                .cloned()
5182            else {
5183                return false;
5184            };
5185            let negated_guard = split_guard.negated();
5186            let mut when_defined = Vec::new();
5187            let mut when_undefined = Vec::new();
5188            for term in normalized {
5189                if term.contains(&negated_guard) {
5190                    // This term cannot hold when `split_guard` is true.
5191                } else if term.contains(&split_guard) {
5192                    let mut reduced = term.clone();
5193                    reduced.remove(&split_guard);
5194                    when_defined.push(reduced);
5195                } else {
5196                    when_defined.push(term.clone());
5197                }
5198                if term.contains(&split_guard) {
5199                    // This term cannot hold when `split_guard` is false.
5200                } else if term.contains(&negated_guard) {
5201                    let mut reduced = term;
5202                    reduced.remove(&negated_guard);
5203                    when_undefined.push(reduced);
5204                } else {
5205                    when_undefined.push(term);
5206                }
5207            }
5208            pending.push(when_defined);
5209            pending.push(when_undefined);
5210        }
5211        true
5212    }
5213
5214    /// The byte range of the one `#if` family with a terminal `#else` that holds
5215    /// every physical declaration of every candidate, or `None` when they do not
5216    /// share one such family.
5217    ///
5218    /// Guard terms alone cannot distinguish one `#if` family from separate blocks
5219    /// whose macros changed between declarations. Require every physical range to
5220    /// belong to one syntax-tree family with a terminal `#else` before the terms
5221    /// can prove branch coverage.
5222    fn declarations_share_exhaustive_conditional_family(
5223        &self,
5224        analyzer: &CppGraphSource<'_>,
5225        candidates: &[&CodeUnit],
5226    ) -> Option<(usize, usize)> {
5227        let mut family_range = None;
5228        for candidate in candidates {
5229            let prepared = self.cpp.prepared_syntax(candidate.source())?;
5230            let root = prepared.tree().root_node();
5231            let mut candidate_family = None;
5232            for range in analyzer.ranges(candidate) {
5233                let node = root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
5234                let family = preprocessor_conditional_family_for_declaration(node)?;
5235                let key = (family.start_byte(), family.end_byte());
5236                if candidate_family.is_some_and(|existing| existing != key) {
5237                    return None;
5238                }
5239                candidate_family = Some(key);
5240            }
5241            let candidate_family = candidate_family?;
5242            if family_range.is_some_and(|existing| existing != candidate_family) {
5243                return None;
5244            }
5245            family_range = Some(candidate_family);
5246        }
5247        family_range
5248    }
5249
5250    pub fn complementary_same_fqn_type_declarations(
5251        &self,
5252        analyzer: &CppGraphSource<'_>,
5253        candidates: &[&CodeUnit],
5254        target: &CodeUnit,
5255    ) -> bool {
5256        if candidates.len() < 2
5257            || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
5258            || self
5259                .declarations_share_exhaustive_conditional_family(analyzer, candidates)
5260                .is_none()
5261        {
5262            return false;
5263        }
5264        Self::preprocessor_guard_terms_cover_all_paths(
5265            &self.declaration_family_guard_terms(analyzer, candidates),
5266        )
5267    }
5268
5269    fn declaration_family_guard_terms(
5270        &self,
5271        analyzer: &CppGraphSource<'_>,
5272        candidates: &[&CodeUnit],
5273    ) -> Vec<HashSet<PreprocessorGuard>> {
5274        candidates
5275            .iter()
5276            .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
5277            .map(|(_, guards)| guards)
5278            .collect()
5279    }
5280
5281    /// A callable name declared on every branch of one completed `#if`/`#else`
5282    /// family is declared on every configuration path, so a reference below the
5283    /// whole family sees one of the branches whatever the preprocessor decides.
5284    /// Answer the family's end byte: only past `#endif` is every branch's
5285    /// declaration behind the reference.
5286    ///
5287    /// This is the callable analogue of `complementary_same_fqn_type_declarations`
5288    /// and shares both of its primitives. It does not require two distinct
5289    /// `CodeUnit`s: branches that declare the same signature can collapse into
5290    /// one unit carrying one physical range per branch.
5291    ///
5292    /// The branches are alternate spellings of one declaration, never competing
5293    /// declarations, so only the first branch stands for the family. Reporting
5294    /// every branch as visible would turn a name the source declares exactly
5295    /// once into an ambiguity between build configurations.
5296    fn exhaustive_guard_family_activation(
5297        &self,
5298        analyzer: &CppGraphSource<'_>,
5299        prepared: &PreparedSyntaxTree,
5300        candidate: &CodeUnit,
5301        reference: &CallableReferenceContext<'_>,
5302    ) -> Option<usize> {
5303        // Branch coverage says nothing about scope: a block-local declaration
5304        // stays invisible however many branches declare it.
5305        if nameable_callable_declaration_nodes(analyzer, prepared, candidate).is_empty() {
5306            return None;
5307        }
5308        let family = self
5309            .visible_identifier_candidates(candidate.source(), candidate.identifier())
5310            .filter(|peer| {
5311                peer.kind() == candidate.kind()
5312                    && peer.fq_name() == candidate.fq_name()
5313                    && peer.source() == candidate.source()
5314            })
5315            .collect::<Vec<_>>();
5316        let (_, family_end) =
5317            self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
5318        if !Self::preprocessor_guard_terms_cover_all_paths(
5319            &self.declaration_family_guard_terms(analyzer, &family),
5320        ) {
5321            return None;
5322        }
5323        // A reference whose own guards pick one branch already reaches that
5324        // branch through the ordinary same-guard path; the family must not
5325        // resurrect the branch the reference contradicts.
5326        if !declaration_guard_requirements(analyzer, self.cpp, candidate)
5327            .iter()
5328            .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
5329        {
5330            return None;
5331        }
5332        (first_declaration_byte(analyzer, candidate)?
5333            == family
5334                .iter()
5335                .filter_map(|peer| first_declaration_byte(analyzer, peer))
5336                .min()?)
5337        .then_some(family_end)
5338    }
5339
5340    fn type_candidate_preserving_target(
5341        &self,
5342        analyzer: &CppGraphSource<'_>,
5343        visible_from: &ProjectFile,
5344        candidate: &CodeUnit,
5345        target: &CodeUnit,
5346    ) -> Option<CodeUnit> {
5347        let mut current = candidate.clone();
5348        let mut matched_target = same_visible_symbol(&current, target)
5349            || self.compatible_primary_template_redeclarations(&current, target);
5350        let mut seen = HashSet::default();
5351        loop {
5352            if !seen.insert(current.clone()) {
5353                return None;
5354            }
5355            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
5356                return matched_target
5357                    .then(|| target.clone())
5358                    .or_else(|| current.is_class().then_some(current));
5359            };
5360            if self.flattened_macro_namespace_alias_target_matches(
5361                analyzer,
5362                visible_from,
5363                &current,
5364                &alias_target,
5365                target,
5366            ) {
5367                return Some(target.clone());
5368            }
5369            if matches!(alias_target, StructuredAliasTarget::Builtin) {
5370                return matched_target
5371                    .then(|| target.clone())
5372                    .or_else(|| current.is_class().then_some(current));
5373            }
5374            // A non-template alias can name a template alias with explicit
5375            // arguments (for example, `using Result = Expected<int>`).  When
5376            // the requested target is that alias's primary declaration, keep
5377            // the primary identity before expanding the RHS arguments.  The
5378            // expansion would otherwise canonicalize through the underlying
5379            // implementation type and lose the target spelling used by the
5380            // forward resolver.
5381            if !self.cpp_template_metadata.contains_key(&current)
5382                && let Some(primary) =
5383                    self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
5384                && (same_visible_symbol(&primary, target)
5385                    || self.compatible_primary_template_redeclarations(&primary, target))
5386            {
5387                return Some(target.clone());
5388            }
5389            if same_visible_symbol(&current, target) {
5390                return Some(target.clone());
5391            }
5392            if self.cpp_template_metadata.contains_key(&current) {
5393                return None;
5394            }
5395            let Some(next) =
5396                self.resolve_structured_alias_target(visible_from, &current, &alias_target)
5397            else {
5398                return matched_target.then(|| target.clone());
5399            };
5400            current = next;
5401            matched_target |= same_visible_symbol(&current, target)
5402                || self.compatible_primary_template_redeclarations(&current, target);
5403        }
5404    }
5405
5406    fn compatible_primary_template_redeclarations(
5407        &self,
5408        left: &CodeUnit,
5409        right: &CodeUnit,
5410    ) -> bool {
5411        let (Some(left_metadata), Some(right_metadata)) = (
5412            self.cpp_template_metadata.get(left),
5413            self.cpp_template_metadata.get(right),
5414        ) else {
5415            return false;
5416        };
5417        left_metadata.primary_fq_name == right_metadata.primary_fq_name
5418            && left_metadata.is_primary()
5419            && right_metadata.is_primary()
5420            && cpp_reconcile_primary_template_parameters(
5421                &[(left, left_metadata), (right, right_metadata)],
5422                right,
5423            )
5424            .is_some()
5425    }
5426
5427    fn alias_candidate_may_preserve_target(
5428        &self,
5429        analyzer: &CppGraphSource<'_>,
5430        visible_from: &ProjectFile,
5431        candidate: &CodeUnit,
5432        target: &CodeUnit,
5433    ) -> bool {
5434        let mut current = candidate.clone();
5435        let mut seen = HashSet::default();
5436        loop {
5437            if same_visible_symbol(&current, target)
5438                || self.compatible_primary_template_redeclarations(&current, target)
5439            {
5440                return true;
5441            }
5442            if self.cpp_template_metadata.contains_key(&current) {
5443                return true;
5444            }
5445            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
5446                return false;
5447            };
5448            let StructuredAliasTarget::Named {
5449                components,
5450                global,
5451                arguments,
5452            } = alias_target
5453            else {
5454                return false;
5455            };
5456            if arguments.is_some() || !seen.insert(current.clone()) {
5457                return true;
5458            }
5459            let qualified = components.join("::");
5460            let next = if global {
5461                unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
5462            } else {
5463                self.resolve_unique_type_for_declaration(visible_from, &current, &qualified)
5464            };
5465            let Some(next) = next else {
5466                return true;
5467            };
5468            current = next;
5469        }
5470    }
5471
5472    /// Every indexed type declaration `raw_name` names when it is written in
5473    /// `declaration`'s namespace: the innermost enclosing namespace that holds
5474    /// the name wins, otherwise the name is looked up unqualified.
5475    fn type_candidates_for_declaration<'b>(
5476        &'b self,
5477        visible_from: &ProjectFile,
5478        declaration: &CodeUnit,
5479        raw_name: &str,
5480    ) -> Vec<&'b CodeUnit> {
5481        let Some(normalized) = normalize_reference_name(raw_name) else {
5482            return Vec::new();
5483        };
5484        if let Some(namespace) = cpp_namespace_for(declaration) {
5485            for prefix in namespace_prefixes(&namespace) {
5486                let qualified = format!("{prefix}::{normalized}");
5487                let candidates = self.type_candidates(visible_from, &qualified);
5488                if !candidates.is_empty() {
5489                    return candidates;
5490                }
5491            }
5492        }
5493        self.type_candidates(visible_from, &normalized)
5494    }
5495
5496    fn resolve_unique_type_for_declaration(
5497        &self,
5498        visible_from: &ProjectFile,
5499        declaration: &CodeUnit,
5500        raw_name: &str,
5501    ) -> Option<CodeUnit> {
5502        unique_logical_type_candidate(self.type_candidates_for_declaration(
5503            visible_from,
5504            declaration,
5505            raw_name,
5506        ))
5507    }
5508
5509    pub fn resolves_to_type(
5510        &self,
5511        analyzer: &CppGraphSource<'_>,
5512        file: &ProjectFile,
5513        raw_name: &str,
5514        target: &CodeUnit,
5515    ) -> bool {
5516        let Some(normalized) = normalize_reference_name(raw_name) else {
5517            return false;
5518        };
5519        let candidates = self.type_candidates(file, &normalized);
5520        if candidates.is_empty() {
5521            return self.parser_alias_resolves_to_type(analyzer, file, raw_name, target);
5522        }
5523        let Some(resolved) =
5524            self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
5525        else {
5526            return false;
5527        };
5528        same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
5529    }
5530
5531    pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
5532        let raw_target = cpp_alias_declaration_target_text(alias.signature()?)?;
5533        let resolved = self.resolve_type_for_declaration(alias.source(), alias, &raw_target)?;
5534        match resolved.kind() {
5535            CodeUnitType::Class => Some(resolved),
5536            _ if is_type_alias(&resolved) => self.alias_target(&resolved),
5537            _ => None,
5538        }
5539    }
5540
5541    /// Whether two callable declarations declare one function.
5542    ///
5543    /// [`same_logical_symbol`] compares the persisted signature strings, which
5544    /// embed each parameter type exactly as it was spelled. A header
5545    /// declaration written inside `namespace zmq { class dist_t { ... } }` says
5546    /// `send_to_matching(msg_t *)` while its out-of-line body at file scope
5547    /// says `zmq::msg_t *`, so the string comparison reports two symbols where
5548    /// C++ ([basic.def], [dcl.fct]) sees one declaration and one definition.
5549    /// This resolves the written parameter names before comparing them and
5550    /// reports the same answer the language does for the cases it can prove.
5551    ///
5552    /// Everything it cannot prove stays two symbols: a template declaration, a
5553    /// parameter with no comparable shape, a name that resolves on one side
5554    /// only, and an alias chain it cannot follow safely (#2010).
5555    pub fn same_logical_callable(
5556        &self,
5557        analyzer: &CppGraphSource<'_>,
5558        left: &CodeUnit,
5559        right: &CodeUnit,
5560    ) -> bool {
5561        if same_logical_symbol(left, right) {
5562            return true;
5563        }
5564        if left.kind() != right.kind()
5565            || !left.is_callable()
5566            || !right.is_callable()
5567            || left.fq_name() != right.fq_name()
5568        {
5569            return false;
5570        }
5571        // A template declaration and its out-of-line body can also diverge
5572        // outside the parameter list - `template <class T>` against
5573        // `template <typename T>` - and the template head is part of the
5574        // persisted signature. Deciding template-head equivalence is a
5575        // separate question, so templates keep string identity.
5576        if self.callable_is_template_declaration(analyzer, left)
5577            || self.callable_is_template_declaration(analyzer, right)
5578        {
5579            return false;
5580        }
5581        let (Some(left_comparable), Some(right_comparable)) = (
5582            self.callable_comparable(analyzer, left),
5583            self.callable_comparable(analyzer, right),
5584        ) else {
5585            return false;
5586        };
5587        // The trailing member `const`, ref-qualifier, `noexcept`, trailing
5588        // return type and requires-clause are part of C++ callable identity and
5589        // an out-of-line definition repeats them verbatim, so they must agree
5590        // as written.
5591        if left_comparable.suffix != right_comparable.suffix
5592            || left_comparable.shapes.len() != right_comparable.shapes.len()
5593        {
5594            return false;
5595        }
5596        left_comparable
5597            .shapes
5598            .iter()
5599            .zip(right_comparable.shapes.iter())
5600            .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
5601                (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
5602                (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
5603                    self.comparable_shapes_agree(analyzer, left_shape, right_shape)
5604                }
5605                // An unstructured parameter records that the reduction failed,
5606                // not that the two spellings mean the same type, so it agrees
5607                // with nothing - including another unstructured parameter.
5608                _ => false,
5609            })
5610    }
5611
5612    /// Compare two parameter shapes node by node with an explicit paired stack.
5613    ///
5614    /// Shape variants and cv-qualifiers must agree exactly at every level; only
5615    /// the named leaves may be spelled differently, and they agree when they
5616    /// resolve to one type declaration.
5617    fn comparable_shapes_agree(
5618        &self,
5619        analyzer: &CppGraphSource<'_>,
5620        left: &CppComparableParameter,
5621        right: &CppComparableParameter,
5622    ) -> bool {
5623        let mut stack = vec![(left.root(), right.root())];
5624        while let Some((left_index, right_index)) = stack.pop() {
5625            match (left.node(left_index), right.node(right_index)) {
5626                (
5627                    CppComparableNode::Named {
5628                        name: left_name,
5629                        primitive: left_primitive,
5630                        konst: left_konst,
5631                        volatil: left_volatil,
5632                    },
5633                    CppComparableNode::Named {
5634                        name: right_name,
5635                        primitive: right_primitive,
5636                        konst: right_konst,
5637                        volatil: right_volatil,
5638                    },
5639                ) => {
5640                    if left_konst != right_konst
5641                        || left_volatil != right_volatil
5642                        || left_primitive != right_primitive
5643                        || !self.comparable_names_agree(
5644                            analyzer,
5645                            left_name,
5646                            right_name,
5647                            *left_primitive,
5648                        )
5649                    {
5650                        return false;
5651                    }
5652                }
5653                (
5654                    CppComparableNode::Pointer {
5655                        inner: left_inner,
5656                        konst: left_konst,
5657                        volatil: left_volatil,
5658                    },
5659                    CppComparableNode::Pointer {
5660                        inner: right_inner,
5661                        konst: right_konst,
5662                        volatil: right_volatil,
5663                    },
5664                ) => {
5665                    if left_konst != right_konst || left_volatil != right_volatil {
5666                        return false;
5667                    }
5668                    stack.push((*left_inner, *right_inner));
5669                }
5670                (
5671                    CppComparableNode::Reference { inner: left_inner },
5672                    CppComparableNode::Reference { inner: right_inner },
5673                )
5674                | (
5675                    CppComparableNode::Array { inner: left_inner },
5676                    CppComparableNode::Array { inner: right_inner },
5677                ) => stack.push((*left_inner, *right_inner)),
5678                (
5679                    CppComparableNode::Generic {
5680                        base: left_base,
5681                        arguments: left_arguments,
5682                    },
5683                    CppComparableNode::Generic {
5684                        base: right_base,
5685                        arguments: right_arguments,
5686                    },
5687                ) => {
5688                    if left_arguments.len() != right_arguments.len() {
5689                        return false;
5690                    }
5691                    stack.push((*left_base, *right_base));
5692                    stack.extend(
5693                        left_arguments.iter().zip(right_arguments.iter()).map(
5694                            |(left_argument, right_argument)| (*left_argument, *right_argument),
5695                        ),
5696                    );
5697                }
5698                _ => return false,
5699            }
5700        }
5701        true
5702    }
5703
5704    /// Whether two written type names denote one type.
5705    ///
5706    /// A primitive denotes the same type in every scope, so its recorded
5707    /// lexical scope is noise and its spelling decides. A nominal name is
5708    /// resolved on each side independently: two resolved names agree when they
5709    /// reach one type declaration, and two unresolved names agree only on
5710    /// exact agreement of what was written, which is no weaker than the
5711    /// whole-signature string equality this comparison replaces. Resolution on
5712    /// one side only is evidence of difference, never of agreement.
5713    fn comparable_names_agree(
5714        &self,
5715        analyzer: &CppGraphSource<'_>,
5716        left: &StructuredTypeName,
5717        right: &StructuredTypeName,
5718        primitive: bool,
5719    ) -> bool {
5720        if primitive {
5721            return left.path() == right.path();
5722        }
5723        match (
5724            self.comparable_name_terminal(analyzer, left),
5725            self.comparable_name_terminal(analyzer, right),
5726        ) {
5727            (Some(left_terminal), Some(right_terminal)) => {
5728                same_logical_symbol(&left_terminal, &right_terminal)
5729            }
5730            (None, None) => {
5731                left.path() == right.path() && left.is_absolute() == right.is_absolute()
5732            }
5733            _ => false,
5734        }
5735    }
5736
5737    /// The class declaration a written type name denotes, or `None` when the
5738    /// workspace cannot prove one.
5739    ///
5740    /// The lookup is a closure-independent lexical-scope prefix walk over the
5741    /// workspace definition index rather than a visibility lookup: the index
5742    /// handed to a definition query is rooted at the reference file, and a
5743    /// body's `.cpp` is almost never in that file's include closure. Any name
5744    /// this walk resolves is one an enclosing-scope lookup could resolve, so it
5745    /// cannot invent a type the compiler could not see; `using`-directives are
5746    /// not modelled, and a name that needs one stays unresolved.
5747    fn comparable_name_terminal(
5748        &self,
5749        analyzer: &CppGraphSource<'_>,
5750        name: &StructuredTypeName,
5751    ) -> Option<CodeUnit> {
5752        let mut current = self.comparable_name_declaration(analyzer, name)?;
5753        let mut visited = HashSet::default();
5754        for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
5755            // The alias question is asked before the class question, and
5756            // through `declared_type_alias` rather than `is_type_alias`,
5757            // because extraction records `using A8 = A7;` as a *Class* unit
5758            // whose signature is the alias declaration. Reading the kind first
5759            // would end the chase on the alias itself and report an alias
5760            // spelling and its underlying class as two types (#2010).
5761            if !declared_type_alias(analyzer, &current) {
5762                return current.is_class().then_some(current);
5763            }
5764            if !visited.insert(current.clone()) {
5765                return None;
5766            }
5767            let signature = current.signature()?;
5768            // `cpp_alias_declaration_target_text` reads the declaration's
5769            // `type` field only, so `typedef Foo *Bar` reports `Foo` and the
5770            // pointer is silently dropped. Substituting such an alias would
5771            // fuse `f(Bar)` and `f(Foo)`, which are two functions.
5772            if cpp_alias_declaration_adds_indirection(signature) {
5773                return None;
5774            }
5775            let raw_target = cpp_alias_declaration_target_text(signature)?;
5776            current = self.comparable_alias_target(analyzer, &current, &raw_target)?;
5777        }
5778        None
5779    }
5780
5781    /// The declaration one alias hop lands on: the type `raw_target` names,
5782    /// looked up from the alias declaration's own enclosing namespace.
5783    ///
5784    /// The hop takes the same closure-independent prefix walk the first lookup
5785    /// took, and deliberately not `resolve_type_for_declaration`: that one
5786    /// answers out of the `VisibilityIndex`, which is rooted at the reference
5787    /// file, while the alias declaration this hop starts from is reached
5788    /// through the workspace definition index and its file need not be in that
5789    /// root's include closure - where the visibility lookup answers nothing and
5790    /// the chase would stop on the alias itself (#2010).
5791    fn comparable_alias_target(
5792        &self,
5793        analyzer: &CppGraphSource<'_>,
5794        alias: &CodeUnit,
5795        raw_target: &str,
5796    ) -> Option<CodeUnit> {
5797        // `raw_target` is the alias declaration's written type text, so it is a
5798        // plain `::`-joined qualified-id: the same domain the shared symbol-path
5799        // parser reads, and the same leading `::` that marks an absolute name
5800        // everywhere else this crate normalizes a reference.
5801        let absolute = raw_target.trim_start().starts_with("::");
5802        let normalized = normalize_reference_name(raw_target)?;
5803        let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5804            brokk_bifrost_core::analyzer::Language::Cpp,
5805            &normalized,
5806        );
5807        let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
5808            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5809                brokk_bifrost_core::analyzer::Language::Cpp,
5810                &namespace,
5811            )
5812        });
5813        let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
5814        self.comparable_name_declaration(analyzer, &name)
5815    }
5816
5817    /// The one type declaration `name` names, by enclosing scope, innermost
5818    /// first.
5819    ///
5820    /// The first prefix depth that names anything decides: an inner scope hides
5821    /// an outer one, so a match there is the answer even when an outer scope
5822    /// also declares the name. Several logically distinct declarations at that
5823    /// depth are an ambiguity this comparison must not guess at.
5824    fn comparable_name_declaration(
5825        &self,
5826        analyzer: &CppGraphSource<'_>,
5827        name: &StructuredTypeName,
5828    ) -> Option<CodeUnit> {
5829        let definitions = analyzer.global_usage_definition_index();
5830        let first_depth = if name.is_absolute() {
5831            0
5832        } else {
5833            name.lexical_scope().len()
5834        };
5835        for depth in (0..=first_depth).rev() {
5836            let mut components = Vec::with_capacity(depth.saturating_add(name.path().len()));
5837            components.extend_from_slice(&name.lexical_scope()[..depth]);
5838            components.extend_from_slice(name.path());
5839            let mut candidates =
5840                definitions
5841                    .fqn(&components.join("."))
5842                    .into_iter()
5843                    .filter(|unit| {
5844                        unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
5845                    });
5846            let Some(first) = candidates.next() else {
5847                continue;
5848            };
5849            return candidates
5850                .all(|unit| same_logical_symbol(unit, first))
5851                .then(|| first.clone());
5852        }
5853        None
5854    }
5855
5856    /// The comparison inputs of one callable declaration, extracted once.
5857    ///
5858    /// The comparison itself runs only when two candidates share kind and fully
5859    /// qualified name but not signature, which is rare; re-reading the same
5860    /// declaration for every pair in a candidate set is not.
5861    fn callable_comparable(
5862        &self,
5863        analyzer: &CppGraphSource<'_>,
5864        unit: &CodeUnit,
5865    ) -> Option<Arc<ExtractedComparable>> {
5866        if let Some(cached) = self
5867            .callable_comparables
5868            .lock()
5869            .expect("C++ callable comparable cache poisoned")
5870            .get(unit)
5871            .cloned()
5872        {
5873            return cached;
5874        }
5875        let extracted = self
5876            .extract_callable_comparable(analyzer, unit)
5877            .map(Arc::new);
5878        self.callable_comparables
5879            .lock()
5880            .expect("C++ callable comparable cache poisoned")
5881            .insert(unit.clone(), extracted.clone());
5882        extracted
5883    }
5884
5885    fn extract_callable_comparable(
5886        &self,
5887        analyzer: &CppGraphSource<'_>,
5888        unit: &CodeUnit,
5889    ) -> Option<ExtractedComparable> {
5890        let prepared = self.cpp.prepared_syntax(unit.source())?;
5891        let root = prepared.tree().root_node();
5892        let declarator = analyzer
5893            .ranges(unit)
5894            .into_iter()
5895            .find_map(|range| cpp_function_declarator_at(root, range.start_byte))?;
5896        Some(ExtractedComparable {
5897            shapes: cpp_comparable_parameter_shapes(declarator, prepared.source()),
5898            suffix: cpp_callable_identity_suffix(declarator, prepared.source())?,
5899        })
5900    }
5901
5902    pub fn canonical_type_for_reference(
5903        &self,
5904        file: &ProjectFile,
5905        raw_name: &str,
5906    ) -> Option<CodeUnit> {
5907        let resolved = self.resolve_type(file, raw_name)?;
5908        self.alias_target(&resolved).or(Some(resolved))
5909    }
5910
5911    pub fn parser_alias_resolves_to_type(
5912        &self,
5913        analyzer: &CppGraphSource<'_>,
5914        file: &ProjectFile,
5915        raw_name: &str,
5916        target: &CodeUnit,
5917    ) -> bool {
5918        let Some(alias_name) = normalize_reference_name(raw_name) else {
5919            return false;
5920        };
5921        let Some(cpp) = analyzer.cpp else {
5922            return false;
5923        };
5924        let matches_file = |source_file: &ProjectFile| {
5925            self.file_alias_matches(cpp, source_file, &alias_name, target)
5926        };
5927        self.visible_source_files_by_root.get(file).map_or_else(
5928            || matches_file(file),
5929            |files| files.iter().any(matches_file),
5930        )
5931    }
5932
5933    fn file_alias_matches(
5934        &self,
5935        cpp: &dyn CppSource,
5936        file: &ProjectFile,
5937        alias_name: &str,
5938        target: &CodeUnit,
5939    ) -> bool {
5940        let cell = {
5941            let mut cells = self.alias_cells.lock().expect("alias cell map lock");
5942            Arc::clone(
5943                cells
5944                    .entry(file.clone())
5945                    .or_insert_with(|| Arc::new(OnceLock::new())),
5946            )
5947        };
5948        cell.get_or_init(|| {
5949            #[cfg(any(test, feature = "test-support"))]
5950            {
5951                *self
5952                    .alias_source_parse_counts
5953                    .lock()
5954                    .expect("alias source parse count lock")
5955                    .entry(file.clone())
5956                    .or_default() += 1;
5957            }
5958            aliases_from_prepared_source(cpp, file).into_boxed_slice()
5959        })
5960        .iter()
5961        .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
5962    }
5963
5964    #[cfg(any(test, feature = "test-support"))]
5965    pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
5966        self.visible_source_files_by_root
5967            .get(file)
5968            .cloned()
5969            .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
5970    }
5971
5972    #[cfg(any(test, feature = "test-support"))]
5973    pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
5974        self.alias_source_parse_counts
5975            .lock()
5976            .expect("alias source parse count lock")
5977            .get(file)
5978            .copied()
5979            .unwrap_or(0)
5980    }
5981
5982    pub fn resolve_named(
5983        &self,
5984        file: &ProjectFile,
5985        raw_name: &str,
5986        kind: TargetKind,
5987    ) -> Option<CodeUnit> {
5988        let normalized = normalize_reference_name(raw_name)?;
5989        self.named_candidates_for_normalized(file, &normalized, kind)
5990            .into_iter()
5991            .next()
5992            .cloned()
5993    }
5994
5995    pub fn contains_named_symbol(
5996        &self,
5997        file: &ProjectFile,
5998        raw_name: &str,
5999        kind: TargetKind,
6000        target: &CodeUnit,
6001    ) -> bool {
6002        let Some(normalized) = normalize_reference_name(raw_name) else {
6003            return false;
6004        };
6005        self.named_candidates_for_normalized(file, &normalized, kind)
6006            .into_iter()
6007            .any(|unit| {
6008                matches_kind_for_lookup(unit, kind)
6009                    && reference_matches_unit(&normalized, unit)
6010                    && same_visible_symbol(unit, target)
6011            })
6012    }
6013
6014    pub fn named_candidates(
6015        &self,
6016        file: &ProjectFile,
6017        raw_name: &str,
6018        kind: TargetKind,
6019    ) -> Vec<CodeUnit> {
6020        let Some(normalized) = normalize_reference_name(raw_name) else {
6021            return Vec::new();
6022        };
6023        self.named_candidates_for_normalized(file, &normalized, kind)
6024            .into_iter()
6025            .cloned()
6026            .collect()
6027    }
6028
6029    pub fn resolve_known_non_target(
6030        &self,
6031        file: &ProjectFile,
6032        raw_name: &str,
6033        kind: TargetKind,
6034        target: &CodeUnit,
6035    ) -> bool {
6036        let Some(normalized) = normalize_reference_name(raw_name) else {
6037            return false;
6038        };
6039        normalized.contains("::")
6040            && self
6041                .named_candidates_for_normalized(file, &normalized, kind)
6042                .into_iter()
6043                .any(|unit| {
6044                    matches_kind_for_lookup(unit, kind)
6045                        && reference_matches_unit(&normalized, unit)
6046                        && !same_visible_symbol(unit, target)
6047                })
6048    }
6049
6050    pub fn resolve_call_return_binding(
6051        &self,
6052        analyzer: &CppGraphSource<'_>,
6053        file: &ProjectFile,
6054        raw_name: &str,
6055        arity: usize,
6056        lexical_namespace: Option<&str>,
6057        direct_type: Option<&CodeUnit>,
6058    ) -> Option<CppScanBinding> {
6059        let normalized = normalize_reference_name(raw_name)?;
6060        let mut candidates = Vec::new();
6061        for function in
6062            self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
6063        {
6064            if cpp_callable_arity(analyzer, function).accepts(arity)
6065                && !direct_type.is_some_and(|direct_type| {
6066                    self.callable_is_constructor_declaration(analyzer, function)
6067                        && type_owner_of(analyzer, function)
6068                            .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
6069                })
6070            {
6071                candidates.push(function.clone());
6072            }
6073        }
6074        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
6075        unanimous_return_binding(analyzer, self, file, &candidates)
6076    }
6077
6078    pub fn resolve_call_return_binding_without_arity(
6079        &self,
6080        analyzer: &CppGraphSource<'_>,
6081        file: &ProjectFile,
6082        raw_name: &str,
6083        lexical_namespace: Option<&str>,
6084        direct_type: Option<&CodeUnit>,
6085    ) -> (bool, Option<CppScanBinding>) {
6086        let Some(normalized) = normalize_reference_name(raw_name) else {
6087            return (false, None);
6088        };
6089        let mut candidates = self
6090            .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
6091            .into_iter()
6092            .filter(|function| {
6093                function.is_function()
6094                    && !direct_type.is_some_and(|direct_type| {
6095                        self.callable_is_constructor_declaration(analyzer, function)
6096                            && type_owner_of(analyzer, function)
6097                                .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
6098                    })
6099            })
6100            .cloned()
6101            .collect::<Vec<_>>();
6102        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
6103        let has_candidates = !candidates.is_empty();
6104        (
6105            has_candidates,
6106            unanimous_return_binding(analyzer, self, file, &candidates),
6107        )
6108    }
6109
6110    pub fn visible_identifier_candidates<'b>(
6111        &'b self,
6112        file: &ProjectFile,
6113        identifier: &str,
6114    ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
6115        self.visible_by_identifier
6116            .get(file)
6117            .and_then(|by_name| by_name.get(identifier))
6118            .into_iter()
6119            .flatten()
6120    }
6121
6122    /// Return terminal reference names that can denote `target` from `file`.
6123    ///
6124    /// The indexed candidate table covers ordinary declarations and aliases;
6125    /// parser-only aliases are read through their per-file cells so this path
6126    /// never reparses a source that has already been inspected by the visibility
6127    /// index.
6128    pub fn visible_type_reference_component_names_for_target(
6129        &self,
6130        analyzer: &CppGraphSource<'_>,
6131        file: &ProjectFile,
6132        target: &CodeUnit,
6133    ) -> HashSet<String> {
6134        let mut names = HashSet::from_iter([target.identifier().to_string()]);
6135        if let Some(metadata) = self.cpp_template_metadata.get(target) {
6136            names.insert(metadata.primary_name.clone());
6137        }
6138
6139        if let Some(by_identifier) = self.visible_by_identifier.get(file) {
6140            for (identifier, candidates) in by_identifier {
6141                if candidates.iter().any(|candidate| {
6142                    (candidate.is_class()
6143                        && (same_visible_symbol(candidate, target)
6144                            || self.compatible_primary_template_redeclarations(candidate, target)))
6145                        || (declared_type_alias(analyzer, candidate)
6146                            && self.alias_candidate_may_preserve_target(
6147                                analyzer, file, candidate, target,
6148                            ))
6149                }) {
6150                    names.insert(identifier.clone());
6151                }
6152            }
6153        }
6154
6155        names.extend(self.visible_parser_alias_names_for_target(file, target));
6156
6157        names
6158    }
6159
6160    pub fn indexed_structural_class_scope(
6161        &self,
6162        file: &ProjectFile,
6163        class: Node<'_>,
6164        source: &str,
6165    ) -> Option<Vec<String>> {
6166        let key = (file.clone(), class.start_byte(), class.end_byte());
6167        if let Some(cached) = self
6168            .indexed_structural_class_scopes
6169            .lock()
6170            .expect("C++ indexed structural-class scope cache poisoned")
6171            .get(&key)
6172            .cloned()
6173        {
6174            return cached;
6175        }
6176        let resolved = (|| {
6177            let name = class.child_by_field_name("name")?;
6178            let identifier = if name.kind() == "template_type" {
6179                node_text(name.child_by_field_name("name")?, source).to_string()
6180            } else {
6181                let mut components = Vec::new();
6182                append_cpp_name_components(name, source, &mut components)?;
6183                components.last()?.clone()
6184            };
6185            let visible = self
6186                .visible_identifier_candidates(file, &identifier)
6187                .cloned()
6188                .collect::<Vec<_>>();
6189            let mut visible = visible;
6190            for candidate in
6191                self.visible_by_file
6192                    .get(file)
6193                    .into_iter()
6194                    .flatten()
6195                    .filter(|candidate| {
6196                        self.cpp_template_metadata
6197                            .get(candidate)
6198                            .is_some_and(|metadata| metadata.primary_name == identifier)
6199                    })
6200            {
6201                if !visible
6202                    .iter()
6203                    .any(|existing| same_logical_symbol(existing, candidate))
6204                {
6205                    visible.push(candidate.clone());
6206                }
6207            }
6208            // Built once per call rather than per candidate; `cpp_source` rebuilds
6209            // the five-field source from the same `self.cpp` on every call.
6210            let cpp_source = self.cpp_source();
6211            let candidates = visible
6212                .iter()
6213                .filter(|candidate| {
6214                    candidate.source() == file
6215                        && candidate.is_class()
6216                        && !declared_type_alias(&cpp_source, candidate)
6217                        && self.cpp.ranges(candidate).iter().any(|range| {
6218                            range.start_byte <= class.start_byte()
6219                                && class.end_byte() <= range.end_byte
6220                        })
6221                })
6222                .collect::<Vec<_>>();
6223            let owner = if name.kind() == "template_type" {
6224                let expected = normalize_cpp_whitespace(node_text(name, source));
6225                let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
6226                let exact = candidates
6227                    .iter()
6228                    .copied()
6229                    .filter(|candidate| {
6230                        candidate
6231                            .fq()
6232                            .segments()
6233                            .iter()
6234                            .rev()
6235                            .find_map(|&segment| {
6236                                let (text, kind) = interner.resolve(segment);
6237                                matches!(
6238                                    kind,
6239                                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
6240                                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
6241                                )
6242                                .then_some(text)
6243                            })
6244                            .is_some_and(|text| text == expected)
6245                    })
6246                    .collect::<Vec<_>>();
6247                unique_logical_type_candidate(exact)
6248                    .or_else(|| unique_logical_type_candidate(candidates.clone()))?
6249            } else {
6250                unique_logical_type_candidate(candidates)?
6251            };
6252            Some(canonical_cpp_scope_components(&owner))
6253        })();
6254        self.indexed_structural_class_scopes
6255            .lock()
6256            .expect("C++ indexed structural-class scope cache poisoned")
6257            .insert(key, resolved.clone());
6258        resolved
6259    }
6260
6261    pub fn indexed_enclosing_owner_scope(
6262        &self,
6263        analyzer: &CppGraphSource<'_>,
6264        file: &ProjectFile,
6265        node: Node<'_>,
6266    ) -> Option<Vec<String>> {
6267        let anchor = std::iter::successors(Some(node), |current| current.parent())
6268            .find(|current| {
6269                matches!(
6270                    current.kind(),
6271                    "function_definition"
6272                        | "class_specifier"
6273                        | "struct_specifier"
6274                        | "union_specifier"
6275                )
6276            })
6277            .unwrap_or(node);
6278        let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
6279        if let Some(cached) = self
6280            .indexed_enclosing_owner_scopes
6281            .lock()
6282            .expect("C++ indexed enclosing-owner scope cache poisoned")
6283            .get(&key)
6284            .cloned()
6285        {
6286            return cached;
6287        }
6288        let resolved = (|| {
6289            let range = Range {
6290                start_byte: node.start_byte(),
6291                end_byte: node.end_byte(),
6292                start_line: node.start_position().row,
6293                end_line: node.end_position().row,
6294            };
6295            let start = analyzer.enclosing_code_unit(file, &range)?;
6296            let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
6297                start,
6298                |unit| self.cached_precise_parent_of(analyzer, unit),
6299            )
6300            .find(|unit| {
6301                unit.is_class()
6302                    && !analyzer
6303                        .type_alias_provider()
6304                        .is_some_and(|provider| provider.is_type_alias(unit))
6305            })?;
6306            Some(canonical_cpp_scope_components(&owner))
6307        })();
6308        self.indexed_enclosing_owner_scopes
6309            .lock()
6310            .expect("C++ indexed enclosing-owner scope cache poisoned")
6311            .insert(key, resolved.clone());
6312        resolved
6313    }
6314
6315    fn cached_precise_parent_of(
6316        &self,
6317        analyzer: &CppGraphSource<'_>,
6318        code_unit: &CodeUnit,
6319    ) -> Option<CodeUnit> {
6320        if let Some(cached) = self
6321            .precise_parent_cache
6322            .lock()
6323            .expect("C++ precise-parent cache poisoned")
6324            .get(code_unit)
6325            .cloned()
6326        {
6327            return cached;
6328        }
6329        let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
6330        self.precise_parent_cache
6331            .lock()
6332            .expect("C++ precise-parent cache poisoned")
6333            .insert(code_unit.clone(), resolved.clone());
6334        resolved
6335    }
6336
6337    pub fn callable_is_constructor_declaration(
6338        &self,
6339        analyzer: &CppGraphSource<'_>,
6340        candidate: &CodeUnit,
6341    ) -> bool {
6342        if !candidate.is_function() {
6343            return false;
6344        }
6345        let Some(prepared) = self.cpp.prepared_syntax(candidate.source()) else {
6346            return false;
6347        };
6348        let root = prepared.tree().root_node();
6349        let candidate_ranges = analyzer.ranges(candidate);
6350        let enclosed_by_matching_type = candidate_ranges.iter().any(|range| {
6351            let mut current = root
6352                .descendant_for_byte_range(range.start_byte, range.end_byte)
6353                .and_then(|node| node.parent());
6354            while let Some(node) = current {
6355                if matches!(
6356                    node.kind(),
6357                    "class_specifier" | "struct_specifier" | "union_specifier"
6358                ) {
6359                    return node
6360                        .child_by_field_name("name")
6361                        .map(|name| terminal_name(node_text(name, prepared.source())))
6362                        .is_some_and(|name| name == candidate.identifier());
6363                }
6364                current = node.parent();
6365            }
6366            false
6367        });
6368        if enclosed_by_matching_type {
6369            return true;
6370        }
6371        let indexed_containment = analyzer
6372            .declarations(candidate.source())
6373            .into_iter()
6374            .filter(|unit| unit.is_class() && unit.identifier() == candidate.identifier())
6375            .any(|owner| {
6376                analyzer.ranges(&owner).iter().any(|owner_range| {
6377                    candidate_ranges.iter().any(|candidate_range| {
6378                        owner_range.start_byte <= candidate_range.start_byte
6379                            && candidate_range.end_byte <= owner_range.end_byte
6380                    })
6381                })
6382            });
6383        if indexed_containment {
6384            return true;
6385        }
6386        let metadata = analyzer.signature_metadata(candidate);
6387        !metadata.is_empty()
6388            && metadata
6389                .iter()
6390                .all(|signature| signature.return_type_text().is_none())
6391    }
6392
6393    /// Whether a callable declaration is a class-template deduction guide.
6394    ///
6395    /// Tree-sitter represents `Box(T) -> Box<T>;` as a declaration with no
6396    /// type field whose function declarator owns a trailing return type. This
6397    /// structured shape distinguishes a guide from both a constructor (no
6398    /// trailing return) and an ordinary trailing-return function (an `auto`
6399    /// type field).
6400    pub fn callable_is_deduction_guide_declaration(
6401        &self,
6402        analyzer: &CppGraphSource<'_>,
6403        candidate: &CodeUnit,
6404    ) -> bool {
6405        if !candidate.is_function() {
6406            return false;
6407        }
6408        let Some(prepared) = self.cpp.prepared_syntax(candidate.source()) else {
6409            return false;
6410        };
6411        nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
6412            .into_iter()
6413            .any(|declaration| {
6414                if declaration.kind() != "declaration"
6415                    || declaration.child_by_field_name("type").is_some()
6416                {
6417                    return false;
6418                }
6419                let Some(declarator) = declaration.child_by_field_name("declarator") else {
6420                    return false;
6421                };
6422                if declarator.kind() != "function_declarator" {
6423                    return false;
6424                }
6425                let mut cursor = declarator.walk();
6426                let has_trailing_return = declarator
6427                    .named_children(&mut cursor)
6428                    .any(|child| child.kind() == "trailing_return_type");
6429                has_trailing_return
6430                    && declarator_name_node(declarator).is_some_and(|name| {
6431                        node_text(name, prepared.source()) == candidate.identifier()
6432                    })
6433            })
6434    }
6435
6436    /// Whether a callable occurrence is directly wrapped by a C++ template
6437    /// declaration. This deliberately inspects declaration syntax instead of
6438    /// inferring template status from the rendered signature.
6439    pub fn callable_is_template_declaration(
6440        &self,
6441        analyzer: &CppGraphSource<'_>,
6442        candidate: &CodeUnit,
6443    ) -> bool {
6444        if !candidate.is_function() {
6445            return false;
6446        }
6447        let Some(prepared) = self.cpp.prepared_syntax(candidate.source()) else {
6448            return false;
6449        };
6450        let root = prepared.tree().root_node();
6451        analyzer.ranges(candidate).iter().any(|range| {
6452            let Some(node) = node_for_exact_range(root, range)
6453                .or_else(|| root.descendant_for_byte_range(range.start_byte, range.end_byte))
6454            else {
6455                return false;
6456            };
6457            node.parent().is_some_and(|parent| {
6458                parent.kind() == "template_declaration"
6459                    && parent
6460                        .named_child(parent.named_child_count().saturating_sub(1))
6461                        .is_some_and(|declaration| same_node(declaration, node))
6462            })
6463        })
6464    }
6465
6466    pub fn type_name_candidates<'b>(
6467        &'b self,
6468        file: &ProjectFile,
6469        normalized: &str,
6470    ) -> Vec<&'b CodeUnit> {
6471        self.candidate_units(file, normalized, TargetKind::Type)
6472    }
6473
6474    pub fn visible_members_for_owner_name<'b>(
6475        &'b self,
6476        file: &ProjectFile,
6477        owner: &CodeUnit,
6478        name: &str,
6479    ) -> Vec<&'b CodeUnit> {
6480        self.visible_identifier_candidates(file, name)
6481            .filter(|unit| {
6482                // Structured owner pop on the unit's own `fq()` (shared with
6483                // `CodeUnitIndex::parent_of`), not a re-split of its rendered fqn
6484                // string.
6485                brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
6486                    .is_some_and(|parent| parent == owner.fq_name())
6487            })
6488            .collect()
6489    }
6490
6491    pub fn visible_member_for_owner_name(
6492        &self,
6493        file: &ProjectFile,
6494        owner: &CodeUnit,
6495        name: &str,
6496    ) -> VisibleMemberResolution {
6497        let candidates = self.visible_members_for_owner_name(file, owner, name);
6498        let mut callables = Vec::new();
6499        let mut non_callable = None;
6500        for candidate in candidates {
6501            if candidate.is_function() {
6502                callables.push(candidate.clone());
6503            } else if non_callable.is_none() {
6504                non_callable = Some(candidate.clone());
6505            }
6506        }
6507        match (callables.is_empty(), non_callable) {
6508            (false, None) => VisibleMemberResolution::Callable(callables),
6509            (true, Some(_)) => VisibleMemberResolution::NonCallable,
6510            (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
6511            (true, None) => VisibleMemberResolution::Missing,
6512        }
6513    }
6514
6515    fn field_declared_type_fact(
6516        &self,
6517        analyzer: &CppGraphSource<'_>,
6518        field: &CodeUnit,
6519    ) -> Option<DeclaredFieldTypeFact> {
6520        if let Some(cached) = self
6521            .field_type_facts
6522            .lock()
6523            .expect("C++ field type fact cache poisoned")
6524            .get(field)
6525            .cloned()
6526        {
6527            return cached;
6528        }
6529        let decoded = decode_field_declared_type_fact(analyzer, field);
6530        self.field_type_facts
6531            .lock()
6532            .expect("C++ field type fact cache poisoned")
6533            .insert(field.clone(), decoded.clone());
6534        decoded
6535    }
6536
6537    fn structured_alias_target(
6538        &self,
6539        analyzer: &CppGraphSource<'_>,
6540        unit: &CodeUnit,
6541    ) -> Option<StructuredAliasTarget> {
6542        if let Some(cached) = self
6543            .structured_alias_targets
6544            .lock()
6545            .expect("C++ structured alias target cache poisoned")
6546            .get(unit)
6547            .cloned()
6548        {
6549            return cached;
6550        }
6551        let decoded = decode_structured_alias_target(analyzer, unit);
6552        self.structured_alias_targets
6553            .lock()
6554            .expect("C++ structured alias target cache poisoned")
6555            .insert(unit.clone(), decoded.clone());
6556        decoded
6557    }
6558
6559    pub fn type_candidates<'b>(
6560        &'b self,
6561        file: &ProjectFile,
6562        normalized: &str,
6563    ) -> Vec<&'b CodeUnit> {
6564        let mut candidates = self
6565            .candidate_units(file, normalized, TargetKind::Type)
6566            .into_iter()
6567            .filter(|unit| unit.kind() == CodeUnitType::Class || is_type_alias(unit))
6568            .collect::<Vec<_>>();
6569        dedup_unit_refs(&mut candidates);
6570        candidates
6571    }
6572
6573    pub fn named_candidates_for_normalized<'b>(
6574        &'b self,
6575        file: &ProjectFile,
6576        normalized: &str,
6577        kind: TargetKind,
6578    ) -> Vec<&'b CodeUnit> {
6579        let mut candidates = self
6580            .candidate_units(file, normalized, kind)
6581            .into_iter()
6582            .filter(|unit| {
6583                matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
6584            })
6585            .collect::<Vec<_>>();
6586        dedup_unit_refs(&mut candidates);
6587        candidates
6588    }
6589
6590    pub fn candidate_units<'b>(
6591        &'b self,
6592        file: &ProjectFile,
6593        normalized: &str,
6594        kind: TargetKind,
6595    ) -> Vec<&'b CodeUnit> {
6596        if normalized.contains("::") {
6597            // `normalized` comes from `normalize_cpp_reference_text`, which
6598            // truncates at the first `(`/`{`/`<`, leaving a plain `::`-joined
6599            // qualified-id with no embedded `.`/`/`/`\` and operator tokens
6600            // kept intact by the shared splitter's operator merge — the same
6601            // domain `cpp_reference_fqn_candidates` below already parses with
6602            // the shared splitter. Re-tokenizing and taking the last segment
6603            // reproduces `rsplit("::").find(non-empty)`'s terminal-component
6604            // scan exactly.
6605            let Some(identifier) = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6606                brokk_bifrost_core::analyzer::Language::Cpp,
6607                normalized,
6608            )
6609            .pop() else {
6610                return Vec::new();
6611            };
6612            let fqns = cpp_reference_fqn_candidates(normalized, kind);
6613            return self
6614                .visible_identifier_candidates(file, &identifier)
6615                .filter(|unit| {
6616                    #[cfg(any(test, feature = "test-support"))]
6617                    self.qualified_candidate_inspections
6618                        .fetch_add(1, Ordering::Relaxed);
6619                    fqns.iter().any(|fqn| unit.fq_name() == *fqn)
6620                        || canonical_cpp_name_matches(unit, normalized)
6621                })
6622                .collect();
6623        }
6624        self.visible_identifier_candidates(file, normalized)
6625            .collect()
6626    }
6627
6628    #[cfg(any(test, feature = "test-support"))]
6629    pub fn reset_qualified_candidate_inspections(&self) {
6630        self.qualified_candidate_inspections
6631            .store(0, Ordering::Relaxed);
6632    }
6633
6634    #[cfg(any(test, feature = "test-support"))]
6635    pub fn qualified_candidate_inspections(&self) -> usize {
6636        self.qualified_candidate_inspections.load(Ordering::Relaxed)
6637    }
6638
6639    #[cfg(any(test, feature = "test-support"))]
6640    pub fn reset_target_preserving_type_resolution_count(&self) {
6641        self.target_preserving_type_resolution_count
6642            .store(0, Ordering::Relaxed);
6643    }
6644
6645    #[cfg(any(test, feature = "test-support"))]
6646    pub fn target_preserving_type_resolution_count(&self) -> usize {
6647        self.target_preserving_type_resolution_count
6648            .load(Ordering::Relaxed)
6649    }
6650
6651    #[cfg(any(test, feature = "test-support"))]
6652    pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
6653        self.visible_parser_alias_name_set_build_count
6654            .load(Ordering::Relaxed)
6655    }
6656
6657    #[cfg(any(test, feature = "test-support"))]
6658    pub fn visible_parser_alias_target_names_build_count(&self) -> usize {
6659        self.visible_parser_alias_target_names_build_count
6660            .load(Ordering::Relaxed)
6661    }
6662}
6663
6664#[derive(Default)]
6665struct IncludeGraph {
6666    targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
6667}
6668
6669impl IncludeGraph {
6670    fn extend_with<F>(
6671        &mut self,
6672        root: &ProjectFile,
6673        cancellation: Option<&CancellationToken>,
6674        targets_for: &mut F,
6675    ) where
6676        F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
6677    {
6678        let mut stack = vec![root.clone()];
6679        while let Some(file) = stack.pop() {
6680            if cancellation.is_some_and(CancellationToken::is_cancelled) {
6681                break;
6682            }
6683            if self.targets_by_file.contains_key(&file) {
6684                continue;
6685            }
6686            let targets = targets_for(&file);
6687            stack.extend(targets.iter().cloned());
6688            self.targets_by_file.insert(file, targets);
6689        }
6690    }
6691
6692    fn files(&self) -> impl Iterator<Item = &ProjectFile> {
6693        self.targets_by_file.keys()
6694    }
6695
6696    fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
6697        self.targets_by_file
6698            .get(file)
6699            .map(Vec::as_slice)
6700            .unwrap_or_default()
6701    }
6702}
6703
6704pub struct VisibilityData {
6705    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
6706    pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
6707}
6708
6709/// Build the per-root include closure and the declarations each root can see
6710/// through it.
6711///
6712/// `declarations_for` takes the reading to answer in (issue #1970): a root
6713/// compiled as C sees the C reading of every file in its closure, a root
6714/// compiled as C++ sees the C++ reading, and `reading_is_c_for` decides which
6715/// per root. The two readings agree for all but a handful of headers, so the
6716/// C map is built only when some root actually asks for it, and only over the
6717/// files that root reaches.
6718pub fn build_visibility_data<F, R, D>(
6719    roots: &HashSet<ProjectFile>,
6720    cancellation: Option<&CancellationToken>,
6721    mut targets_for: F,
6722    mut reading_is_c_for: R,
6723    mut declarations_for: D,
6724) -> VisibilityData
6725where
6726    F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
6727    R: FnMut(&ProjectFile) -> bool,
6728    D: FnMut(&ProjectFile, bool) -> BTreeSet<CodeUnit>,
6729{
6730    let mut include_graph = IncludeGraph::default();
6731    for file in roots {
6732        if cancellation.is_some_and(CancellationToken::is_cancelled) {
6733            break;
6734        }
6735        include_graph.extend_with(file, cancellation, &mut targets_for);
6736    }
6737    let cpp_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
6738        .files()
6739        .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
6740        .map(|file| (file.clone(), declarations_for(file, false)))
6741        .collect();
6742    let mut c_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = HashMap::default();
6743    let mut visible_by_file = HashMap::default();
6744    let mut visible_source_files_by_root = HashMap::default();
6745    for file in roots {
6746        if cancellation.is_some_and(CancellationToken::is_cancelled) {
6747            break;
6748        }
6749        let mut visited = HashSet::default();
6750        let mut visible = HashSet::default();
6751        let declarations_by_file = if reading_is_c_for(file) {
6752            for reached in cpp_declarations_by_file.keys() {
6753                if !c_declarations_by_file.contains_key(reached) {
6754                    let declarations = declarations_for(reached, true);
6755                    c_declarations_by_file.insert(reached.clone(), declarations);
6756                }
6757            }
6758            &c_declarations_by_file
6759        } else {
6760            &cpp_declarations_by_file
6761        };
6762        collect_visible_declarations(
6763            &include_graph,
6764            declarations_by_file,
6765            file,
6766            &mut visited,
6767            &mut visible,
6768            cancellation,
6769        );
6770        visible_by_file.insert(file.clone(), visible);
6771        visible_source_files_by_root.insert(file.clone(), visited);
6772    }
6773    VisibilityData {
6774        visible_by_file,
6775        visible_source_files_by_root,
6776    }
6777}
6778
6779/// Admit the class that an out-of-line definition proves is in scope.
6780///
6781/// `Owner::member(...) { ... }` in a file is structured proof that `Owner`
6782/// names a class-like entity in that file's scope: a member declaration can
6783/// live in a file other than its class's only when it is written out of line.
6784/// A file a build concatenates rather than compiles carries no `#include` edge
6785/// to the header declaring `Owner` -- google/wuffs
6786/// `internal/cgen/auxiliary/image.cc` defines
6787/// `DecodeImageResult::DecodeImageResult` and never includes `image.hh` -- so
6788/// every unqualified member and constructor reference in it had no candidate at
6789/// all (#1832).
6790///
6791/// The evidence is the indexed declaration's own owner name, taken from its
6792/// `FqName`, so this stays a structured answer rather than a text fallback.
6793/// Only an owner the file cannot already see is admitted: that is what keeps a
6794/// header declaring its own class from additionally seeing every same-named
6795/// class in the workspace, and it makes the pass free for the ordinary file
6796/// whose owners are all visible.
6797fn extend_with_out_of_line_owner_bindings(
6798    cpp: &dyn CppSource,
6799    visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
6800) {
6801    for (file, visible) in visible_by_file.iter_mut() {
6802        // The include-closure walk seeds every root with its own declarations,
6803        // so the file's members are already here; re-reading them from the
6804        // analyzer would pay for the same declaration set twice.
6805        let mut unseen_owners: HashSet<String> = visible
6806            .iter()
6807            .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
6808            .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
6809            .collect();
6810        if unseen_owners.is_empty() {
6811            continue;
6812        }
6813        for unit in visible.iter().filter(|unit| unit.is_class()) {
6814            unseen_owners.remove(&unit.fq_name());
6815        }
6816        let admitted = unseen_owners
6817            .iter()
6818            .flat_map(|owner| cpp.definitions(owner))
6819            .filter(CodeUnit::is_class)
6820            .collect::<Vec<_>>();
6821        visible.extend(admitted);
6822    }
6823}
6824
6825pub enum VisibleMemberResolution {
6826    Callable(Vec<CodeUnit>),
6827    NonCallable,
6828    AmbiguousKind,
6829    Missing,
6830}
6831
6832#[derive(Clone)]
6833pub enum EnclosingMemberOwnerResolution {
6834    Owner(CodeUnit),
6835    Ambiguous,
6836    Missing,
6837}
6838
6839pub fn resolve_declaring_member_owner(
6840    analyzer: &CppGraphSource<'_>,
6841    visibility: &VisibilityIndex<'_>,
6842    file: &ProjectFile,
6843    receiver_owner: &CodeUnit,
6844    member_name: &str,
6845) -> EnclosingMemberOwnerResolution {
6846    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
6847        return EnclosingMemberOwnerResolution::Missing;
6848    };
6849    let Some(receiver_owner) =
6850        visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
6851    else {
6852        return EnclosingMemberOwnerResolution::Ambiguous;
6853    };
6854    let resolve_level = |frontier: &[CodeUnit]| {
6855        let mut member_owners = Vec::new();
6856        for raw_owner in frontier {
6857            let Some(owner) =
6858                visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
6859            else {
6860                return EnclosingMemberOwnerResolution::Ambiguous;
6861            };
6862            for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
6863                let Some(member_owner) = type_owner_of(analyzer, member) else {
6864                    return EnclosingMemberOwnerResolution::Ambiguous;
6865                };
6866                if !member_owners
6867                    .iter()
6868                    .any(|existing| same_visible_symbol(existing, &member_owner))
6869                {
6870                    member_owners.push(member_owner);
6871                }
6872            }
6873        }
6874        match member_owners.len() {
6875            0 => EnclosingMemberOwnerResolution::Missing,
6876            1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
6877            _ => EnclosingMemberOwnerResolution::Ambiguous,
6878        }
6879    };
6880    // The first declaration on each structured base path hides deeper names,
6881    // regardless of whether its callable overload is applicable at a particular
6882    // call site. Applicability is checked only after this owner is established.
6883    let direct = resolve_level(std::slice::from_ref(&receiver_owner));
6884    if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
6885        return direct;
6886    }
6887    let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
6888    let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
6889    let mut path_matches = Vec::new();
6890    while let Some(raw_owner) = stack.pop() {
6891        let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
6892        else {
6893            return EnclosingMemberOwnerResolution::Ambiguous;
6894        };
6895        // Persisted hierarchy edges do not encode virtual-base or base-subobject paths.
6896        // Propagate at most two occurrences of each owner: that preserves the distinction
6897        // between one and multiple resolving base paths without exponential diamond walks.
6898        let propagated = propagated_counts.entry(owner.clone()).or_default();
6899        if *propagated == 2 {
6900            continue;
6901        }
6902        *propagated += 1;
6903        match resolve_level(std::slice::from_ref(&owner)) {
6904            EnclosingMemberOwnerResolution::Owner(owner) => {
6905                path_matches.push(owner);
6906                if path_matches.len() == 2 {
6907                    return EnclosingMemberOwnerResolution::Ambiguous;
6908                }
6909            }
6910            EnclosingMemberOwnerResolution::Ambiguous => {
6911                return EnclosingMemberOwnerResolution::Ambiguous;
6912            }
6913            EnclosingMemberOwnerResolution::Missing => {
6914                stack.extend(hierarchy.get_direct_ancestors(&owner));
6915            }
6916        }
6917    }
6918    match path_matches.len() {
6919        0 => EnclosingMemberOwnerResolution::Missing,
6920        1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
6921        _ => unreachable!("base-path matches are capped at one before returning"),
6922    }
6923}
6924
6925/// Resolve the declaring owner of a callable after applying a member
6926/// `using <Base>::<member>;` declaration to one exact call arity.
6927///
6928/// Ordinary member lookup is intentionally name-based: the first class that
6929/// declares a name hides the same name on deeper bases. A member
6930/// using-declaration is the one exception. When none of the declarations on
6931/// that first owner accepts the call arity, it can reintroduce an applicable
6932/// overload from the named base. If a declaration on the first owner does
6933/// accept the arity, argument types would be needed to choose between it and
6934/// a same-arity introduced overload, so this resolver conservatively keeps the
6935/// ordinary owner (#1835/#1843).
6936///
6937/// The caller supplies ordinary name-based owner resolution so a file scan can
6938/// reuse its existing owner cache before applying this callable-only exception.
6939pub fn resolve_declaring_callable_owner(
6940    analyzer: &CppGraphSource<'_>,
6941    visibility: &VisibilityIndex<'_>,
6942    file: &ProjectFile,
6943    ordinary: EnclosingMemberOwnerResolution,
6944    member_name: &str,
6945    call_arity: usize,
6946) -> EnclosingMemberOwnerResolution {
6947    let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
6948        return ordinary;
6949    };
6950    if visibility
6951        .visible_members_for_owner_name(file, ordinary_owner, member_name)
6952        .into_iter()
6953        .any(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity))
6954    {
6955        return ordinary;
6956    }
6957
6958    let mut pending = match member_using_declaration_bases(
6959        analyzer,
6960        visibility,
6961        file,
6962        ordinary_owner,
6963        member_name,
6964    ) {
6965        Ok(bases) => bases,
6966        Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
6967    };
6968    let mut visited = HashSet::default();
6969    let mut introduced_owners = Vec::new();
6970    while let Some(owner) = pending.pop() {
6971        if !visited.insert(owner.clone()) {
6972            continue;
6973        }
6974        let accepts_arity = visibility
6975            .visible_members_for_owner_name(file, &owner, member_name)
6976            .into_iter()
6977            .any(|unit| {
6978                unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity)
6979            });
6980        if accepts_arity {
6981            if !introduced_owners
6982                .iter()
6983                .any(|existing| same_visible_symbol(existing, &owner))
6984            {
6985                introduced_owners.push(owner);
6986            }
6987            continue;
6988        }
6989        match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
6990            Ok(bases) => pending.extend(bases),
6991            Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
6992        }
6993    }
6994    match introduced_owners.as_slice() {
6995        [] => ordinary,
6996        [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
6997        _ => EnclosingMemberOwnerResolution::Ambiguous,
6998    }
6999}
7000
7001fn member_using_declaration_bases(
7002    analyzer: &CppGraphSource<'_>,
7003    visibility: &VisibilityIndex<'_>,
7004    file: &ProjectFile,
7005    owner: &CodeUnit,
7006    member_name: &str,
7007) -> Result<Vec<CodeUnit>, ()> {
7008    let Some(source) = analyzer.get_source(owner, false) else {
7009        return Ok(Vec::new());
7010    };
7011    let scopes = cpp_member_using_declaration_scopes(&source, member_name);
7012    if scopes.is_empty() {
7013        return Ok(Vec::new());
7014    }
7015    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
7016        return Ok(Vec::new());
7017    };
7018    let mut bases = Vec::new();
7019    for raw_ancestor in hierarchy.get_ancestors(owner) {
7020        let Some(ancestor) =
7021            visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
7022        else {
7023            return Err(());
7024        };
7025        let qualified = cpp_name_for(&ancestor);
7026        if scopes
7027            .iter()
7028            .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
7029            && !bases
7030                .iter()
7031                .any(|existing| same_visible_symbol(existing, &ancestor))
7032        {
7033            bases.push(ancestor);
7034        }
7035    }
7036    Ok(bases)
7037}
7038
7039pub fn lexical_component_tiers<'a>(
7040    components: &'a [String],
7041    global: bool,
7042    lexical_scope: &'a [String],
7043) -> impl Iterator<Item = Vec<String>> + 'a {
7044    let first_prefix_len = if global { 0 } else { lexical_scope.len() };
7045    (0..=first_prefix_len).rev().map(move |prefix_len| {
7046        let mut qualified = Vec::with_capacity(prefix_len + components.len());
7047        qualified.extend_from_slice(&lexical_scope[..prefix_len]);
7048        qualified.extend_from_slice(components);
7049        qualified
7050    })
7051}
7052
7053pub fn build_visible_identifier_index(
7054    analyzer: &CppGraphSource<'_>,
7055    visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
7056    visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
7057    global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
7058) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
7059    let mut out = HashMap::default();
7060    for (file, visible) in visible_by_file {
7061        let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
7062        for unit in visible {
7063            if unit.is_field()
7064                && !visible_source_files_by_root
7065                    .get(file)
7066                    .is_some_and(|sources| sources.contains(unit.source()))
7067                && cpp_global_field_has_internal_linkage_cached(
7068                    analyzer,
7069                    global_field_internal_linkage,
7070                    unit,
7071                )
7072            {
7073                continue;
7074            }
7075            by_identifier
7076                .entry(unit.identifier().to_string())
7077                .or_default()
7078                .push(unit.clone());
7079        }
7080        for units in by_identifier.values_mut() {
7081            sort_lookup_units(units);
7082            units.dedup();
7083        }
7084        out.insert(file.clone(), by_identifier);
7085    }
7086    out
7087}
7088
7089fn sort_lookup_units(units: &mut [CodeUnit]) {
7090    units.sort_by(|left, right| {
7091        left.fq_name()
7092            .cmp(&right.fq_name())
7093            .then_with(|| left.signature().cmp(&right.signature()))
7094            .then_with(|| left.source().cmp(right.source()))
7095            .then_with(|| left.kind().cmp(&right.kind()))
7096            .then_with(|| {
7097                left.package_segment_count()
7098                    .cmp(&right.package_segment_count())
7099            })
7100            .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
7101            .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
7102    });
7103}
7104
7105fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
7106    let interner = segment_interner();
7107    for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
7108        let (left_text, left_kind) = interner.resolve(left_id);
7109        let (right_text, right_kind) = interner.resolve(right_id);
7110        let order = left_text
7111            .cmp(right_text)
7112            .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
7113        if order != CmpOrdering::Equal {
7114            return order;
7115        }
7116    }
7117    left.len().cmp(&right.len())
7118}
7119
7120const fn segment_kind_order(kind: SegmentKind) -> u8 {
7121    match kind {
7122        SegmentKind::Path => 0,
7123        SegmentKind::Package => 1,
7124        SegmentKind::Type => 2,
7125        SegmentKind::Companion => 3,
7126        SegmentKind::Nested => 4,
7127        SegmentKind::Member => 5,
7128        SegmentKind::Unknown => 6,
7129    }
7130}
7131
7132fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
7133    let mut deduped = Vec::with_capacity(units.len());
7134    for unit in units.drain(..) {
7135        if !deduped.contains(&unit) {
7136            deduped.push(unit);
7137        }
7138    }
7139    *units = deduped;
7140}
7141
7142pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
7143    // Same domain as `candidate_units` above: `reference` is a plain
7144    // `::`-joined qualified-id with operator tokens kept intact by the shared
7145    // splitter's operator merge.
7146    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
7147        brokk_bifrost_core::analyzer::Language::Cpp,
7148        reference,
7149    );
7150    if parts.is_empty() {
7151        return Vec::new();
7152    }
7153
7154    let mut candidates = Vec::new();
7155    for package_len in 0..parts.len() {
7156        let package = parts[..package_len].join("::");
7157        let rest = &parts[package_len..];
7158        if rest.is_empty() {
7159            continue;
7160        }
7161        match kind {
7162            TargetKind::Type | TargetKind::Constructor => {
7163                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
7164                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
7165            }
7166            TargetKind::FreeFunction
7167            | TargetKind::Method
7168            | TargetKind::GlobalField
7169            | TargetKind::MemberField
7170            | TargetKind::Macro => {
7171                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
7172                if rest.len() > 1 {
7173                    let owner = rest[..rest.len() - 1].join("$");
7174                    let short = format!("{}.{}", owner, rest[rest.len() - 1]);
7175                    push_cpp_fqn_candidate(&mut candidates, &package, &short);
7176                }
7177            }
7178        }
7179    }
7180    candidates
7181}
7182
7183fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
7184    let fqn = if package.is_empty() {
7185        short.to_string()
7186    } else {
7187        format!("{package}.{short}")
7188    };
7189    if !out.contains(&fqn) {
7190        out.push(fqn);
7191    }
7192}
7193
7194pub fn infer_cpp_initializer_type(
7195    analyzer: &CppGraphSource<'_>,
7196    visibility: &VisibilityIndex<'_>,
7197    file: &ProjectFile,
7198    source: &str,
7199    node: Node<'_>,
7200) -> Option<CodeUnit> {
7201    infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
7202        .and_then(|binding| binding.unit)
7203}
7204
7205pub fn infer_cpp_initializer_binding(
7206    analyzer: &CppGraphSource<'_>,
7207    visibility: &VisibilityIndex<'_>,
7208    file: &ProjectFile,
7209    source: &str,
7210    node: Node<'_>,
7211    receiver_resolver: Option<&ReceiverResolver<'_>>,
7212) -> Option<CppScanBinding> {
7213    match node.kind() {
7214        "new_expression" => {
7215            let text = normalize_cpp_whitespace(node_text(node, source));
7216            let rest = text.strip_prefix("new ").unwrap_or(text.as_str());
7217            let type_text = rest.split(['(', '{']).next().unwrap_or(rest);
7218            let name = normalize_cpp_type_name(type_text);
7219            Some(CppScanBinding::from_type_name(
7220                name.clone(),
7221                visibility.resolve_type(file, &name),
7222                1,
7223            ))
7224        }
7225        "call_expression" => node.child_by_field_name("function").and_then(|function| {
7226            let function_text = node_text(function, source);
7227            let direct_type_binding = visibility
7228                .resolve_type(file, function_text)
7229                .map(|unit| CppScanBinding::from_unit(unit, 0));
7230            if function.kind() == "template_function" && direct_type_binding.is_some() {
7231                let lexical_namespace = enclosing_namespace_context(node, source);
7232                let arity = visibility.call_arity_evidence(file, node, source).exact();
7233                if let Some(arity) = arity
7234                    && let Some(binding) = visibility.resolve_call_return_binding(
7235                        analyzer,
7236                        file,
7237                        function_text,
7238                        arity,
7239                        lexical_namespace.as_deref(),
7240                        direct_type_binding
7241                            .as_ref()
7242                            .and_then(|binding| binding.unit.as_ref()),
7243                    )
7244                {
7245                    return Some(binding);
7246                }
7247                let (has_callable, callable_binding) = visibility
7248                    .resolve_call_return_binding_without_arity(
7249                        analyzer,
7250                        file,
7251                        function_text,
7252                        lexical_namespace.as_deref(),
7253                        direct_type_binding
7254                            .as_ref()
7255                            .and_then(|binding| binding.unit.as_ref()),
7256                    );
7257                if let Some(binding) = callable_binding {
7258                    return Some(binding);
7259                }
7260                if has_callable {
7261                    return None;
7262                }
7263                return direct_type_binding;
7264            }
7265            let arity = visibility.call_arity_evidence(file, node, source).exact()?;
7266            let direct_type_binding_for_call = direct_type_binding.clone();
7267            resolve_static_method_call_return_binding(
7268                analyzer, visibility, file, source, function, arity,
7269            )
7270            .or_else(|| {
7271                // An applicable free function supplies the receiver value
7272                // before an unrelated visible type with the same terminal
7273                // name. The direct type still excludes its own constructor
7274                // declaration below and remains the construction fallback.
7275                visibility.resolve_call_return_binding(
7276                    analyzer,
7277                    file,
7278                    function_text,
7279                    arity,
7280                    enclosing_namespace_context(node, source).as_deref(),
7281                    direct_type_binding_for_call
7282                        .as_ref()
7283                        .and_then(|binding| binding.unit.as_ref()),
7284                )
7285            })
7286            .or(direct_type_binding)
7287            .or_else(|| {
7288                resolve_field_method_call_return_binding(
7289                    analyzer,
7290                    visibility,
7291                    file,
7292                    source,
7293                    function,
7294                    arity,
7295                    receiver_resolver,
7296                )
7297            })
7298        }),
7299        _ => None,
7300    }
7301}
7302
7303fn resolve_static_method_call_return_binding(
7304    analyzer: &CppGraphSource<'_>,
7305    visibility: &VisibilityIndex<'_>,
7306    file: &ProjectFile,
7307    source: &str,
7308    function: Node<'_>,
7309    arity: usize,
7310) -> Option<CppScanBinding> {
7311    if function.kind() != "qualified_identifier" {
7312        return None;
7313    }
7314    let qualified = normalize_cpp_reference_text(node_text(function, source));
7315    // A C++ qualified-id is `::`-joined with no embedded delimiters in any
7316    // single component (the shared splitter's operator-token merge keeps
7317    // `operator+`-style names intact), so re-tokenizing with the shared
7318    // structured splitter and peeling the terminal segment reproduces
7319    // `rsplit_once("::")`'s (owner, member) split exactly — same shape as
7320    // `cpp_out_of_line_function_owner`'s `qualified` split above.
7321    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
7322        brokk_bifrost_core::analyzer::Language::Cpp,
7323        &qualified,
7324    );
7325    let (owner_text, member_name) = match parts.split_last() {
7326        Some((member, owner_parts)) if !owner_parts.is_empty() => {
7327            (owner_parts.join("::"), member.clone())
7328        }
7329        _ => {
7330            let scope = function.child_by_field_name("scope")?;
7331            let name = function.child_by_field_name("name")?;
7332            (
7333                node_text(scope, source).to_string(),
7334                node_text(name, source).to_string(),
7335            )
7336        }
7337    };
7338    let owner = visibility.resolve_type(file, &owner_text)?;
7339    let candidates = visibility
7340        .visible_members_for_owner_name(file, &owner, &member_name)
7341        .into_iter()
7342        .filter(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity))
7343        .cloned()
7344        .collect::<Vec<_>>();
7345    unanimous_return_binding(analyzer, visibility, file, &candidates)
7346}
7347
7348fn resolve_field_method_call_return_binding(
7349    analyzer: &CppGraphSource<'_>,
7350    visibility: &VisibilityIndex<'_>,
7351    file: &ProjectFile,
7352    source: &str,
7353    function: Node<'_>,
7354    arity: usize,
7355    receiver_resolver: Option<&ReceiverResolver<'_>>,
7356) -> Option<CppScanBinding> {
7357    if function.kind() != "field_expression" {
7358        return None;
7359    }
7360    let receiver_resolver = receiver_resolver?;
7361    let field = function.child_by_field_name("field")?;
7362    let member_name = node_text(function_terminal_node(field), source);
7363    let receiver = function
7364        .child_by_field_name("argument")
7365        .or_else(|| function.named_child(0))?;
7366    let owners = receiver_resolver(receiver, source);
7367    let mut candidates = Vec::new();
7368    for owner in owners {
7369        let declaring_owner =
7370            match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
7371                EnclosingMemberOwnerResolution::Owner(owner) => owner,
7372                EnclosingMemberOwnerResolution::Missing => continue,
7373                EnclosingMemberOwnerResolution::Ambiguous => return None,
7374            };
7375        candidates.extend(
7376            visibility
7377                .visible_members_for_owner_name(file, &declaring_owner, member_name)
7378                .into_iter()
7379                .filter(|unit| {
7380                    unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity)
7381                })
7382                .cloned(),
7383        );
7384    }
7385    unanimous_return_binding(analyzer, visibility, file, &candidates)
7386}
7387
7388fn unanimous_return_binding(
7389    analyzer: &CppGraphSource<'_>,
7390    visibility: &VisibilityIndex<'_>,
7391    file: &ProjectFile,
7392    candidates: &[CodeUnit],
7393) -> Option<CppScanBinding> {
7394    let mut resolved_return: Option<CppScanBinding> = None;
7395    for function in candidates {
7396        let metadata = analyzer.signature_metadata(function);
7397        let return_types = if metadata.is_empty() {
7398            vec![cpp_function_return_type_text(analyzer, function)?]
7399        } else {
7400            metadata
7401                .iter()
7402                .map(|metadata| metadata.return_type_text().map(str::to_string))
7403                .collect::<Option<Vec<_>>>()?
7404        };
7405        for return_text in return_types {
7406            let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
7407            let name = normalize_cpp_type_name(&return_text);
7408            let binding = CppScanBinding::from_type_name(
7409                name.clone(),
7410                visibility
7411                    .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
7412                indirection,
7413            );
7414            if let Some(existing) = resolved_return.as_ref()
7415                && (existing.indirection != binding.indirection
7416                    || match (&existing.unit, &binding.unit) {
7417                        (Some(left), Some(right)) => !same_visible_symbol(left, right),
7418                        (None, None) => existing.type_name != binding.type_name,
7419                        (Some(_), None) | (None, Some(_)) => true,
7420                    })
7421            {
7422                return None;
7423            }
7424            resolved_return = Some(binding);
7425        }
7426    }
7427    resolved_return
7428}
7429
7430fn aliases_from_prepared_source(cpp: &dyn CppSource, file: &ProjectFile) -> Vec<CppAlias> {
7431    let Some(prepared) = cpp.prepared_syntax(file) else {
7432        return Vec::new();
7433    };
7434    let mut aliases = Vec::new();
7435    collect_cpp_aliases(prepared.tree().root_node(), prepared.source(), &mut aliases);
7436    aliases
7437}
7438
7439fn collect_cpp_aliases(root: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
7440    let mut stack = vec![root];
7441    while let Some(node) = stack.pop() {
7442        match node.kind() {
7443            "alias_declaration" if alias_has_visible_file_scope(node) => {
7444                if let Some(alias) = cpp_alias_from_alias_declaration(node, source) {
7445                    out.push(alias);
7446                }
7447            }
7448            "type_definition" if alias_has_visible_file_scope(node) => {
7449                collect_typedef_aliases(node, source, out)
7450            }
7451            _ => {}
7452        }
7453
7454        for index in (0..node.named_child_count()).rev() {
7455            if let Some(child) = node.named_child(index) {
7456                stack.push(child);
7457            }
7458        }
7459    }
7460}
7461
7462fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
7463    let mut current = node.parent();
7464    while let Some(parent) = current {
7465        match parent.kind() {
7466            "translation_unit"
7467            | "namespace_definition"
7468            | "declaration_list"
7469            | "linkage_specification" => current = parent.parent(),
7470            "template_declaration" => current = parent.parent(),
7471            _ => return false,
7472        }
7473    }
7474    true
7475}
7476
7477fn cpp_alias_from_alias_declaration(node: Node<'_>, source: &str) -> Option<CppAlias> {
7478    let name = node
7479        .child_by_field_name("name")
7480        .and_then(|node| normalize_reference_name(node_text(node, source)))?;
7481    let target = node
7482        .child_by_field_name("type")
7483        .and_then(|node| normalize_reference_name(node_text(node, source)))?;
7484    Some(CppAlias {
7485        name,
7486        target,
7487        namespace: enclosing_namespace_context(node, source),
7488    })
7489}
7490
7491fn collect_typedef_aliases(node: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
7492    let Some(type_node) = node.child_by_field_name("type") else {
7493        return;
7494    };
7495    let Some(target) = normalize_reference_name(node_text(type_node, source)) else {
7496        return;
7497    };
7498
7499    let mut cursor = node.walk();
7500    for child in node.named_children(&mut cursor) {
7501        if same_node(child, type_node) {
7502            continue;
7503        }
7504        if let Some(name) = extract_typedef_declarator_name(child, source) {
7505            out.push(CppAlias {
7506                name,
7507                target: target.clone(),
7508                namespace: enclosing_namespace_context(node, source),
7509            });
7510        }
7511    }
7512}
7513
7514fn extract_typedef_declarator_name(node: Node<'_>, source: &str) -> Option<String> {
7515    match node.kind() {
7516        "identifier" | "field_identifier" | "type_identifier" | "qualified_identifier" => {
7517            normalize_reference_name(node_text(node, source))
7518        }
7519        _ => node
7520            .child_by_field_name("declarator")
7521            .or_else(|| node.child_by_field_name("name"))
7522            .or_else(|| last_named_child(node))
7523            .and_then(|child| extract_typedef_declarator_name(child, source)),
7524    }
7525}
7526
7527fn last_named_child(node: Node<'_>) -> Option<Node<'_>> {
7528    let count = node.named_child_count();
7529    if count == 0 {
7530        None
7531    } else {
7532        node.named_child(count - 1)
7533    }
7534}
7535
7536pub fn collect_include_closure(
7537    analyzer: &CppGraphSource<'_>,
7538    include_targets: &IncludeTargetIndex,
7539    file: &ProjectFile,
7540    out: &mut HashSet<ProjectFile>,
7541    cancellation: Option<&CancellationToken>,
7542) {
7543    let mut stack = vec![file.clone()];
7544    while let Some(file) = stack.pop() {
7545        if cancellation.is_some_and(CancellationToken::is_cancelled) {
7546            break;
7547        }
7548        if !out.insert(file.clone()) {
7549            continue;
7550        }
7551        let imports = analyzer.import_statements(&file);
7552        for include in cpp_include_paths(&imports) {
7553            for target in resolve_include_targets_with_index(&file, &include, include_targets) {
7554                stack.push(target);
7555            }
7556        }
7557    }
7558}
7559
7560fn collect_visible_declarations(
7561    include_graph: &IncludeGraph,
7562    declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
7563    file: &ProjectFile,
7564    visited: &mut HashSet<ProjectFile>,
7565    out: &mut HashSet<CodeUnit>,
7566    cancellation: Option<&CancellationToken>,
7567) {
7568    let mut stack = vec![file.clone()];
7569    while let Some(file) = stack.pop() {
7570        if cancellation.is_some_and(CancellationToken::is_cancelled) {
7571            break;
7572        }
7573        if !visited.insert(file.clone()) {
7574            continue;
7575        }
7576        if let Some(declarations) = declarations_by_file.get(&file) {
7577            out.extend(declarations.iter().cloned());
7578        }
7579        stack.extend(include_graph.targets(&file).iter().cloned());
7580    }
7581}
7582
7583pub fn signature_arity(signature: Option<&str>) -> usize {
7584    let Some(signature) = signature else {
7585        return 0;
7586    };
7587    let inner = signature
7588        .find('(')
7589        .and_then(|open| {
7590            signature[open + 1..]
7591                .find(')')
7592                .map(|close| &signature[open + 1..open + 1 + close])
7593        })
7594        .unwrap_or(signature)
7595        .trim();
7596    if inner.is_empty() || inner == "void" {
7597        return 0;
7598    }
7599    cpp_split_top_level_commas(inner).count()
7600}
7601
7602fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
7603    let source = format!("void __bifrost_macro_parameters({replacement});");
7604    let mut parser = Parser::new();
7605    parser
7606        .set_language(&tree_sitter_cpp::LANGUAGE.into())
7607        .ok()?;
7608    let tree = parser.parse(&source, None)?;
7609    let root = tree.root_node();
7610    if root.has_error() {
7611        return None;
7612    }
7613    let declaration = root.named_child(0)?;
7614    let declarator = declaration.child_by_field_name("declarator")?;
7615    let parameters = declarator.child_by_field_name("parameters")?;
7616    let mut required = 0;
7617    let mut total = 0;
7618    let mut repeated = false;
7619    let mut cursor = parameters.walk();
7620    for parameter in parameters.children(&mut cursor) {
7621        match parameter.kind() {
7622            "parameter_declaration" => {
7623                if parameter.child_by_field_name("declarator").is_none()
7624                    && parameter
7625                        .child_by_field_name("type")
7626                        .is_some_and(|type_node| node_text(type_node, &source).trim() == "void")
7627                {
7628                    continue;
7629                }
7630                required += 1;
7631                total += 1;
7632            }
7633            "optional_parameter_declaration" => total += 1,
7634            "variadic_parameter" | "variadic_parameter_declaration" | "..." => {
7635                repeated = true;
7636            }
7637            _ => {}
7638        }
7639    }
7640    Some(CallableArity::new(required, total, repeated))
7641}
7642
7643pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> CallableArity {
7644    analyzer
7645        .signature_metadata(unit)
7646        .into_iter()
7647        .find_map(|metadata| metadata.callable_arity())
7648        .unwrap_or_else(|| CallableArity::exact(signature_arity(unit.signature())))
7649}
7650
7651pub fn cpp_callable_parameter_types(
7652    analyzer: &CppGraphSource<'_>,
7653    unit: &CodeUnit,
7654) -> Option<Vec<String>> {
7655    analyzer
7656        .signature_metadata(unit)
7657        .into_iter()
7658        .find_map(|metadata| metadata.callable_parameter_types().map(<[String]>::to_vec))
7659        .or_else(|| unit.signature().and_then(cpp_signature_param_types))
7660}
7661
7662fn merge_compatible_callable_arities(
7663    left: CallableArity,
7664    right: CallableArity,
7665) -> Option<CallableArity> {
7666    let total = left.total();
7667    let left_repeated = left.accepts(total.saturating_add(1));
7668    let right_repeated = right.accepts(right.total().saturating_add(1));
7669    if total != right.total() || left_repeated != right_repeated {
7670        return None;
7671    }
7672    let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
7673    Some(CallableArity::new(required, total, left_repeated))
7674}
7675
7676fn find_include_activation(
7677    cpp: &dyn CppSource,
7678    file: &ProjectFile,
7679    prepared: &PreparedSyntaxTree,
7680    donor_source: &ProjectFile,
7681) -> Option<usize> {
7682    let include_targets = cpp.include_target_index();
7683    let mut direct_includes = Vec::new();
7684    let mut nodes = vec![prepared.tree().root_node()];
7685    // An include activates for the whole file, so only an unconditional
7686    // directive counts here.
7687    let reference = CallableReferenceContext {
7688        file,
7689        position: None,
7690    };
7691    while let Some(node) = nodes.pop() {
7692        if node.kind() == "preproc_include" {
7693            if callable_preprocessor_context_is_visible_for_reference(
7694                node,
7695                prepared.source(),
7696                &reference,
7697            ) {
7698                let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
7699                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
7700                    if let Some(target) = unique_include_target(resolve_include_targets_with_index(
7701                        file,
7702                        &include,
7703                        include_targets,
7704                    )) {
7705                        direct_includes.push((node.end_byte(), target));
7706                    }
7707                }
7708            }
7709            continue;
7710        }
7711        for index in (0..node.named_child_count()).rev() {
7712            if let Some(child) = node.named_child(index) {
7713                nodes.push(child);
7714            }
7715        }
7716    }
7717    direct_includes.sort_by_key(|(activation, _)| *activation);
7718    let mut known_missing = HashSet::default();
7719    direct_includes
7720        .into_iter()
7721        .find(|(_, direct)| {
7722            unconditional_include_reaches(
7723                cpp,
7724                include_targets,
7725                direct,
7726                donor_source,
7727                file,
7728                &mut known_missing,
7729            )
7730        })
7731        .map(|(activation, _)| activation)
7732}
7733
7734fn find_conditional_include_projection_index(
7735    cpp: &dyn CppSource,
7736    file: &ProjectFile,
7737    prepared: &PreparedSyntaxTree,
7738    on_state: &dyn Fn(),
7739) -> ConditionalIncludeProjectionIndex {
7740    let include_targets = cpp.include_target_index();
7741    let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
7742        HashMap::default();
7743    let mut pending = Vec::new();
7744    let mut nodes = vec![prepared.tree().root_node()];
7745    while let Some(node) = nodes.pop() {
7746        if node.kind() == "preproc_include" {
7747            let Some(required_guards) = preprocessor_guard_environment(node, prepared.source())
7748            else {
7749                continue;
7750            };
7751            let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
7752            for include in cpp_include_paths(std::slice::from_ref(&raw)) {
7753                let Some(target) = unique_include_target(resolve_include_targets_with_index(
7754                    file,
7755                    &include,
7756                    include_targets,
7757                )) else {
7758                    continue;
7759                };
7760                pending.push((target, node.end_byte(), required_guards.clone()));
7761            }
7762            continue;
7763        }
7764        for index in (0..node.named_child_count()).rev() {
7765            if let Some(child) = node.named_child(index) {
7766                nodes.push(child);
7767            }
7768        }
7769    }
7770
7771    // One reached file can have several distinct compatible guard paths. Each
7772    // (file, activation byte) key keeps only the inclusion-minimal guard sets:
7773    // the consumers ask existence questions whose answers are monotone in the
7774    // guard set -- a path whose requirements hold, stay stable, and stay
7775    // compatible under one environment does so under every subset as well --
7776    // so a state subsumed by an existing subset cannot witness anything its
7777    // subset does not, and inserting a smaller set evicts the supersets it
7778    // subsumes. Exact-set dedup still terminated cycles, but dense `#ifdef`
7779    // lattices (QMK's per-keyboard feature guards) enumerated the powerset of
7780    // path-union guard sets through it: the state space, the per-key linear
7781    // scans, and resident memory all grew without bound (#2365).
7782    let mut expanded: HashMap<(ProjectFile, usize), Vec<HashSet<PreprocessorGuard>>> =
7783        HashMap::default();
7784    while let Some((current_file, activation_byte, required_guards)) = pending.pop() {
7785        let guard_sets = expanded
7786            .entry((current_file.clone(), activation_byte))
7787            .or_default();
7788        if guard_sets
7789            .iter()
7790            .any(|existing| existing.is_subset(&required_guards))
7791        {
7792            continue;
7793        }
7794        let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
7795            .drain(..)
7796            .partition(|existing| required_guards.is_subset(existing));
7797        *guard_sets = kept;
7798        guard_sets.push(required_guards.clone());
7799        if !evicted.is_empty()
7800            && let Some(projections) = projections_by_source.get_mut(&current_file)
7801        {
7802            projections.retain(|projection| {
7803                projection.activation_byte != activation_byte
7804                    || !evicted.contains(&projection.required_guards)
7805            });
7806        }
7807        on_state();
7808
7809        // A fresh minimal set has no equal in the store: equality would have
7810        // been caught by the subset check above.
7811        projections_by_source
7812            .entry(current_file.clone())
7813            .or_default()
7814            .push(ConditionalIncludeProjection {
7815                activation_byte,
7816                required_guards: required_guards.clone(),
7817            });
7818
7819        let Some(current_prepared) = cpp.prepared_syntax(&current_file) else {
7820            continue;
7821        };
7822        let mut nodes = vec![current_prepared.tree().root_node()];
7823        while let Some(node) = nodes.pop() {
7824            if node.kind() == "preproc_include" {
7825                let Some(include_guards) =
7826                    preprocessor_guard_environment(node, current_prepared.source())
7827                else {
7828                    continue;
7829                };
7830                let Some(path_guards) =
7831                    merge_preprocessor_guards(&required_guards, &include_guards)
7832                else {
7833                    continue;
7834                };
7835                let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
7836                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
7837                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
7838                        &current_file,
7839                        &include,
7840                        include_targets,
7841                    )) else {
7842                        continue;
7843                    };
7844                    pending.push((target, activation_byte, path_guards.clone()));
7845                }
7846                continue;
7847            }
7848            for index in (0..node.named_child_count()).rev() {
7849                if let Some(child) = node.named_child(index) {
7850                    nodes.push(child);
7851                }
7852            }
7853        }
7854    }
7855
7856    projections_by_source
7857        .into_iter()
7858        .map(|(source, mut projections)| {
7859            projections.sort_by_key(|projection| projection.activation_byte);
7860            (source, Arc::from(projections))
7861        })
7862        .collect()
7863}
7864
7865fn unconditional_include_reaches(
7866    cpp: &dyn CppSource,
7867    include_targets: &IncludeTargetIndex,
7868    first: &ProjectFile,
7869    donor_source: &ProjectFile,
7870    reference_file: &ProjectFile,
7871    known_missing: &mut HashSet<ProjectFile>,
7872) -> bool {
7873    if first == donor_source {
7874        return true;
7875    }
7876    if known_missing.contains(first) {
7877        return false;
7878    }
7879    let reference_is_c = reference_file
7880        .rel_path()
7881        .extension()
7882        .and_then(|extension| extension.to_str())
7883        == Some("c");
7884    if let Some(reaches) =
7885        cpp.cached_unconditional_include_reachability(first, donor_source, reference_is_c)
7886    {
7887        return reaches;
7888    }
7889    let mut visited = HashSet::default();
7890    let mut files = vec![first.clone()];
7891    // Only an unconditional directive extends the include reach, so the walk
7892    // asks the question without a reference position.
7893    let reference = CallableReferenceContext {
7894        file: reference_file,
7895        position: None,
7896    };
7897    while let Some(file) = files.pop() {
7898        if file == *donor_source {
7899            cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, true);
7900            return true;
7901        }
7902        if known_missing.contains(&file) || !visited.insert(file.clone()) {
7903            continue;
7904        }
7905        let Some(prepared) = cpp.prepared_syntax(&file) else {
7906            continue;
7907        };
7908        let mut nodes = vec![prepared.tree().root_node()];
7909        while let Some(node) = nodes.pop() {
7910            if node.kind() == "preproc_include" {
7911                if callable_preprocessor_context_is_visible_for_reference(
7912                    node,
7913                    prepared.source(),
7914                    &reference,
7915                ) {
7916                    let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
7917                    for include in cpp_include_paths(std::slice::from_ref(&raw)) {
7918                        if let Some(target) = unique_include_target(
7919                            resolve_include_targets_with_index(&file, &include, include_targets),
7920                        ) {
7921                            files.push(target);
7922                        }
7923                    }
7924                }
7925                continue;
7926            }
7927            for index in (0..node.named_child_count()).rev() {
7928                if let Some(child) = node.named_child(index) {
7929                    nodes.push(child);
7930                }
7931            }
7932        }
7933    }
7934    known_missing.extend(visited);
7935    cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, false);
7936    false
7937}
7938
7939fn declaration_guard_requirements(
7940    analyzer: &CppGraphSource<'_>,
7941    cpp: &dyn CppSource,
7942    candidate: &CodeUnit,
7943) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
7944    let Some(prepared) = cpp.prepared_syntax(candidate.source()) else {
7945        return Vec::new();
7946    };
7947    let root = prepared.tree().root_node();
7948    analyzer
7949        .ranges(candidate)
7950        .into_iter()
7951        .filter_map(|range| {
7952            root.descendant_for_byte_range(range.start_byte, range.end_byte)
7953                .and_then(|node| preprocessor_guard_environment(node, prepared.source()))
7954                // A class name is injected into its own body at the declaration's
7955                // introduction point, not after the complete class range. Using
7956                // the start also preserves normal before/after ordering for aliases.
7957                .map(|required| (range.start_byte, required))
7958        })
7959        .collect()
7960}
7961
7962fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
7963    analyzer
7964        .ranges(candidate)
7965        .into_iter()
7966        .map(|range| range.start_byte)
7967        .min()
7968}
7969
7970/// The macro names every configuration in `contexts` defines -- the fact set
7971/// one file's compile-database coverage proves (#2011). `None` when the
7972/// database has no entry for the file, which is different from an empty
7973/// intersection: no entry means no coverage, while an empty intersection is
7974/// covered-and-proves-nothing.
7975fn context_fact_names(contexts: &[CppCompileContext]) -> Option<HashSet<String>> {
7976    let (first, rest) = contexts.split_first()?;
7977    Some(
7978        first
7979            .defined_macros
7980            .iter()
7981            .filter(|name| {
7982                rest.iter()
7983                    .all(|context| context.defined_macros.contains(*name))
7984            })
7985            .cloned()
7986            .collect(),
7987    )
7988}
7989
7990fn guard_requirements_hold_at_reference(
7991    required: &HashSet<PreprocessorGuard>,
7992    reference: Option<&HashSet<PreprocessorGuard>>,
7993) -> bool {
7994    reference.is_some_and(|active| {
7995        required
7996            .iter()
7997            .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
7998    })
7999}
8000
8001fn preprocessor_guard_holds_at_reference(
8002    required: &PreprocessorGuard,
8003    active: &HashSet<PreprocessorGuard>,
8004) -> bool {
8005    if active.contains(required) {
8006        return true;
8007    }
8008    let active_expression = BooleanGuardExpression::all(
8009        active
8010            .iter()
8011            .filter_map(PreprocessorGuard::as_boolean_expression),
8012    );
8013    required
8014        .as_boolean_expression()
8015        .is_some_and(|required| active_expression.implies(&required))
8016}
8017
8018/// Cross-file guard rule: two guard sets are compatible when neither one
8019/// contradicts the other. Use this instead of the subset test whenever the
8020/// guards come from a foreign file, which resolves its own conditionals
8021/// independently of the reference.
8022fn guards_compatible_at_reference(
8023    declaration: &HashSet<PreprocessorGuard>,
8024    reference: Option<&HashSet<PreprocessorGuard>>,
8025) -> bool {
8026    reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
8027}
8028
8029/// The byte range of the `#if`/`#elif`/`#else` chain that encloses the smallest
8030/// node covering `[start_byte, end_byte)`, or `None` when nothing there is
8031/// conditional.
8032///
8033/// Two declarations of one name that report the same chain stand in different
8034/// branches of it, so at most one of them is compiled in any configuration.
8035/// They are alternate spellings of a single declaration, not competing
8036/// declarations, and navigation must not present them as an ambiguity.
8037pub fn preprocessor_conditional_family_range(
8038    root: Node<'_>,
8039    start_byte: usize,
8040    end_byte: usize,
8041) -> Option<(usize, usize)> {
8042    let node = root.descendant_for_byte_range(start_byte, end_byte)?;
8043    let mut ancestor = Some(node);
8044    while let Some(current) = ancestor {
8045        if is_preprocessor_conditional(current)
8046            && preprocessor_conditional_contains_descendant(current, node)
8047        {
8048            let family = preprocessor_conditional_family_root(current);
8049            return Some((family.start_byte(), family.end_byte()));
8050        }
8051        ancestor = current.parent();
8052    }
8053    None
8054}
8055
8056fn preprocessor_conditional_family_for_declaration(node: Node<'_>) -> Option<Node<'_>> {
8057    let mut ancestor = node.parent();
8058    while let Some(current) = ancestor {
8059        if is_preprocessor_conditional(current)
8060            && preprocessor_conditional_contains_descendant(current, node)
8061        {
8062            let family = preprocessor_conditional_family_root(current);
8063            if preprocessor_conditional_family_has_terminal_else(family) {
8064                return Some(family);
8065            }
8066        }
8067        ancestor = current.parent();
8068    }
8069    None
8070}
8071
8072fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
8073    while let Some(parent) = conditional.parent() {
8074        let is_alternative = parent
8075            .child_by_field_name("alternative")
8076            .is_some_and(|alternative| {
8077                alternative.start_byte() == conditional.start_byte()
8078                    && alternative.end_byte() == conditional.end_byte()
8079            });
8080        if !is_alternative {
8081            break;
8082        }
8083        conditional = parent;
8084    }
8085    conditional
8086}
8087
8088fn preprocessor_conditional_family_has_terminal_else(mut conditional: Node<'_>) -> bool {
8089    loop {
8090        let Some(alternative) = conditional.child_by_field_name("alternative") else {
8091            return false;
8092        };
8093        match alternative.kind() {
8094            "preproc_else" => return true,
8095            "preproc_elif" => conditional = alternative,
8096            _ => return false,
8097        }
8098    }
8099}
8100
8101pub fn preprocessor_guard_environment(
8102    node: Node<'_>,
8103    source: &str,
8104) -> Option<HashSet<PreprocessorGuard>> {
8105    let mut guards = HashSet::default();
8106    let mut ancestor = node.parent();
8107    while let Some(conditional) = ancestor {
8108        if matches!(
8109            conditional.kind(),
8110            "preproc_if" | "preproc_ifdef" | "preproc_elif"
8111        ) && !is_file_covering_include_guard(conditional, source)
8112            && preprocessor_conditional_contains_descendant(conditional, node)
8113        {
8114            let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
8115            match guard {
8116                PreprocessorGuard::Constant(true) => {
8117                    ancestor = conditional.parent();
8118                    continue;
8119                }
8120                PreprocessorGuard::Constant(false) => return None,
8121                _ => {}
8122            }
8123            if guards.contains(&guard.negated()) {
8124                return None;
8125            }
8126            guards.insert(guard);
8127        }
8128        ancestor = conditional.parent();
8129    }
8130    if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
8131        match guard {
8132            PreprocessorGuard::Constant(true) => {}
8133            PreprocessorGuard::Constant(false) => return None,
8134            _ => {
8135                if guards.contains(&guard.negated()) {
8136                    return None;
8137                }
8138                guards.insert(guard);
8139            }
8140        }
8141    }
8142    Some(guards)
8143}
8144
8145fn fragmented_statement_preprocessor_guard(
8146    descendant: Node<'_>,
8147    source: &str,
8148) -> Option<PreprocessorGuard> {
8149    // A conditional that starts before `} else if (...) {` crosses the
8150    // enclosing statement's grammar boundary. tree-sitter leaves its opener
8151    // as a `preproc_if` with a missing terminator in the consequence and
8152    // reparses the real `#endif` as a `preproc_call` in the alternative. Pair
8153    // those structured nodes before restoring the guard to intervening uses.
8154    let mut ancestor = descendant.parent();
8155    while let Some(statement) = ancestor {
8156        if statement.kind() == "if_statement"
8157            && let (Some(consequence), Some(alternative)) = (
8158                statement.child_by_field_name("consequence"),
8159                statement.child_by_field_name("alternative"),
8160            )
8161            && alternative.start_byte() <= descendant.start_byte()
8162            && descendant.end_byte() <= alternative.end_byte()
8163        {
8164            let mut cursor = consequence.walk();
8165            let openers = consequence
8166                .named_children(&mut cursor)
8167                .filter(|child| {
8168                    matches!(child.kind(), "preproc_if" | "preproc_ifdef")
8169                        && child
8170                            .child(child.child_count().saturating_sub(1))
8171                            .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
8172                })
8173                .collect::<Vec<_>>();
8174            if openers.len() != 1 {
8175                ancestor = statement.parent();
8176                continue;
8177            }
8178
8179            let mut terminators = Vec::new();
8180            let mut stack = vec![alternative];
8181            while let Some(node) = stack.pop() {
8182                if node.kind() == "preproc_call"
8183                    && node.start_byte() >= descendant.end_byte()
8184                    && node
8185                        .child_by_field_name("directive")
8186                        .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
8187                {
8188                    terminators.push(node);
8189                    continue;
8190                }
8191                for index in (0..node.named_child_count()).rev() {
8192                    if let Some(child) = node.named_child(index) {
8193                        stack.push(child);
8194                    }
8195                }
8196            }
8197            if terminators.len() == 1 {
8198                return simple_preprocessor_guard(openers[0], source);
8199            }
8200        }
8201        ancestor = statement.parent();
8202    }
8203    None
8204}
8205
8206fn preprocessor_guard_for_descendant(
8207    conditional: Node<'_>,
8208    descendant: Node<'_>,
8209    source: &str,
8210) -> Option<PreprocessorGuard> {
8211    let mut guard = simple_preprocessor_guard(conditional, source)?;
8212    if conditional
8213        .child_by_field_name("alternative")
8214        .is_some_and(|alternative| {
8215            alternative.start_byte() <= descendant.start_byte()
8216                && descendant.end_byte() <= alternative.end_byte()
8217        })
8218    {
8219        let alternative = conditional.child_by_field_name("alternative")?;
8220        // Tree-sitter nests an `#elif` chain in each `alternative` field. A
8221        // descendant in any later branch must first exclude the parent branch,
8222        // then collect the nested `preproc_elif` guard from its own ancestor.
8223        if !matches!(alternative.kind(), "preproc_else" | "preproc_elif") {
8224            return None;
8225        }
8226        guard = guard.negated();
8227    }
8228    Some(guard)
8229}
8230
8231fn preprocessor_conditional_contains_descendant(
8232    conditional: Node<'_>,
8233    descendant: Node<'_>,
8234) -> bool {
8235    cpp_displaced_preprocessor_boundary(conditional)
8236        .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
8237}
8238
8239pub fn merge_preprocessor_guards(
8240    left: &HashSet<PreprocessorGuard>,
8241    right: &HashSet<PreprocessorGuard>,
8242) -> Option<HashSet<PreprocessorGuard>> {
8243    let mut merged = left.clone();
8244    for guard in right {
8245        if merged.contains(&guard.negated()) {
8246            return None;
8247        }
8248        merged.insert(guard.clone());
8249    }
8250    Some(merged)
8251}
8252
8253fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
8254    if conditional.kind() == "preproc_ifdef" {
8255        let name = conditional.child_by_field_name("name")?;
8256        let name = node_text(name, source).to_string();
8257        return match conditional.child(0)?.kind() {
8258            "#ifdef" => Some(PreprocessorGuard::Defined(name)),
8259            "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
8260            _ => None,
8261        };
8262    }
8263    let condition = conditional.child_by_field_name("condition")?;
8264    simple_preprocessor_expression_guard(condition, source).or_else(|| {
8265        Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
8266            node_text(condition, source),
8267        )))
8268    })
8269}
8270
8271fn simple_preprocessor_expression_guard(
8272    expression: Node<'_>,
8273    source: &str,
8274) -> Option<PreprocessorGuard> {
8275    match expression.kind() {
8276        "number_literal" => match node_text(expression, source).trim() {
8277            "0" => Some(PreprocessorGuard::Constant(false)),
8278            "1" => Some(PreprocessorGuard::Constant(true)),
8279            _ => None,
8280        },
8281        "preproc_defined" => {
8282            let identifier = (0..expression.named_child_count())
8283                .filter_map(|index| expression.named_child(index))
8284                .find(|child| child.kind() == "identifier")?;
8285            Some(PreprocessorGuard::Defined(
8286                node_text(identifier, source).to_string(),
8287            ))
8288        }
8289        "unary_expression"
8290            if expression
8291                .child_by_field_name("operator")
8292                .is_some_and(|operator| operator.kind() == "!") =>
8293        {
8294            simple_preprocessor_expression_guard(
8295                expression.child_by_field_name("argument")?,
8296                source,
8297            )
8298            .map(|guard| guard.negated())
8299        }
8300        "parenthesized_expression" => (0..expression.named_child_count())
8301            .filter_map(|index| expression.named_child(index))
8302            .next()
8303            .and_then(|child| simple_preprocessor_expression_guard(child, source)),
8304        "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
8305            expression, source,
8306        ))),
8307        _ => None,
8308    }
8309}
8310
8311fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
8312    match expression.kind() {
8313        "number_literal" => match node_text(expression, source).trim() {
8314            "0" => BooleanGuardExpression::Constant(false),
8315            "1" => BooleanGuardExpression::Constant(true),
8316            _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8317                expression, source,
8318            ))),
8319        },
8320        "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
8321        "preproc_defined" => {
8322            let identifier = (0..expression.named_child_count())
8323                .filter_map(|index| expression.named_child(index))
8324                .find(|child| child.kind() == "identifier");
8325            identifier.map_or_else(
8326                || {
8327                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8328                        expression, source,
8329                    )))
8330                },
8331                |identifier| {
8332                    BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
8333                },
8334            )
8335        }
8336        "unary_expression"
8337            if expression
8338                .child_by_field_name("operator")
8339                .is_some_and(|operator| operator.kind() == "!") =>
8340        {
8341            expression.child_by_field_name("argument").map_or_else(
8342                || {
8343                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8344                        expression, source,
8345                    )))
8346                },
8347                |argument| boolean_preprocessor_expression(argument, source).negated(),
8348            )
8349        }
8350        "parenthesized_expression" => (0..expression.named_child_count())
8351            .filter_map(|index| expression.named_child(index))
8352            .next()
8353            .map_or_else(
8354                || {
8355                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8356                        expression, source,
8357                    )))
8358                },
8359                |child| boolean_preprocessor_expression(child, source),
8360            ),
8361        "binary_expression" => {
8362            let operands = || {
8363                Some((
8364                    boolean_preprocessor_expression(
8365                        expression.child_by_field_name("left")?,
8366                        source,
8367                    ),
8368                    boolean_preprocessor_expression(
8369                        expression.child_by_field_name("right")?,
8370                        source,
8371                    ),
8372                ))
8373            };
8374            match expression
8375                .child_by_field_name("operator")
8376                .map(|operator| operator.kind())
8377            {
8378                Some("&&") => operands().map_or_else(
8379                    || {
8380                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8381                            expression, source,
8382                        )))
8383                    },
8384                    |(left, right)| BooleanGuardExpression::all([left, right]),
8385                ),
8386                Some("||") => operands().map_or_else(
8387                    || {
8388                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8389                            expression, source,
8390                        )))
8391                    },
8392                    |(left, right)| BooleanGuardExpression::any([left, right]),
8393                ),
8394                _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8395                    expression, source,
8396                ))),
8397            }
8398        }
8399        _ => {
8400            BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
8401        }
8402    }
8403}
8404
8405fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
8406    if targets.len() == 1 {
8407        targets.pop()
8408    } else {
8409        None
8410    }
8411}
8412
8413/// The declaration nodes of `candidate` in `prepared` that stand at a scope a
8414/// later reference can name.
8415///
8416/// A declaration inside a real function body, lambda, or nested block is block
8417/// local and is dropped. A declaration inside a parser-recovery wrapper that
8418/// merely looks callable -- an export macro between `class` and its name, or a
8419/// namespace-opening macro token before `namespace x {` -- keeps class or
8420/// namespace scope and is kept.
8421fn nameable_callable_declaration_nodes<'tree>(
8422    analyzer: &CppGraphSource<'_>,
8423    prepared: &'tree PreparedSyntaxTree,
8424    candidate: &CodeUnit,
8425) -> Vec<Node<'tree>> {
8426    let root = prepared.tree().root_node();
8427    analyzer
8428        .ranges(candidate)
8429        .into_iter()
8430        .filter_map(|range| {
8431            let mut declaration =
8432                root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
8433            while !matches!(
8434                declaration.kind(),
8435                "declaration" | "field_declaration" | "function_definition"
8436            ) {
8437                declaration = declaration.parent()?;
8438            }
8439            let mut ancestor = declaration.parent();
8440            while let Some(node) = ancestor {
8441                if node.kind() == "function_definition"
8442                    && is_recovered_declaration_scope_container(node, prepared.source())
8443                {
8444                    ancestor = node.parent();
8445                    continue;
8446                }
8447                if node.kind() == "compound_statement"
8448                    && node.parent().is_some_and(|parent| {
8449                        is_recovered_declaration_scope_container(parent, prepared.source())
8450                    })
8451                {
8452                    ancestor = node.parent().and_then(|parent| parent.parent());
8453                    continue;
8454                }
8455                if matches!(
8456                    node.kind(),
8457                    "compound_statement" | "function_definition" | "lambda_expression"
8458                ) {
8459                    return None;
8460                }
8461                ancestor = node.parent();
8462            }
8463            Some(declaration)
8464        })
8465        .collect()
8466}
8467
8468fn callable_declaration_activation_in_file(
8469    analyzer: &CppGraphSource<'_>,
8470    prepared: &PreparedSyntaxTree,
8471    candidate: &CodeUnit,
8472    reference: &CallableReferenceContext<'_>,
8473) -> Option<usize> {
8474    nameable_callable_declaration_nodes(analyzer, prepared, candidate)
8475        .into_iter()
8476        .filter(|declaration| {
8477            callable_preprocessor_context_is_visible_for_reference(
8478                *declaration,
8479                prepared.source(),
8480                reference,
8481            )
8482        })
8483        .map(callable_declaration_activation_byte)
8484        .min()
8485}
8486
8487/// C and C++ activate a declared name at the end of its declarator, not at the
8488/// end of the whole declaration. A function definition ends at the closing
8489/// brace of its body, so the declaration end byte would hide the function from
8490/// its own body and make self recursion unresolvable without a prototype.
8491fn callable_declaration_activation_byte(declaration: Node<'_>) -> usize {
8492    if declaration.kind() != "function_definition" {
8493        return declaration.end_byte();
8494    }
8495    declaration
8496        .child_by_field_name("declarator")
8497        .map_or(declaration.end_byte(), |declarator| declarator.end_byte())
8498}
8499
8500/// The reference side of a callable visibility question.
8501///
8502/// An include-graph walk and a whole-file arity activation ask the question
8503/// without one reference position, so they carry no `position` and therefore no
8504/// guard environment.
8505struct CallableReferenceContext<'a> {
8506    file: &'a ProjectFile,
8507    position: Option<CallableReferencePosition<'a>>,
8508}
8509
8510/// One reference position plus its preprocessor guard environment. The
8511/// environment is computed on demand because most declarations carry no
8512/// non-trivial guard.
8513struct CallableReferencePosition<'a> {
8514    prepared: &'a PreparedSyntaxTree,
8515    byte: usize,
8516    guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
8517}
8518
8519impl CallableReferenceContext<'_> {
8520    fn is_c(&self) -> bool {
8521        self.file
8522            .rel_path()
8523            .extension()
8524            .and_then(|extension| extension.to_str())
8525            == Some("c")
8526    }
8527
8528    fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
8529        let position = self.position.as_ref()?;
8530        position
8531            .guards
8532            .get_or_init(|| {
8533                position
8534                    .prepared
8535                    .tree()
8536                    .root_node()
8537                    .descendant_for_byte_range(position.byte, position.byte)
8538                    .and_then(|node| {
8539                        preprocessor_guard_environment(node, position.prepared.source())
8540                    })
8541            })
8542            .as_ref()
8543    }
8544}
8545
8546fn callable_preprocessor_context_is_visible_for_reference(
8547    node: Node<'_>,
8548    source: &str,
8549    reference: &CallableReferenceContext<'_>,
8550) -> bool {
8551    let reference_is_c = reference.is_c();
8552    let mut ancestor = node.parent();
8553    while let Some(conditional) = ancestor {
8554        if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
8555            && !is_file_covering_include_guard(conditional, source)
8556            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
8557            && preprocessor_conditional_contains_descendant(conditional, node)
8558        {
8559            let Some(guard) = preprocessor_guard_for_descendant(conditional, node, source) else {
8560                return false;
8561            };
8562            match guard {
8563                PreprocessorGuard::Constant(true) => {}
8564                PreprocessorGuard::Constant(false) => return false,
8565                PreprocessorGuard::Defined(name) if name == "__cplusplus" => {
8566                    if reference_is_c {
8567                        return false;
8568                    }
8569                }
8570                PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
8571                    if !reference_is_c {
8572                        return false;
8573                    }
8574                }
8575                // The declaration stands under a guard whose value this
8576                // analyzer cannot decide. It is still co-active with a
8577                // reference whose active guards imply it. Collecting one guard
8578                // per ancestor makes the whole walk a conjunction of the
8579                // declaration requirements.
8580                guard => {
8581                    if !reference
8582                        .guards()
8583                        .is_some_and(|active| preprocessor_guard_holds_at_reference(&guard, active))
8584                    {
8585                        return false;
8586                    }
8587                }
8588            }
8589        }
8590        ancestor = conditional.parent();
8591    }
8592    true
8593}
8594
8595fn flattened_macro_namespace_declaration_matches(
8596    analyzer: &CppGraphSource<'_>,
8597    cpp: &dyn CppSource,
8598    reference_file: &ProjectFile,
8599    visible_declaration: &CodeUnit,
8600    qualified_candidate: &CodeUnit,
8601    reference_byte: usize,
8602) -> bool {
8603    // Namespace-opening macros can leave tree-sitter unable to retain the
8604    // namespace owner after a later recovery point. In that shape the forward
8605    // declaration is indexed at translation-unit scope, while the definition
8606    // still has its qualified owner. Require all surviving structural evidence
8607    // before treating the declaration as activation for that definition.
8608    if visible_declaration.kind() != qualified_candidate.kind()
8609        || visible_declaration.identifier() != qualified_candidate.identifier()
8610        || visible_declaration.signature() != qualified_candidate.signature()
8611        || !visible_declaration.package_name().is_empty()
8612        || qualified_candidate.package_name().is_empty()
8613    {
8614        return false;
8615    }
8616
8617    let Some(prepared) = cpp.prepared_syntax(visible_declaration.source()) else {
8618        return false;
8619    };
8620    let root = prepared.tree().root_node();
8621    let closing_brace_limit = if visible_declaration.source() == reference_file {
8622        reference_byte
8623    } else {
8624        usize::MAX
8625    };
8626
8627    analyzer
8628        .ranges(visible_declaration)
8629        .into_iter()
8630        .any(|range| {
8631            let Some(mut declaration) =
8632                root.descendant_for_byte_range(range.start_byte, range.end_byte)
8633            else {
8634                return false;
8635            };
8636            while !matches!(
8637                declaration.kind(),
8638                "declaration" | "field_declaration" | "function_definition"
8639            ) {
8640                let Some(parent) = declaration.parent() else {
8641                    return false;
8642                };
8643                declaration = parent;
8644            }
8645            if declaration
8646                .parent()
8647                .is_none_or(|parent| parent.kind() != "translation_unit")
8648                || !macro_displaced_cpp_return_type(declaration, prepared.source())
8649            {
8650                return false;
8651            }
8652
8653            let mut cursor = root.walk();
8654            root.named_children(&mut cursor).any(|sibling| {
8655                sibling.start_byte() >= declaration.end_byte()
8656                    && sibling.start_byte() < closing_brace_limit
8657                    && direct_unmatched_closing_brace(sibling)
8658            })
8659        })
8660}
8661
8662fn flattened_macro_namespace_components(
8663    declaration: Node<'_>,
8664    source: &str,
8665) -> Option<Vec<String>> {
8666    flattened_macro_function_namespace_components(declaration, source)
8667        .or_else(|| flattened_macro_error_namespace_components(declaration, source))
8668}
8669
8670fn flattened_macro_function_namespace_components(
8671    declaration: Node<'_>,
8672    source: &str,
8673) -> Option<Vec<String>> {
8674    let body = declaration
8675        .parent()
8676        .filter(|parent| parent.kind() == "compound_statement")?;
8677    let function = body.parent()?;
8678    if function.child_by_field_name("body") != Some(body) {
8679        return None;
8680    }
8681    let namespace_name = recovered_macro_namespace_name(function, source)?;
8682    let mut components = enclosing_namespace_components(declaration, source)?;
8683    components.push(namespace_name);
8684    Some(components)
8685}
8686
8687/// The namespace name a namespace-opening macro token displaced into a
8688/// synthetic `function_definition`, or `None` when `function` is not that
8689/// recovery shape.
8690///
8691/// `ABSL_NAMESPACE_BEGIN` (or `FMT_BEGIN_NAMESPACE`, ...) immediately before
8692/// `namespace x {` leaves tree-sitter with a `function_definition` whose type is
8693/// the macro token, whose declarator is the namespace name behind an `ERROR`
8694/// holding the `namespace` keyword, and whose body spans the whole namespace
8695/// region. The matching `*_NAMESPACE_END` sibling is what separates the recovery
8696/// artifact from a real function definition.
8697fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
8698    if function.kind() != "function_definition" || !function.has_error() {
8699        return None;
8700    }
8701    let body = function
8702        .child_by_field_name("body")
8703        .filter(|body| body.kind() == "compound_statement")?;
8704    let mut cursor = function.walk();
8705    let prefix = function
8706        .named_children(&mut cursor)
8707        .take_while(|child| child.start_byte() < body.start_byte())
8708        .filter(|child| child.kind() != "comment")
8709        .collect::<Vec<_>>();
8710    let begin_index = prefix.iter().rposition(|child| {
8711        flattened_macro_sentinel_name(*child, source)
8712            .is_some_and(|name| is_namespace_begin_sentinel(&name))
8713    })?;
8714    let mut identifiers = Vec::new();
8715    let mut stack = prefix[begin_index + 1..]
8716        .iter()
8717        .rev()
8718        .copied()
8719        .collect::<Vec<_>>();
8720    while let Some(current) = stack.pop() {
8721        if let Some(identifier) = direct_cpp_identifier_name(current, source) {
8722            identifiers.push(identifier);
8723            continue;
8724        }
8725        let mut cursor = current.walk();
8726        let children = current.named_children(&mut cursor).collect::<Vec<_>>();
8727        stack.extend(children.into_iter().rev());
8728    }
8729    let [keyword, namespace_name] = identifiers.as_slice() else {
8730        return None;
8731    };
8732    if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
8733    {
8734        return None;
8735    }
8736    let mut next = function.next_named_sibling();
8737    let next = loop {
8738        let candidate = next?;
8739        next = candidate.next_named_sibling();
8740        if candidate.kind() != "comment" {
8741            break candidate;
8742        }
8743    };
8744    flattened_macro_sentinel_name(next, source)
8745        .is_some_and(|name| is_namespace_end_sentinel(&name))
8746        .then(|| namespace_name.clone())
8747}
8748
8749/// A `function_definition` that exists only because tree-sitter recovered a
8750/// macro-decorated class head or a namespace-opening macro token. A declaration
8751/// in such a body keeps class or namespace scope, so a scope walk must step over
8752/// the wrapper instead of treating the declaration as block local.
8753fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
8754    crate::declarations::is_recovered_exported_class_container(node, source)
8755        || recovered_macro_namespace_name(node, source).is_some()
8756}
8757
8758fn flattened_macro_error_namespace_components(
8759    declaration: Node<'_>,
8760    source: &str,
8761) -> Option<Vec<String>> {
8762    let parent = declaration
8763        .parent()
8764        .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
8765    let mut cursor = parent.walk();
8766    let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
8767    let declaration_index = siblings
8768        .iter()
8769        .position(|candidate| same_node(*candidate, declaration))?;
8770    let begin_index = (0..declaration_index).rev().find(|index| {
8771        flattened_macro_sentinel_name(siblings[*index], source)
8772            .is_some_and(|name| is_namespace_begin_sentinel(&name))
8773    })?;
8774
8775    let significant = siblings[begin_index + 1..declaration_index]
8776        .iter()
8777        .copied()
8778        .filter(|node| node.kind() != "comment")
8779        .collect::<Vec<_>>();
8780    let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
8781        return None;
8782    };
8783    if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
8784        return None;
8785    }
8786    let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
8787    if significant[2..].iter().any(|node| {
8788        flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
8789            is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
8790        })
8791    }) {
8792        return None;
8793    }
8794
8795    let mut saw_namespace_close = false;
8796    for sibling in siblings.iter().skip(declaration_index + 1).copied() {
8797        if sibling.kind() == "comment" {
8798            continue;
8799        }
8800        if !saw_namespace_close {
8801            if direct_unmatched_closing_brace(sibling) {
8802                saw_namespace_close = true;
8803                continue;
8804            }
8805            if flattened_macro_sentinel_name(sibling, source).is_some() {
8806                return None;
8807            }
8808            continue;
8809        }
8810        if !flattened_macro_sentinel_name(sibling, source)
8811            .is_some_and(|name| is_namespace_end_sentinel(&name))
8812        {
8813            return None;
8814        }
8815        let mut components = enclosing_namespace_components(declaration, source)?;
8816        components.push(namespace_name);
8817        return Some(components);
8818    }
8819    None
8820}
8821
8822fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
8823    // At translation-unit scope the trailing `X_NAMESPACE_END` token parses as
8824    // an `expression_statement` with a missing semicolon; inside a namespace
8825    // body the same token stays a bare `type_identifier`.
8826    let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
8827        node.named_child(0)?
8828    } else {
8829        node
8830    };
8831    let candidate = direct_cpp_identifier_name(node, source).or_else(|| {
8832        node.child_by_field_name("type")
8833            .and_then(|type_node| direct_cpp_identifier_name(type_node, source))
8834    })?;
8835    (cpp_export_macro_token(&candidate)
8836        && (is_namespace_begin_sentinel(&candidate) || is_namespace_end_sentinel(&candidate)))
8837    .then_some(candidate)
8838}
8839
8840/// Namespace-opening macros are spelled both ways in the wild:
8841/// `ABSL_NAMESPACE_BEGIN` (abseil, nlohmann) and `FMT_BEGIN_NAMESPACE` (fmt).
8842fn is_namespace_begin_sentinel(name: &str) -> bool {
8843    name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE")
8844}
8845
8846fn is_namespace_end_sentinel(name: &str) -> bool {
8847    name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE")
8848}
8849
8850fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
8851    if node.kind() != "ERROR" || node.named_child_count() != 1 {
8852        return None;
8853    }
8854    let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
8855    (!cpp_export_macro_token(&name)).then_some(name)
8856}
8857
8858fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
8859    if !matches!(
8860        node.kind(),
8861        "identifier" | "namespace_identifier" | "type_identifier"
8862    ) {
8863        return None;
8864    }
8865    let name = normalize_cpp_whitespace(node_text(node, source));
8866    (!name.is_empty()).then_some(name)
8867}
8868
8869fn guard_requirement_sets_match(
8870    left: &[(usize, HashSet<PreprocessorGuard>)],
8871    right: &[(usize, HashSet<PreprocessorGuard>)],
8872) -> bool {
8873    left.len() == right.len()
8874        && left.iter().all(|(_, left_guards)| {
8875            right
8876                .iter()
8877                .any(|(_, right_guards)| left_guards == right_guards)
8878        })
8879        && right.iter().all(|(_, right_guards)| {
8880            left.iter()
8881                .any(|(_, left_guards)| right_guards == left_guards)
8882        })
8883}
8884
8885fn macro_displaced_cpp_return_type(declaration: Node<'_>, source: &str) -> bool {
8886    let Some(type_node) = declaration.child_by_field_name("type") else {
8887        return false;
8888    };
8889    let type_name = normalize_cpp_whitespace(node_text(type_node, source));
8890    !type_name.is_empty()
8891        && type_name
8892            .chars()
8893            .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
8894        && (0..declaration.named_child_count()).any(|index| {
8895            declaration
8896                .named_child(index)
8897                .is_some_and(|child| child.kind() == "ERROR")
8898        })
8899}
8900
8901fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
8902    node.kind() == "ERROR"
8903        && (0..node.child_count())
8904            .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
8905}
8906
8907pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
8908    let mut ancestor = node.parent();
8909    while let Some(parent) = ancestor {
8910        if is_preprocessor_conditional(parent)
8911            && !is_file_covering_include_guard(parent, source)
8912            && !is_split_cpp_language_linkage_wrapper(parent, node, source)
8913        {
8914            return false;
8915        }
8916        ancestor = parent.parent();
8917    }
8918    true
8919}
8920
8921fn is_split_cpp_language_linkage_wrapper(
8922    conditional: Node<'_>,
8923    descendant: Node<'_>,
8924    source: &str,
8925) -> bool {
8926    if conditional.child_by_field_name("alternative").is_some()
8927        || !matches!(
8928            simple_preprocessor_guard(conditional, source),
8929            Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
8930        )
8931    {
8932        return false;
8933    }
8934    let mut current = descendant.parent();
8935    let linkage = loop {
8936        let Some(node) = current else {
8937            return false;
8938        };
8939        if node == conditional {
8940            return false;
8941        }
8942        if node.kind() == "linkage_specification" {
8943            break node;
8944        }
8945        current = node.parent();
8946    };
8947    if linkage
8948        .child_by_field_name("value")
8949        .is_none_or(|value| node_text(value, source) != "\"C\"")
8950    {
8951        return false;
8952    }
8953    let Some(body) = linkage.child_by_field_name("body") else {
8954        return false;
8955    };
8956    let closes_opening_branch = (0..body.named_child_count())
8957        .filter_map(|index| body.named_child(index))
8958        .take_while(|child| child.end_byte() <= descendant.start_byte())
8959        .any(|child| {
8960            child.kind() == "preproc_call"
8961                && child
8962                    .child_by_field_name("directive")
8963                    .is_some_and(|directive| node_text(directive, source) == "#endif")
8964        });
8965    let reopens_for_closing_brace = (0..body.named_child_count())
8966        .filter_map(|index| body.named_child(index))
8967        .skip_while(|child| child.start_byte() < descendant.end_byte())
8968        .any(|child| {
8969            matches!(
8970                simple_preprocessor_guard(child, source),
8971                Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
8972            ) && (0..child.child_count()).any(|index| {
8973                child
8974                    .child(index)
8975                    .is_some_and(|token| token.kind() == "#endif" && token.is_missing())
8976            })
8977        });
8978    closes_opening_branch && reopens_for_closing_brace
8979}
8980
8981pub fn call_arity(node: Node<'_>) -> usize {
8982    node.child_by_field_name("arguments")
8983        .or_else(|| node.child_by_field_name("parameters"))
8984        .or_else(|| node.child_by_field_name("value"))
8985        .or_else(|| first_named_child_of_kind(node, "argument_list"))
8986        .or_else(|| first_named_child_of_kind(node, "initializer_list"))
8987        .map(|args| argument_children(args).count())
8988        .unwrap_or(0)
8989}
8990
8991pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
8992    let recovered_block_arguments = recovered_block_literal_arguments(node);
8993    (0..node.child_count())
8994        .filter_map(move |index| node.child(index))
8995        .filter(|child| child.is_named() && !child.is_extra())
8996        .flat_map(move |child| {
8997            if let Some((raw, left, right)) = recovered_block_arguments
8998                && child == raw
8999            {
9000                [Some(left), Some(right)]
9001            } else {
9002                [Some(child), None]
9003            }
9004        })
9005        .flatten()
9006}
9007
9008fn recovered_c_keyword_argument_count(
9009    file: &ProjectFile,
9010    call: Node<'_>,
9011    arguments: Node<'_>,
9012    source: &str,
9013) -> usize {
9014    // A C identifier that is a C++ keyword can be displaced twice by the C++
9015    // grammar: first into a direct parameter-list `ERROR(keyword)`, then into
9016    // a direct argument-list `ERROR(',', keyword)`. Match those CST tokens in
9017    // the enclosing C function before restoring the otherwise dropped slot.
9018    if !is_c_source_file(file) || arguments.kind() != "argument_list" {
9019        return 0;
9020    }
9021    let mut ancestor = Some(call);
9022    let function = loop {
9023        let Some(current) = ancestor else {
9024            return 0;
9025        };
9026        if current.kind() == "function_definition" {
9027            break current;
9028        }
9029        ancestor = current.parent();
9030    };
9031    let Some(parameters) = function
9032        .child_by_field_name("declarator")
9033        .and_then(|declarator| declarator.child_by_field_name("parameters"))
9034    else {
9035        return 0;
9036    };
9037    let displaced_parameter_keywords = (0..parameters.child_count())
9038        .filter_map(|index| parameters.child(index))
9039        .filter(|error| error.kind() == "ERROR")
9040        .filter_map(|error| {
9041            let parameter = error.prev_named_sibling()?;
9042            if parameter.kind() != "parameter_declaration"
9043                || parameter.end_byte() != error.start_byte()
9044                || extract_variable_name(parameter, source).is_some()
9045            {
9046                return None;
9047            }
9048            let mut children = (0..error.child_count())
9049                .filter_map(|index| error.child(index))
9050                .filter(|child| !child.is_extra() && !child.is_missing());
9051            let keyword = children.next()?;
9052            (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
9053                .then_some(keyword)
9054        })
9055        .collect::<Vec<_>>();
9056    if displaced_parameter_keywords.is_empty() {
9057        return 0;
9058    }
9059
9060    (0..arguments.child_count())
9061        .filter_map(|index| arguments.child(index))
9062        .filter(|error| error.kind() == "ERROR" && error.is_extra())
9063        .filter(|error| {
9064            let mut children = (0..error.child_count())
9065                .filter_map(|index| error.child(index))
9066                .filter(|child| !child.is_extra() && !child.is_missing());
9067            let Some(comma) = children.next() else {
9068                return false;
9069            };
9070            let Some(keyword) = children.next() else {
9071                return false;
9072            };
9073            children.next().is_none()
9074                && comma.kind() == ","
9075                && !keyword.is_named()
9076                && keyword.child_count() == 0
9077                && displaced_parameter_keywords
9078                    .iter()
9079                    .any(|parameter| parameter.kind_id() == keyword.kind_id())
9080        })
9081        .count()
9082}
9083
9084fn recovered_block_literal_arguments<'tree>(
9085    arguments: Node<'tree>,
9086) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
9087    if arguments.kind() != "argument_list" {
9088        return None;
9089    }
9090    let mut raw_arguments = (0..arguments.child_count())
9091        .filter_map(|index| arguments.child(index))
9092        .filter(|child| child.is_named() && !child.is_extra());
9093    let raw = raw_arguments.next()?;
9094    if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
9095        return None;
9096    }
9097
9098    let left = raw.child_by_field_name("left")?;
9099    if left.is_missing() || left.start_byte() == left.end_byte() {
9100        return None;
9101    }
9102    let right = raw.child_by_field_name("right")?;
9103    if right.kind() != "compound_literal_expression"
9104        || right.is_missing()
9105        || right
9106            .child_by_field_name("type")
9107            .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
9108        || right
9109            .child_by_field_name("value")
9110            .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
9111    {
9112        return None;
9113    }
9114    let has_intervening_error = (0..raw.child_count())
9115        .filter_map(|index| raw.child(index))
9116        .any(|child| {
9117            child.kind() == "ERROR"
9118                && !child.is_missing()
9119                && child.start_byte() >= left.end_byte()
9120                && child.end_byte() <= right.start_byte()
9121        });
9122    has_intervening_error.then_some((raw, left, right))
9123}
9124
9125pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
9126    match node.kind() {
9127        "new_expression" => node
9128            .child_by_field_name("type")
9129            .or_else(|| node.named_child(0)),
9130        "compound_literal_expression" => node.child_by_field_name("type"),
9131        "call_expression" => node.child_by_field_name("function"),
9132        _ => None,
9133    }
9134}
9135
9136pub fn field_initializer_constructs_target(
9137    node: Node<'_>,
9138    ctx: &ScanCtx<'_>,
9139    owner: &CodeUnit,
9140) -> bool {
9141    // A qualified name in a constructor initializer denotes a base
9142    // subobject constructor (`namespace::Base(args)`), not a member field.  The
9143    // field-initializer grammar exposes the qualified name as one structured
9144    // `qualified_identifier`; resolve its owner through the same lexical type
9145    // machinery used for ordinary C++ type references before considering the
9146    // initializer a hit.  This keeps an unrelated `namespace::Other(...)`, a
9147    // qualified non-constructor member, and an unresolved owner out of the
9148    // target constructor's inverse usage set.
9149    if first_named_child_of_kind(node, "qualified_identifier").is_some() {
9150        return qualified_base_initializer_constructs_target(node, ctx, owner);
9151    }
9152    let Some(name) = node
9153        .child_by_field_name("name")
9154        .or_else(|| first_named_child_of_kind(node, "field_identifier"))
9155        .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
9156    else {
9157        return false;
9158    };
9159    let field_name = node_text(name, ctx.source);
9160    ctx.visibility
9161        .visible_identifier_candidates(ctx.file, field_name)
9162        .filter(|unit| unit.is_field() && unit.identifier() == field_name)
9163        .any(|unit| field_declares_type(unit, ctx, owner))
9164}
9165
9166fn qualified_base_initializer_constructs_target(
9167    node: Node<'_>,
9168    ctx: &ScanCtx<'_>,
9169    owner: &CodeUnit,
9170) -> bool {
9171    let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
9172        return false;
9173    };
9174    let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
9175        return false;
9176    };
9177    let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
9178        return false;
9179    };
9180    let resolves_target = |components: &[String]| {
9181        matches!(
9182            ctx.visibility.resolve_type_components_lexically_for_target(
9183                &ctx.analyzer,
9184                ctx.file,
9185                components,
9186                is_globally_qualified_cpp_name(qualified),
9187                &lexical_scope,
9188                owner,
9189            ),
9190            LexicalTypeResolution::Resolved { unit, .. }
9191                if same_visible_symbol(&unit, owner)
9192        )
9193    };
9194    if resolves_target(&components) {
9195        return true;
9196    }
9197
9198    // Some real-world code spells a base mem-initializer as
9199    // `Base::Base(args)`. In that structured path the final component repeats
9200    // the constructor name; resolve the preceding type path. The terminal
9201    // identity check prevents an arbitrary qualified member from taking this
9202    // route.
9203    components
9204        .last()
9205        .is_some_and(|terminal| terminal == owner.identifier())
9206        && resolves_target(&components[..components.len() - 1])
9207}
9208
9209fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
9210    unit.signature()
9211        .is_some_and(|declaration| field_declaration_type_matches(declaration, unit, ctx, owner))
9212        || ctx
9213            .analyzer
9214            .get_source(unit, false)
9215            .is_some_and(|declaration| {
9216                field_declaration_type_matches(&declaration, unit, ctx, owner)
9217            })
9218}
9219
9220pub fn field_declared_binding(
9221    analyzer: &CppGraphSource<'_>,
9222    visibility: &VisibilityIndex<'_>,
9223    visible_from: &ProjectFile,
9224    field: &CodeUnit,
9225) -> Option<CppScanBinding> {
9226    let fact = visibility.field_declared_type_fact(analyzer, field)?;
9227    let normalized = normalize_field_type_text(&fact.type_text);
9228    let resolved = visibility.resolve_unique_canonical_type_for_declaration(
9229        analyzer,
9230        visible_from,
9231        field,
9232        &normalized,
9233    );
9234    let resolved = match (resolved, fact.template_arguments.as_deref()) {
9235        (Some(primary), Some(arguments)) => visibility
9236            .resolve_template_arguments(visible_from, primary, arguments)
9237            .ok(),
9238        (resolved, None) => resolved,
9239        (None, Some(_)) => None,
9240    };
9241    Some(CppScanBinding::from_type_name(
9242        normalized,
9243        resolved,
9244        fact.indirection,
9245    ))
9246}
9247
9248/// The one logical type the candidates name, or why they do not name one.
9249fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
9250    let Some(first) = candidates.first() else {
9251        return Err(TypeCandidateFailure::Unresolvable);
9252    };
9253    if candidates
9254        .iter()
9255        .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
9256    {
9257        Ok((*first).clone())
9258    } else {
9259        Err(TypeCandidateFailure::Ambiguous)
9260    }
9261}
9262
9263fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
9264    logical_type_candidate(candidates).ok()
9265}
9266
9267fn unique_type_candidate_preserving_alias(
9268    analyzer: &CppGraphSource<'_>,
9269    candidates: &[&CodeUnit],
9270) -> Option<CodeUnit> {
9271    let first = *candidates.first()?;
9272    if declared_type_alias(analyzer, first) {
9273        return candidates
9274            .iter()
9275            .all(|candidate| {
9276                declared_type_alias(analyzer, candidate)
9277                    && candidate.kind() == first.kind()
9278                    && candidate.fq_name() == first.fq_name()
9279                    && candidate.source() == first.source()
9280            })
9281            .then(|| first.clone());
9282    }
9283    candidates
9284        .iter()
9285        .all(|candidate| {
9286            !declared_type_alias(analyzer, candidate)
9287                && candidate.kind() == first.kind()
9288                && candidate.fq_name() == first.fq_name()
9289        })
9290        .then(|| first.clone())
9291}
9292
9293fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
9294    is_type_alias(unit)
9295        || analyzer
9296            .type_alias_provider()
9297            .is_some_and(|provider| provider.is_type_alias(unit))
9298}
9299
9300pub fn field_declared_type_binding(
9301    analyzer: &CppGraphSource<'_>,
9302    visibility: &VisibilityIndex<'_>,
9303    visible_from: &ProjectFile,
9304    field: &CodeUnit,
9305) -> Option<(String, Option<CodeUnit>, i32)> {
9306    let fact = visibility.field_declared_type_fact(analyzer, field)?;
9307    let normalized = normalize_field_type_text(&fact.type_text);
9308    let primary = visibility.resolve_unique_canonical_type_for_declaration(
9309        analyzer,
9310        visible_from,
9311        field,
9312        &normalized,
9313    );
9314    let resolved = match (primary, fact.template_arguments.as_deref()) {
9315        (Some(primary), Some(arguments)) => visibility
9316            .resolve_template_arguments(visible_from, primary, arguments)
9317            .ok(),
9318        (resolved, None) => resolved,
9319        (None, Some(_)) => None,
9320    };
9321    Some((normalized, resolved, fact.indirection))
9322}
9323
9324fn decode_field_declared_type_fact(
9325    analyzer: &CppGraphSource<'_>,
9326    field: &CodeUnit,
9327) -> Option<DeclaredFieldTypeFact> {
9328    let declaration = analyzer.get_source(field, false)?;
9329    let mut parser = Parser::new();
9330    parser
9331        .set_language(&tree_sitter_cpp::LANGUAGE.into())
9332        .ok()?;
9333    let tree = parser.parse(&declaration, None)?;
9334    let mut stack = vec![tree.root_node()];
9335    while let Some(node) = stack.pop() {
9336        if matches!(node.kind(), "declaration" | "field_declaration")
9337            && let Some(type_node) = node
9338                .child_by_field_name("type")
9339                .or_else(|| first_type_child(node))
9340            && let Some(indirection) =
9341                declared_name_indirection(node, type_node, field.identifier(), &declaration)
9342        {
9343            let declared_type = if matches!(
9344                type_node.kind(),
9345                "class_specifier" | "struct_specifier" | "union_specifier"
9346            ) {
9347                type_node.child_by_field_name("name")?
9348            } else {
9349                type_node
9350            };
9351            return Some(DeclaredFieldTypeFact {
9352                type_text: node_text(declared_type, &declaration).to_string(),
9353                indirection,
9354                template_arguments: cpp_template_reference_arguments(declared_type, &declaration),
9355            });
9356        }
9357        let mut cursor = node.walk();
9358        stack.extend(node.named_children(&mut cursor));
9359    }
9360    None
9361}
9362
9363/// Text of the type that a C or C++ alias declaration names, read from the
9364/// `type_definition` or `alias_declaration` node's `type` field.
9365///
9366/// The declaration text is never scanned. A function-pointer typedef
9367/// interleaves its aliased type with its declarator (`typedef R (*F)(int)`),
9368/// so no prefix or suffix of the spelling isolates the target.
9369///
9370/// An alias whose declarator is a function declarator names a function type:
9371/// `typedef R F(int)`, `typedef R (*F)(int)`, `typedef R *F(int)`, and
9372/// `using F = R (*)(int)`. The analyzer's type model names declared types only,
9373/// so such an alias has no canonical target. Its `type` field holds the return
9374/// type `R`, which is a different type from the alias, so this returns `None`
9375/// rather than that return type.
9376pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
9377    let mut parser = Parser::new();
9378    parser
9379        .set_language(&tree_sitter_cpp::LANGUAGE.into())
9380        .ok()?;
9381    let tree = parser.parse(declaration, None)?;
9382    let mut stack = vec![tree.root_node()];
9383    while let Some(node) = stack.pop() {
9384        let type_node = match node.kind() {
9385            "type_definition" => {
9386                let mut cursor = node.walk();
9387                if node
9388                    .children_by_field_name("declarator", &mut cursor)
9389                    .any(declarator_names_function_type)
9390                {
9391                    return None;
9392                }
9393                node.child_by_field_name("type")?
9394            }
9395            "alias_declaration" => {
9396                let type_node = node.child_by_field_name("type")?;
9397                if type_node
9398                    .child_by_field_name("declarator")
9399                    .is_some_and(declarator_names_function_type)
9400                {
9401                    return None;
9402                }
9403                type_node
9404            }
9405            _ => {
9406                let mut cursor = node.walk();
9407                let children = node.named_children(&mut cursor).collect::<Vec<_>>();
9408                stack.extend(children.into_iter().rev());
9409                continue;
9410            }
9411        };
9412        return Some(node_text(type_node, declaration).to_string());
9413    }
9414    None
9415}
9416
9417/// Whether an alias declaration's own declarator adds indirection that
9418/// [`cpp_alias_declaration_target_text`] does not report.
9419///
9420/// That function reads the declaration's `type` field, where `typedef Foo *Bar`
9421/// keeps only `Foo`: the `*` lives in the sibling declarator. Substituting such
9422/// an alias would equate `f(Bar)` with `f(Foo)`, so a comparison that cannot
9423/// prove the alias adds no indirection must refuse to follow it. A declaration
9424/// this cannot read at all is refused for the same reason.
9425fn cpp_alias_declaration_adds_indirection(declaration: &str) -> bool {
9426    let mut parser = Parser::new();
9427    if parser
9428        .set_language(&tree_sitter_cpp::LANGUAGE.into())
9429        .is_err()
9430    {
9431        return true;
9432    }
9433    let Some(tree) = parser.parse(declaration, None) else {
9434        return true;
9435    };
9436    let mut stack = vec![tree.root_node()];
9437    while let Some(node) = stack.pop() {
9438        let declarators = match node.kind() {
9439            "type_definition" => {
9440                let mut cursor = node.walk();
9441                node.children_by_field_name("declarator", &mut cursor)
9442                    .collect::<Vec<_>>()
9443            }
9444            "alias_declaration" => node
9445                .child_by_field_name("type")
9446                .and_then(|type_node| type_node.child_by_field_name("declarator"))
9447                .into_iter()
9448                .collect::<Vec<_>>(),
9449            _ => {
9450                let mut cursor = node.walk();
9451                let children = node.named_children(&mut cursor).collect::<Vec<_>>();
9452                stack.extend(children.into_iter().rev());
9453                continue;
9454            }
9455        };
9456        return declarators.into_iter().any(cpp_declarator_adds_indirection);
9457    }
9458    true
9459}
9460
9461/// True when an alias declarator names a function type.
9462///
9463/// The declarator chain is walked through the `declarator` field, so the
9464/// parameter list -- a sibling field -- is never entered and a parameter's own
9465/// function declarator cannot be mistaken for the alias's.
9466fn declarator_names_function_type(declarator: Node<'_>) -> bool {
9467    let mut current = Some(declarator);
9468    while let Some(node) = current {
9469        match node.kind() {
9470            "function_declarator" | "abstract_function_declarator" => return true,
9471            "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
9472                current = node.named_child(0);
9473            }
9474            _ => current = node.child_by_field_name("declarator"),
9475        }
9476    }
9477    false
9478}
9479
9480fn decode_structured_alias_target(
9481    analyzer: &CppGraphSource<'_>,
9482    unit: &CodeUnit,
9483) -> Option<StructuredAliasTarget> {
9484    analyzer
9485        .get_source(unit, false)
9486        .and_then(|declaration| decode_structured_alias_target_source(unit, &declaration, true))
9487        .or_else(|| {
9488            let signature = unit.signature()?;
9489            decode_structured_alias_target_source(unit, signature, false)
9490        })
9491}
9492
9493fn decode_structured_alias_target_source(
9494    unit: &CodeUnit,
9495    declaration: &str,
9496    require_top_level: bool,
9497) -> Option<StructuredAliasTarget> {
9498    let mut parser = Parser::new();
9499    parser
9500        .set_language(&tree_sitter_cpp::LANGUAGE.into())
9501        .ok()?;
9502    let tree = parser.parse(declaration, None)?;
9503    let mut stack = vec![tree.root_node()];
9504    while let Some(node) = stack.pop() {
9505        let type_node = match node.kind() {
9506            "type_definition" => {
9507                if require_top_level
9508                    && node
9509                        .parent()
9510                        .is_none_or(|parent| parent.kind() != "translation_unit")
9511                {
9512                    let mut cursor = node.walk();
9513                    stack.extend(node.named_children(&mut cursor));
9514                    continue;
9515                }
9516                let mut declarator_cursor = node.walk();
9517                let declarator = node
9518                    .children_by_field_name("declarator", &mut declarator_cursor)
9519                    .find(|declarator| {
9520                        extract_typedef_declarator_name(*declarator, declaration)
9521                            .is_some_and(|name| name == unit.identifier())
9522                    })?;
9523                if declarator_names_function_type(declarator) {
9524                    return None;
9525                }
9526                node.child_by_field_name("type")?
9527            }
9528            "alias_declaration" => {
9529                if require_top_level
9530                    && node
9531                        .parent()
9532                        .is_none_or(|parent| parent.kind() != "translation_unit")
9533                {
9534                    let mut cursor = node.walk();
9535                    stack.extend(node.named_children(&mut cursor));
9536                    continue;
9537                }
9538                let name = node.child_by_field_name("name")?;
9539                if node_text(name, declaration) != unit.identifier() {
9540                    return None;
9541                }
9542                let type_node = node.child_by_field_name("type")?;
9543                if type_node
9544                    .child_by_field_name("declarator")
9545                    .is_some_and(declarator_names_function_type)
9546                {
9547                    return None;
9548                }
9549                type_node
9550            }
9551            _ => {
9552                let mut cursor = node.walk();
9553                stack.extend(node.named_children(&mut cursor));
9554                continue;
9555            }
9556        };
9557        return structured_alias_type_target(type_node, declaration);
9558    }
9559    None
9560}
9561
9562fn structured_alias_type_target(
9563    mut type_node: Node<'_>,
9564    source: &str,
9565) -> Option<StructuredAliasTarget> {
9566    while type_node.kind() == "type_descriptor" {
9567        type_node = type_node.child_by_field_name("type")?;
9568    }
9569    if type_node.kind() == "primitive_type" {
9570        return Some(StructuredAliasTarget::Builtin);
9571    }
9572    if matches!(
9573        type_node.kind(),
9574        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
9575    ) {
9576        type_node = type_node.child_by_field_name("name")?;
9577    }
9578    let global = type_node.child_by_field_name("scope").is_none()
9579        && type_node.child(0).is_some_and(|child| child.kind() == "::");
9580    let mut components = Vec::new();
9581    append_structured_type_components(type_node, source, &mut components)?;
9582    let arguments = cpp_template_reference_arguments(type_node, source);
9583    (!components.is_empty()).then_some(StructuredAliasTarget::Named {
9584        components,
9585        global,
9586        arguments,
9587    })
9588}
9589
9590fn append_structured_type_components(
9591    node: Node<'_>,
9592    source: &str,
9593    out: &mut Vec<String>,
9594) -> Option<()> {
9595    match node.kind() {
9596        "identifier" | "namespace_identifier" | "type_identifier" => {
9597            out.push(node_text(node, source).to_string());
9598            Some(())
9599        }
9600        "template_type" => {
9601            append_structured_type_components(node.child_by_field_name("name")?, source, out)
9602        }
9603        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
9604            if let Some(scope) = node.child_by_field_name("scope") {
9605                append_structured_type_components(scope, source, out)?;
9606            }
9607            append_structured_type_components(node.child_by_field_name("name")?, source, out)
9608        }
9609        _ => None,
9610    }
9611}
9612
9613fn declared_name_indirection(
9614    declaration: Node<'_>,
9615    type_node: Node<'_>,
9616    field_name: &str,
9617    source: &str,
9618) -> Option<i32> {
9619    let mut stack = Vec::new();
9620    let mut cursor = declaration.walk();
9621    stack.extend(
9622        declaration
9623            .named_children(&mut cursor)
9624            .filter(|child| !same_node(*child, type_node)),
9625    );
9626    while let Some(node) = stack.pop() {
9627        if matches!(node.kind(), "identifier" | "field_identifier")
9628            && node_text(node, source) == field_name
9629        {
9630            let mut indirection = 0;
9631            let mut current = node.parent();
9632            while let Some(parent) = current {
9633                if same_node(parent, declaration) {
9634                    return Some(indirection);
9635                }
9636                if parent.kind() == "pointer_declarator" {
9637                    indirection += 1;
9638                }
9639                current = parent.parent();
9640            }
9641            return None;
9642        }
9643        let mut cursor = node.walk();
9644        stack.extend(node.named_children(&mut cursor));
9645    }
9646    None
9647}
9648
9649fn field_declaration_type_matches(
9650    declaration: &str,
9651    unit: &CodeUnit,
9652    ctx: &ScanCtx<'_>,
9653    owner: &CodeUnit,
9654) -> bool {
9655    ctx.visibility
9656        .resolves_to_type(&ctx.analyzer, ctx.file, declaration, owner)
9657        || field_type_prefix(declaration, unit.identifier()).is_some_and(|type_text| {
9658            let normalized = normalize_field_type_text(type_text);
9659            ctx.visibility
9660                .resolves_to_type(&ctx.analyzer, ctx.file, type_text, owner)
9661                || ctx.visibility.resolves_to_type(
9662                    &ctx.analyzer,
9663                    ctx.file,
9664                    normalized.as_str(),
9665                    owner,
9666                )
9667        })
9668}
9669
9670fn field_type_prefix<'a>(declaration: &'a str, field_name: &str) -> Option<&'a str> {
9671    let declaration = declaration
9672        .split(['=', ';'])
9673        .next()
9674        .unwrap_or(declaration)
9675        .trim();
9676    let index = declaration.rfind(field_name)?;
9677    let before = &declaration[..index];
9678    let after = &declaration[index + field_name.len()..];
9679    if before.chars().next_back().is_some_and(is_identifier_char)
9680        || after.chars().next().is_some_and(is_identifier_char)
9681    {
9682        return None;
9683    }
9684    Some(before.trim())
9685}
9686
9687fn normalize_field_type_text(type_text: &str) -> String {
9688    const FIELD_SPECIFIERS: [&str; 8] = [
9689        "extern ",
9690        "static ",
9691        "mutable ",
9692        "constexpr ",
9693        "constinit ",
9694        "inline ",
9695        "volatile ",
9696        "const ",
9697    ];
9698
9699    let mut normalized = normalize_type_text(type_text);
9700    loop {
9701        let Some(stripped) = FIELD_SPECIFIERS
9702            .iter()
9703            .find_map(|specifier| normalized.strip_prefix(specifier))
9704        else {
9705            return normalized;
9706        };
9707        normalized = normalize_type_text(stripped);
9708    }
9709}
9710
9711fn is_identifier_char(ch: char) -> bool {
9712    ch == '_' || ch.is_ascii_alphanumeric()
9713}
9714
9715pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
9716    let Some(type_node) = node.child_by_field_name("type") else {
9717        return false;
9718    };
9719    ctx.visibility.resolves_to_type(
9720        &ctx.analyzer,
9721        ctx.file,
9722        node_text(type_node, ctx.source),
9723        owner,
9724    )
9725}
9726
9727pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9728    !ctx.analyzer
9729        .declarations(ctx.file)
9730        .into_iter()
9731        .filter(|unit| unit.is_function())
9732        .any(|unit| {
9733            ctx.analyzer.ranges(&unit).iter().any(|range| {
9734                node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
9735            })
9736        })
9737}
9738
9739pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
9740    let mut cursor = node.walk();
9741    for child in node.named_children(&mut cursor) {
9742        if child.kind() == "init_declarator" {
9743            return child
9744                .child_by_field_name("value")
9745                .or_else(|| first_named_child_of_kind(child, "initializer_list"))
9746                .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
9747                .map(declaration_init_value_arity)
9748                .unwrap_or(0);
9749        }
9750        if is_declarator_node(child) {
9751            return declaration_declarator_arity(child);
9752        }
9753    }
9754    0
9755}
9756
9757fn declaration_init_value_arity(value: Node<'_>) -> usize {
9758    match value.kind() {
9759        "argument_list" | "initializer_list" => argument_children(value).count(),
9760        "compound_literal_expression" => call_arity(value),
9761        _ => 1,
9762    }
9763}
9764
9765fn declaration_declarator_arity(node: Node<'_>) -> usize {
9766    if let Some(parameters) = node.child_by_field_name("parameters") {
9767        return argument_children(parameters).count();
9768    }
9769    node.child_by_field_name("declarator")
9770        .map(declaration_declarator_arity)
9771        .unwrap_or(0)
9772}
9773
9774fn first_named_child_of_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
9775    let mut cursor = node.walk();
9776    node.named_children(&mut cursor)
9777        .find(|child| child.kind() == kind)
9778}
9779
9780fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
9781    let mut stack = vec![root];
9782    while let Some(node) = stack.pop() {
9783        if node.kind() == kind {
9784            return Some(node);
9785        }
9786        for index in (0..node.named_child_count()).rev() {
9787            if let Some(child) = node.named_child(index) {
9788                stack.push(child);
9789            }
9790        }
9791    }
9792    None
9793}
9794
9795fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
9796    if node.kind() == "identifier" {
9797        return true;
9798    }
9799    if node.kind() == "parenthesized_expression" {
9800        return false;
9801    }
9802    if node.kind() == "call_expression" {
9803        return node
9804            .child_by_field_name("function")
9805            .is_some_and(|function| function.kind() == "identifier");
9806    }
9807    let mut stack = vec![node];
9808    while let Some(descendant) = stack.pop() {
9809        if descendant != node && descendant.kind() == "parenthesized_expression" {
9810            continue;
9811        }
9812        if descendant.kind() == "identifier" {
9813            return true;
9814        }
9815        if descendant.kind() == "call_expression" {
9816            if descendant
9817                .child_by_field_name("function")
9818                .is_some_and(|function| function.kind() == "identifier")
9819            {
9820                return true;
9821            }
9822            continue;
9823        }
9824        for index in (0..descendant.named_child_count()).rev() {
9825            if let Some(child) = descendant.named_child(index) {
9826                stack.push(child);
9827            }
9828        }
9829    }
9830    false
9831}
9832
9833fn macro_expansion_shape_is_safe(
9834    node: Node<'_>,
9835    source: &str,
9836    parameters: &[String],
9837    environment: &MacroEnvironment,
9838) -> bool {
9839    if matches!(node.kind(), "identifier" | "parenthesized_expression") {
9840        return true;
9841    }
9842    if node.kind() == "call_expression" {
9843        let Some(function) = node.child_by_field_name("function") else {
9844            return true;
9845        };
9846        if function.kind() != "identifier" {
9847            return true;
9848        }
9849        let function_name = node_text(function, source);
9850        if parameters
9851            .iter()
9852            .any(|parameter| parameter == function_name)
9853        {
9854            return false;
9855        }
9856        if !environment.may_bind(function_name) {
9857            return true;
9858        }
9859        let Some(arguments) = node.child_by_field_name("arguments") else {
9860            return false;
9861        };
9862        return argument_children(arguments).all(|argument| {
9863            if argument.kind() == "identifier"
9864                && parameters
9865                    .iter()
9866                    .any(|parameter| parameter == node_text(argument, source))
9867            {
9868                return false;
9869            }
9870            macro_expansion_shape_is_safe(argument, source, parameters, environment)
9871        });
9872    }
9873    let mut stack = vec![node];
9874    while let Some(descendant) = stack.pop() {
9875        if descendant != node {
9876            if descendant.kind() == "parenthesized_expression" {
9877                continue;
9878            }
9879            if descendant.kind() == "call_expression" {
9880                let expands = descendant
9881                    .child_by_field_name("function")
9882                    .filter(|function| function.kind() == "identifier")
9883                    .is_some_and(|function| environment.may_bind(node_text(function, source)));
9884                if expands {
9885                    return false;
9886                }
9887                continue;
9888            }
9889        }
9890        if descendant.kind() == "identifier" {
9891            let identifier = node_text(descendant, source);
9892            if parameters.iter().any(|parameter| parameter == identifier)
9893                || environment.may_bind(identifier)
9894            {
9895                return false;
9896            }
9897        }
9898        for index in (0..descendant.named_child_count()).rev() {
9899            if let Some(child) = descendant.named_child(index) {
9900                stack.push(child);
9901            }
9902        }
9903    }
9904    true
9905}
9906
9907fn structured_include_path<'a>(path: Node<'_>, source: &'a str) -> Option<&'a str> {
9908    let text = node_text(path, source);
9909    match path.kind() {
9910        "string_literal" => text.strip_prefix('"')?.strip_suffix('"'),
9911        "system_lib_string" => text.strip_prefix('<')?.strip_suffix('>'),
9912        _ => None,
9913    }
9914}
9915
9916fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
9917    let descendant = node;
9918    while let Some(parent) = node.parent() {
9919        if is_preprocessor_conditional(parent)
9920            && !is_file_covering_include_guard(parent, source)
9921            && preprocessor_conditional_contains_descendant(parent, descendant)
9922        {
9923            return true;
9924        }
9925        node = parent;
9926    }
9927    false
9928}
9929
9930fn is_preprocessor_conditional(node: Node<'_>) -> bool {
9931    matches!(
9932        node.kind(),
9933        "preproc_if"
9934            | "preproc_ifdef"
9935            | "preproc_ifndef"
9936            | "preproc_elif"
9937            | "preproc_elifdef"
9938            | "preproc_else"
9939    )
9940}
9941
9942fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
9943    node.parent()
9944        .filter(|parent| parent.kind() == "translation_unit")
9945        .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
9946        && is_canonical_include_guard(node, source)
9947}
9948
9949fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
9950    if node.kind() != "preproc_ifdef"
9951        || node
9952            .child(0)
9953            .is_none_or(|directive| directive.kind() != "#ifndef")
9954        || node.child_by_field_name("alternative").is_some()
9955    {
9956        return false;
9957    }
9958    let Some(guard_name) = node.child_by_field_name("name") else {
9959        return false;
9960    };
9961    let mut cursor = node.walk();
9962    node.named_children(&mut cursor)
9963        .find(|child| *child != guard_name && child.kind() != "comment")
9964        .filter(|child| child.kind() == "preproc_def")
9965        .and_then(|definition| definition.child_by_field_name("name"))
9966        .is_some_and(|defined_name| {
9967            node_text(defined_name, source) == node_text(guard_name, source)
9968        })
9969}
9970
9971fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
9972    let mut guard = None;
9973    for index in 0..root.named_child_count() {
9974        let Some(child) = root.named_child(index) else {
9975            continue;
9976        };
9977        if child.kind() == "comment" || is_pragma_once(child, source) {
9978            continue;
9979        }
9980        if guard.is_none() && is_canonical_include_guard(child, source) {
9981            guard = Some(child);
9982        } else {
9983            return None;
9984        }
9985    }
9986    guard
9987        .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
9988        .map(|name| node_text(name, source).to_string())
9989}
9990
9991fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
9992    if (0..root.named_child_count())
9993        .filter_map(|index| root.named_child(index))
9994        .any(|child| is_pragma_once(child, source))
9995    {
9996        return MacroIncludeProtection::PragmaOnce;
9997    }
9998    top_level_canonical_include_guard_name(root, source)
9999        .map(MacroIncludeProtection::MacroGuard)
10000        .unwrap_or(MacroIncludeProtection::None)
10001}
10002
10003fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
10004    node.kind() == "preproc_call"
10005        && node
10006            .child_by_field_name("directive")
10007            .is_some_and(|directive| node_text(directive, source) == "#pragma")
10008        && node
10009            .child_by_field_name("argument")
10010            .is_some_and(|argument| node_text(argument, source).trim() == "once")
10011}
10012
10013fn parse_preproc_identifier(argument: &str) -> Option<String> {
10014    let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
10015    let mut parser = Parser::new();
10016    parser
10017        .set_language(&tree_sitter_cpp::LANGUAGE.into())
10018        .ok()?;
10019    let tree = parser.parse(&sentinel, None)?;
10020    if tree.root_node().has_error() {
10021        return None;
10022    }
10023    let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
10024    let identifier = statement.named_child(0)?;
10025    (identifier.kind() == "identifier" && statement.named_child_count() == 1)
10026        .then(|| node_text(identifier, &sentinel).to_string())
10027}
10028
10029pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
10030    match node.kind() {
10031        "identifier" | "field_identifier" => {
10032            let name = node_text(node, source).trim();
10033            (!name.is_empty()).then(|| name.to_string())
10034        }
10035        "abstract_array_declarator"
10036        | "abstract_function_declarator"
10037        | "abstract_parenthesized_declarator"
10038        | "abstract_pointer_declarator"
10039        | "abstract_reference_declarator" => None,
10040        "function_declarator" => node
10041            .child_by_field_name("declarator")
10042            .or_else(|| node.child_by_field_name("name"))
10043            .and_then(|child| extract_variable_name(child, source)),
10044        _ => node
10045            .child_by_field_name("declarator")
10046            .or_else(|| node.child_by_field_name("name"))
10047            .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
10048            .and_then(|child| extract_variable_name(child, source)),
10049    }
10050}
10051
10052/// Whether `file` is proven to use plain-C source semantics.
10053///
10054/// `Language::Cpp` intentionally serves both C and C++. Headers do not carry a
10055/// compilation dialect on their own, so only an exact `.c` source extension is
10056/// sufficient to reinterpret C++-grammar keyword nodes such as `this` as C
10057/// identifiers.
10058///
10059/// The exact-lowercase-`.c` rule itself lives in [`LanguageDialect::for_path`],
10060/// which extraction reads too (a `.c` file is extracted with C tag scope), so
10061/// the doctrine has exactly one definition.
10062pub fn is_c_source_file(file: &ProjectFile) -> bool {
10063    LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
10064}
10065
10066/// Whether a reference written in `file` reads C++ source with C semantics.
10067///
10068/// [`is_c_source_file`] answers the half a path settles on its own. The other
10069/// half is a header, which has no dialect of its own: it is read as C exactly
10070/// when every workspace translation unit that provably compiles it compiles it
10071/// as C ([`CppSource::header_uses_c_semantics`], issue #1970).
10072///
10073/// This is the gate for anything that is really about the compilation
10074/// language of the code being read -- which reading of an included header's
10075/// declarations is in scope, whether `this` is an ordinary identifier. It is
10076/// NOT the gate for a question that is genuinely about a `.c` file on disk;
10077/// those keep calling [`is_c_source_file`].
10078pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
10079    is_c_source_file(file) || cpp.header_uses_c_semantics(file)
10080}
10081
10082pub fn is_declarator_node(node: Node<'_>) -> bool {
10083    matches!(
10084        node.kind(),
10085        "identifier"
10086            | "field_identifier"
10087            | "pointer_declarator"
10088            | "reference_declarator"
10089            | "array_declarator"
10090            | "parenthesized_declarator"
10091            | "function_declarator"
10092    )
10093}
10094
10095#[derive(Clone, Default)]
10096pub struct OrphanedNamespaceTypeScopeIndex {
10097    scopes: Vec<OrphanedNamespaceTypeScope>,
10098}
10099
10100#[derive(Clone)]
10101struct OrphanedNamespaceTypeScope {
10102    body_end: usize,
10103    scope_end: usize,
10104    components: Vec<String>,
10105}
10106
10107impl OrphanedNamespaceTypeScopeIndex {
10108    /// Index the physical namespace interval that remains after tree-sitter
10109    /// prematurely closes an error-marked namespace at a recovered class body.
10110    /// The later unmatched `}` is the structured upper bound: declarations
10111    /// between the truncated body and that token remain in the namespace, while
10112    /// declarations after it do not.
10113    pub fn build(root: Node<'_>, source: &str) -> Self {
10114        let mut scopes = Vec::new();
10115        let mut stack = vec![root];
10116        while let Some(current) = stack.pop() {
10117            if current.kind() == "namespace_definition"
10118                && current.has_error()
10119                && let Some(body) = current.child_by_field_name("body")
10120                && current.end_byte() == body.end_byte()
10121                && let Some(name) = current.child_by_field_name("name")
10122            {
10123                let mut components =
10124                    enclosing_namespace_components(current, source).unwrap_or_default();
10125                if append_cpp_name_components(name, source, &mut components).is_some()
10126                    && !components.is_empty()
10127                {
10128                    let mut following = current.next_named_sibling();
10129                    while let Some(candidate) = following {
10130                        if direct_unmatched_closing_brace(candidate) {
10131                            scopes.push(OrphanedNamespaceTypeScope {
10132                                body_end: body.end_byte(),
10133                                scope_end: candidate.start_byte(),
10134                                components,
10135                            });
10136                            break;
10137                        }
10138                        following = candidate.next_named_sibling();
10139                    }
10140                }
10141            }
10142            if !current.has_error() {
10143                continue;
10144            }
10145            let mut cursor = current.walk();
10146            stack.extend(
10147                current
10148                    .named_children(&mut cursor)
10149                    .filter(|child| child.has_error()),
10150            );
10151        }
10152        Self { scopes }
10153    }
10154
10155    pub fn scope_at(&self, byte: usize) -> Option<(usize, &[String])> {
10156        self.scopes
10157            .iter()
10158            .filter(|scope| scope.body_end < byte && byte < scope.scope_end)
10159            .max_by_key(|scope| (scope.components.len(), scope.body_end))
10160            .map(|scope| (scope.body_end, scope.components.as_slice()))
10161    }
10162}
10163
10164#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10165pub enum RecoveredDeclaratorTypeContext {
10166    Declaration,
10167    FunctionDefinition,
10168    Parameter,
10169}
10170
10171/// Recognize a real type displaced into a qualified declarator by parser
10172/// recovery.
10173///
10174/// Tree-sitter parses `API Result *make(Arg);` as if `API` were the declared
10175/// type and `Result` were the scope of a qualified declarator with a missing
10176/// `::`. A template return such as `API Result<T> make()` uses a
10177/// `template_type` for the same recovered scope. The same recovery occurs for
10178/// macro-prefixed definitions, extern variables, and macro-decorated
10179/// parameters (`f(MACRO T* p)`, where the parameter's own `type` field takes
10180/// the macro). Keep this intentionally structural: the recovered scope must
10181/// have the grammar's missing separator, the qualified node must occupy the
10182/// declaration's declarator chain, a separate nonempty type must occupy the
10183/// normal type field, and the recovered name must unwrap to a real declarator
10184/// name.
10185pub fn recovered_macro_decorated_declarator_type(
10186    node: Node<'_>,
10187) -> Option<RecoveredDeclaratorTypeContext> {
10188    recovered_macro_decorated_type_node(node).map(|(_, context)| context)
10189}
10190
10191/// Return the declaration/function `type` displaced by a macro-shaped
10192/// qualified declarator, together with the enclosing declaration context.
10193/// Callers use the macro scope only as structural admission evidence; the
10194/// returned node is the real type reference to resolve and record.
10195pub fn recovered_macro_decorated_type_node(
10196    node: Node<'_>,
10197) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
10198    if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
10199        return None;
10200    }
10201    let qualified = node.parent()?;
10202    if qualified.kind() != "qualified_identifier"
10203        || qualified.child_by_field_name("scope") != Some(node)
10204        || !(0..qualified.child_count())
10205            .filter_map(|index| qualified.child(index))
10206            .any(|child| child.kind() == "::" && child.is_missing())
10207    {
10208        return None;
10209    }
10210    if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
10211        return None;
10212    }
10213
10214    let (declaration, context) = recovered_declarator_container(qualified)?;
10215    let type_node = declaration
10216        .child_by_field_name("type")
10217        .filter(|type_node| {
10218            *type_node != qualified
10219                && !type_node.is_missing()
10220                && type_node.start_byte() != type_node.end_byte()
10221        })?;
10222    Some((type_node, context))
10223}
10224
10225fn recovered_declarator_container(
10226    mut declarator: Node<'_>,
10227) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
10228    loop {
10229        let parent = declarator.parent()?;
10230        if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
10231            return Some((
10232                parent
10233                    .parent()
10234                    .filter(|declaration| declaration.kind() == "declaration")?,
10235                RecoveredDeclaratorTypeContext::Declaration,
10236            ));
10237        }
10238        if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
10239            return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
10240        }
10241        if parent.kind() == "function_definition"
10242            && has_field_child(parent, "declarator", declarator)
10243        {
10244            return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
10245        }
10246        // `f(MACRO T* p)` recovers exactly like `MACRO T *make(...)` does, one
10247        // level down: the parameter's `type` field takes the macro token and
10248        // the real type `T` becomes the recovered scope of the declarator.
10249        // Declining here left every xxhash `XXH_NOESCAPE` parameter with no
10250        // candidate at all (#1830).
10251        if matches!(
10252            parent.kind(),
10253            "parameter_declaration" | "optional_parameter_declaration"
10254        ) && has_field_child(parent, "declarator", declarator)
10255        {
10256            return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
10257        }
10258        if !matches!(
10259            parent.kind(),
10260            "array_declarator"
10261                | "function_declarator"
10262                | "parenthesized_declarator"
10263                | "pointer_declarator"
10264                | "pointer_type_declarator"
10265                | "reference_declarator"
10266        ) || !has_field_child(parent, "declarator", declarator)
10267        {
10268            return None;
10269        }
10270        declarator = parent;
10271    }
10272}
10273
10274fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
10275    let mut cursor = parent.walk();
10276    parent
10277        .children_by_field_name(field, &mut cursor)
10278        .any(|child| child == target)
10279}
10280
10281fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
10282    loop {
10283        if node.is_missing() || node.start_byte() == node.end_byte() {
10284            return false;
10285        }
10286        match node.kind() {
10287            "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
10288                return true;
10289            }
10290            "array_declarator"
10291            | "function_declarator"
10292            | "parenthesized_declarator"
10293            | "pointer_declarator"
10294            | "pointer_type_declarator"
10295            | "reference_declarator" => {
10296                let Some(declarator) = node.child_by_field_name("declarator") else {
10297                    return false;
10298                };
10299                node = declarator;
10300            }
10301            _ => return false,
10302        }
10303    }
10304}
10305
10306/// Aggregate-owner proof for a structurally recognized designated initializer.
10307pub enum DesignatedInitializerOwner {
10308    Resolved(CodeUnit),
10309    Unresolved,
10310}
10311
10312/// Recognize a designated-initializer field and, when possible, resolve its
10313/// aggregate owner.
10314///
10315/// Covers both the grammar's ordinary `field_designator` shape and the exact
10316/// recovery used for `.field = value` after a preprocessor-split array
10317/// initializer. Nested aggregate levels are deliberately left unresolved unless
10318/// the single outer level is the containing array initializer: resolving those
10319/// would require following the enclosing field's declared type. `None` means the
10320/// node is not a designator at all; an unresolved designator remains classified so
10321/// callers cannot fall through to unrelated global/member heuristics.
10322pub fn designated_initializer_owner(
10323    visibility: &VisibilityIndex<'_>,
10324    file: &ProjectFile,
10325    source: &str,
10326    node: Node<'_>,
10327) -> Option<DesignatedInitializerOwner> {
10328    if let Some(designator) = node
10329        .parent()
10330        .filter(|parent| parent.kind() == "field_designator")
10331    {
10332        let pair = designator.parent()?;
10333        if pair.kind() != "initializer_pair"
10334            || pair.child_by_field_name("designator") != Some(designator)
10335        {
10336            return None;
10337        }
10338        let initializer = pair.parent()?;
10339        if initializer.kind() != "initializer_list" {
10340            return None;
10341        }
10342        return Some(classified_designated_owner(initializer_list_owner(
10343            visibility,
10344            file,
10345            source,
10346            initializer,
10347        )));
10348    }
10349
10350    let init_declarator = node.parent()?;
10351    if init_declarator.child_by_field_name("declarator") != Some(node)
10352        || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
10353    {
10354        return None;
10355    }
10356    Some(classified_designated_owner(declaration_owner(
10357        visibility,
10358        file,
10359        source,
10360        init_declarator.parent()?,
10361    )))
10362}
10363
10364fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
10365    owner.map_or(
10366        DesignatedInitializerOwner::Unresolved,
10367        DesignatedInitializerOwner::Resolved,
10368    )
10369}
10370
10371fn initializer_list_owner(
10372    visibility: &VisibilityIndex<'_>,
10373    file: &ProjectFile,
10374    source: &str,
10375    initializer: Node<'_>,
10376) -> Option<CodeUnit> {
10377    let mut current = initializer;
10378    let mut outer_initializer_lists = 0usize;
10379    loop {
10380        let parent = current.parent()?;
10381        match parent.kind() {
10382            "initializer_pair" => return None,
10383            "initializer_list" => {
10384                outer_initializer_lists += 1;
10385                if outer_initializer_lists > 1 {
10386                    return None;
10387                }
10388                current = parent;
10389            }
10390            "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
10391                let declaration = parent.parent()?;
10392                if outer_initializer_lists == 1
10393                    && !parent
10394                        .child_by_field_name("declarator")
10395                        .is_some_and(contains_array_declarator)
10396                {
10397                    return None;
10398                }
10399                return declaration_owner(visibility, file, source, declaration);
10400            }
10401            "compound_literal_expression"
10402                if parent.child_by_field_name("value") == Some(current)
10403                    && outer_initializer_lists == 0 =>
10404            {
10405                let type_node = parent.child_by_field_name("type")?;
10406                return resolve_designated_owner_type(visibility, file, source, type_node);
10407            }
10408            "ERROR" => current = parent,
10409            _ => return None,
10410        }
10411    }
10412}
10413
10414fn declaration_owner(
10415    visibility: &VisibilityIndex<'_>,
10416    file: &ProjectFile,
10417    source: &str,
10418    declaration: Node<'_>,
10419) -> Option<CodeUnit> {
10420    if !matches!(declaration.kind(), "declaration" | "field_declaration") {
10421        return None;
10422    }
10423    let type_node = declaration
10424        .child_by_field_name("type")
10425        .or_else(|| first_type_child(declaration))?;
10426    resolve_designated_owner_type(visibility, file, source, type_node)
10427}
10428
10429fn resolve_designated_owner_type(
10430    visibility: &VisibilityIndex<'_>,
10431    file: &ProjectFile,
10432    source: &str,
10433    type_node: Node<'_>,
10434) -> Option<CodeUnit> {
10435    let type_name = normalize_type_text(node_text(type_node, source));
10436    visibility
10437        .resolve_type(file, &type_name)
10438        .filter(CodeUnit::is_class)
10439}
10440
10441fn contains_array_declarator(declarator: Node<'_>) -> bool {
10442    let mut stack = vec![declarator];
10443    while let Some(node) = stack.pop() {
10444        if node.kind() == "array_declarator" {
10445            return true;
10446        }
10447        if matches!(node.kind(), "initializer_list" | "compound_statement") {
10448            continue;
10449        }
10450        let mut cursor = node.walk();
10451        stack.extend(node.named_children(&mut cursor));
10452    }
10453    false
10454}
10455
10456pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
10457    let mut cursor = node.walk();
10458    node.named_children(&mut cursor).find(|child| {
10459        matches!(
10460            child.kind(),
10461            "type_identifier"
10462                | "primitive_type"
10463                | "qualified_identifier"
10464                | "scoped_type_identifier"
10465                | "struct_specifier"
10466                | "union_specifier"
10467                | "enum_specifier"
10468        )
10469    })
10470}
10471
10472pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
10473    visibility: &VisibilityIndex<'_>,
10474    file: &ProjectFile,
10475    source: &str,
10476    declarator: Node<'_>,
10477    type_text: Option<&str>,
10478    bindings: &LocalInferenceEngine<T>,
10479) -> bool {
10480    if !has_ancestor_kind(declarator, "compound_statement") {
10481        return false;
10482    }
10483    if declarator
10484        .child_by_field_name("declarator")
10485        .is_none_or(|declarator| declarator.kind() != "identifier")
10486    {
10487        return false;
10488    }
10489    if !type_text
10490        .and_then(|text| visibility.resolve_type(file, text))
10491        .is_some_and(|unit| unit.is_class())
10492    {
10493        return false;
10494    }
10495    declarator
10496        .child_by_field_name("parameters")
10497        .is_some_and(|parameters| {
10498            constructor_parameters_look_like_expressions(parameters, source, bindings)
10499        })
10500}
10501
10502fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
10503    parameters: Node<'_>,
10504    source: &str,
10505    bindings: &LocalInferenceEngine<T>,
10506) -> bool {
10507    let mut cursor = parameters.walk();
10508    parameters.named_children(&mut cursor).any(|parameter| {
10509        !matches!(
10510            parameter.kind(),
10511            "parameter_declaration" | "optional_parameter_declaration"
10512        ) || parameter_declaration_is_local_expression(parameter, source, bindings)
10513    })
10514}
10515
10516fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
10517    parameter: Node<'_>,
10518    source: &str,
10519    bindings: &LocalInferenceEngine<T>,
10520) -> bool {
10521    let text = node_text(parameter, source).trim();
10522    if text
10523        .chars()
10524        .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
10525        && bindings.is_shadowed(text)
10526    {
10527        return true;
10528    }
10529
10530    let Some(base) = parameter
10531        .child_by_field_name("type")
10532        .filter(|base| base.kind() == "type_identifier")
10533    else {
10534        return false;
10535    };
10536    let Some(subscript) = parameter
10537        .child_by_field_name("declarator")
10538        .filter(|declarator| declarator.kind() == "abstract_array_declarator")
10539    else {
10540        return false;
10541    };
10542    subscript.child_by_field_name("size").is_some()
10543        && bindings.is_shadowed(node_text(base, source).trim())
10544}
10545
10546pub fn is_declaration_name(node: Node<'_>) -> bool {
10547    let Some(parent) = node.parent() else {
10548        return false;
10549    };
10550    if parent
10551        .child_by_field_name("name")
10552        .is_some_and(|name| same_node(name, node))
10553    {
10554        if matches!(
10555            parent.kind(),
10556            "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
10557        ) {
10558            return cpp_tag_specifier_declares_name(parent);
10559        }
10560        if matches!(
10561            parent.kind(),
10562            "namespace_definition"
10563                | "namespace_alias_definition"
10564                | "alias_declaration"
10565                | "enumerator"
10566        ) {
10567            return true;
10568        }
10569    }
10570
10571    let mut current = Some(parent);
10572    while let Some(ancestor) = current {
10573        let type_definition = ancestor.kind() == "type_definition";
10574        let mut declarator_cursor = ancestor.walk();
10575        if ancestor
10576            .children_by_field_name("declarator", &mut declarator_cursor)
10577            .any(|declarator| declarator_name_path_contains(declarator, node, type_definition))
10578        {
10579            return true;
10580        }
10581        if matches!(
10582            ancestor.kind(),
10583            "declaration"
10584                | "field_declaration"
10585                | "parameter_declaration"
10586                | "optional_parameter_declaration"
10587                | "function_definition"
10588                | "type_definition"
10589                | "alias_declaration"
10590                | "class_specifier"
10591                | "struct_specifier"
10592                | "union_specifier"
10593                | "enum_specifier"
10594        ) {
10595            return false;
10596        }
10597        current = ancestor.parent();
10598    }
10599    false
10600}
10601
10602/// Whether tree-sitter recovered a qualified friend-class type as an ordinary
10603/// declaration's declarator inside a malformed class body.
10604///
10605/// An export macro between `class` and the class name can make the containing
10606/// body parse as a function body. A source declaration such as
10607/// `friend class internal::Friend;` then retains this exact structure:
10608/// `declaration(type: friend, ERROR(class), declarator: internal::Friend)`.
10609/// The declarator is a type reference despite its field role.
10610pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
10611    if !matches!(
10612        node.kind(),
10613        "qualified_identifier" | "scoped_type_identifier"
10614    ) {
10615        return false;
10616    }
10617    let Some(declaration) = node
10618        .parent()
10619        .filter(|parent| parent.kind() == "declaration")
10620    else {
10621        return false;
10622    };
10623    if declaration.child_by_field_name("declarator") != Some(node)
10624        || !declaration
10625            .child_by_field_name("type")
10626            .is_some_and(|friend| {
10627                friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
10628            })
10629    {
10630        return false;
10631    }
10632    let mut cursor = declaration.walk();
10633    let mut errors = declaration
10634        .named_children(&mut cursor)
10635        .filter(|child| child.kind() == "ERROR");
10636    let Some(error) = errors.next() else {
10637        return false;
10638    };
10639    errors.next().is_none()
10640        && error.named_child_count() == 1
10641        && error.named_child(0).is_some_and(|class| {
10642            class.kind() == "identifier" && node_text(class, source) == "class"
10643        })
10644}
10645
10646pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
10647    if !matches!(node.kind(), "identifier" | "field_identifier") || is_declaration_name(node) {
10648        return false;
10649    }
10650    if let Some(parent) = node.parent() {
10651        if parent.kind() == "call_expression"
10652            && parent.child_by_field_name("function") == Some(node)
10653        {
10654            return false;
10655        }
10656        if matches!(parent.kind(), "labeled_statement" | "goto_statement")
10657            && parent.child_by_field_name("label") == Some(node)
10658        {
10659            return false;
10660        }
10661    }
10662    let mut current = node.parent();
10663    while let Some(ancestor) = current {
10664        if ancestor.kind().starts_with("preproc_") {
10665            return false;
10666        }
10667        if matches!(
10668            ancestor.kind(),
10669            "translation_unit" | "function_definition" | "compound_statement"
10670        ) {
10671            break;
10672        }
10673        current = ancestor.parent();
10674    }
10675    true
10676}
10677
10678fn recovered_c_reference_node(
10679    visibility: &VisibilityIndex<'_>,
10680    file: &ProjectFile,
10681    node: Node<'_>,
10682    source: &str,
10683) -> bool {
10684    if node.start_byte() >= node.end_byte()
10685        || node.is_error()
10686        || node.is_missing()
10687        || !matches!(
10688            node.kind(),
10689            "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
10690        )
10691        || recovered_c_macro_binding_role(node)
10692        || recovered_c_label_role(node)
10693    {
10694        return false;
10695    }
10696
10697    let name = node_text(node, source);
10698    if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
10699        return true;
10700    }
10701    if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
10702        return true;
10703    }
10704    if is_declaration_name(node) {
10705        return false;
10706    }
10707    if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
10708        return true;
10709    }
10710    recovered_c_reference_anchor(node)
10711}
10712
10713fn recovered_c_explicit_assignment_callee(
10714    visibility: &VisibilityIndex<'_>,
10715    file: &ProjectFile,
10716    node: Node<'_>,
10717    name: &str,
10718) -> bool {
10719    let mut current = node;
10720    let error = loop {
10721        let Some(parent) = current.parent() else {
10722            return false;
10723        };
10724        if parent.is_error() {
10725            break parent;
10726        }
10727        current = parent;
10728    };
10729    let mut cursor = error.walk();
10730    let explicit_recovery_precedes_callee = error
10731        .named_children(&mut cursor)
10732        .take_while(|child| child.start_byte() < node.start_byte())
10733        .any(|child| child.kind() == "explicit_function_specifier");
10734    if !explicit_recovery_precedes_callee {
10735        return false;
10736    }
10737    visibility
10738        .cpp
10739        .declarations(file)
10740        .iter()
10741        .chain(visibility.visible_by_file.get(file).into_iter().flatten())
10742        .any(|candidate| candidate.identifier() == name && candidate.is_function())
10743}
10744
10745fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
10746    while let Some(parent) = node.parent() {
10747        if matches!(
10748            parent.kind(),
10749            "preproc_def" | "preproc_function_def" | "preproc_params"
10750        ) {
10751            return true;
10752        }
10753        if parent.is_error()
10754            || matches!(
10755                parent.kind(),
10756                "translation_unit" | "function_definition" | "compound_statement"
10757            )
10758        {
10759            return false;
10760        }
10761        node = parent;
10762    }
10763    false
10764}
10765
10766fn recovered_c_label_role(node: Node<'_>) -> bool {
10767    node.parent().is_some_and(|parent| {
10768        matches!(parent.kind(), "labeled_statement" | "goto_statement")
10769            && parent.child_by_field_name("label") == Some(node)
10770    })
10771}
10772
10773fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
10774    while let Some(parent) = node.parent() {
10775        if parent.is_error() {
10776            return false;
10777        }
10778        if parent.kind().ends_with("_expression")
10779            || matches!(
10780                parent.kind(),
10781                "argument_list"
10782                    | "return_statement"
10783                    | "expression_statement"
10784                    | "case_statement"
10785                    | "initializer_list"
10786                    | "init_declarator"
10787                    | "array_declarator"
10788                    | "field_designator"
10789                    | "enumerator"
10790            )
10791        {
10792            return true;
10793        }
10794        if matches!(
10795            parent.kind(),
10796            "translation_unit"
10797                | "function_definition"
10798                | "compound_statement"
10799                | "declaration"
10800                | "field_declaration"
10801                | "parameter_declaration"
10802        ) {
10803            return false;
10804        }
10805        node = parent;
10806    }
10807    false
10808}
10809
10810/// Whether a parameter declaration belongs to the callable scope whose body can
10811/// contain references to it.
10812///
10813/// Error recovery can wrap a macro-decorated class body in a synthetic outer
10814/// `function_definition`. Merely finding any callable ancestor would then leak
10815/// parameters from member prototypes into later member bodies. Require the
10816/// parameter to be inside that definition's own declarator instead.
10817pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
10818    let mut current = parameter.parent();
10819    while let Some(ancestor) = current {
10820        if ancestor.kind() == "lambda_expression" {
10821            return ancestor
10822                .child_by_field_name("declarator")
10823                .is_some_and(|declarator| {
10824                    declarator.start_byte() <= parameter.start_byte()
10825                        && parameter.end_byte() <= declarator.end_byte()
10826                });
10827        }
10828        if ancestor.kind() == "function_definition" {
10829            return ancestor
10830                .child_by_field_name("declarator")
10831                .is_some_and(|declarator| {
10832                    declarator.start_byte() <= parameter.start_byte()
10833                        && parameter.end_byte() <= declarator.end_byte()
10834                });
10835        }
10836        current = ancestor.parent();
10837    }
10838    false
10839}
10840
10841pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
10842    let mut current = node.parent();
10843    while let Some(ancestor) = current {
10844        if matches!(
10845            ancestor.kind(),
10846            "parameter_declaration" | "optional_parameter_declaration"
10847        ) {
10848            return ancestor
10849                .child_by_field_name("type")
10850                .is_some_and(|type_node| {
10851                    type_node.start_byte() <= node.start_byte()
10852                        && node.end_byte() <= type_node.end_byte()
10853                });
10854        }
10855        if matches!(
10856            ancestor.kind(),
10857            "function_definition" | "lambda_expression" | "compound_statement"
10858        ) {
10859            return false;
10860        }
10861        current = ancestor.parent();
10862    }
10863    false
10864}
10865
10866fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
10867    if specifier.child_by_field_name("body").is_some() {
10868        return true;
10869    }
10870    let mut current = specifier.parent();
10871    while let Some(ancestor) = current {
10872        match ancestor.kind() {
10873            "type_descriptor"
10874            | "parameter_declaration"
10875            | "optional_parameter_declaration"
10876            | "template_argument_list"
10877            | "cast_expression" => return false,
10878            "declaration" | "field_declaration" => {
10879                let mut cursor = ancestor.walk();
10880                return ancestor
10881                    .children_by_field_name("declarator", &mut cursor)
10882                    .next()
10883                    .is_none();
10884            }
10885            "translation_unit" => return true,
10886            _ => current = ancestor.parent(),
10887        }
10888    }
10889    false
10890}
10891
10892pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
10893    match node.kind() {
10894        "identifier"
10895        | "field_identifier"
10896        | "qualified_identifier"
10897        | "scoped_identifier"
10898        | "operator_name"
10899        | "destructor_name"
10900        | "literal_operator_name" => Some(node),
10901        "reference_declarator" | "parenthesized_declarator" => {
10902            node.named_child(0).and_then(declarator_name_node)
10903        }
10904        _ => node
10905            .child_by_field_name("declarator")
10906            .or_else(|| node.child_by_field_name("name"))
10907            .or_else(|| node.child_by_field_name("field"))
10908            .and_then(declarator_name_node),
10909    }
10910}
10911
10912fn declarator_name_path_contains(
10913    declarator: Node<'_>,
10914    candidate: Node<'_>,
10915    allow_type_identifier: bool,
10916) -> bool {
10917    let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
10918        return false;
10919    };
10920    let mut current = Some(declarator);
10921    while let Some(node) = current {
10922        if same_node(node, candidate) {
10923            return true;
10924        }
10925        if same_node(node, name) {
10926            return false;
10927        }
10928        current = node
10929            .child_by_field_name("declarator")
10930            .or_else(|| node.child_by_field_name("name"))
10931            .or_else(|| node.child_by_field_name("field"));
10932    }
10933    false
10934}
10935
10936fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
10937    match node.kind() {
10938        "identifier"
10939        | "field_identifier"
10940        | "operator_name"
10941        | "destructor_name"
10942        | "literal_operator_name" => Some(node),
10943        "type_identifier" if allow_type_identifier => Some(node),
10944        _ => node
10945            .child_by_field_name("declarator")
10946            .or_else(|| node.child_by_field_name("name"))
10947            .or_else(|| node.child_by_field_name("field"))
10948            .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
10949    }
10950}
10951
10952/// True when `node` is a component of a larger structured type node whose outer
10953/// range is the single reference surfaced to callers.
10954pub fn is_nested_type_node(node: Node<'_>) -> bool {
10955    node.parent().is_some_and(|parent| {
10956        matches!(
10957            parent.kind(),
10958            "qualified_identifier" | "scoped_type_identifier" | "template_type"
10959        )
10960    })
10961}
10962
10963pub struct OutOfLineMemberDefinitionOwners<'tree> {
10964    pub owners: Vec<(Node<'tree>, CodeUnit)>,
10965    innermost: Option<(Node<'tree>, CodeUnit)>,
10966}
10967
10968impl OutOfLineMemberDefinitionOwners<'_> {
10969    pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
10970        self.innermost.as_ref().map(|(node, owner)| (*node, owner))
10971    }
10972}
10973
10974pub struct QualifiedOwnerComponents<'tree> {
10975    pub nodes: Vec<Node<'tree>>,
10976    pub names: Vec<String>,
10977    pub global: bool,
10978}
10979
10980/// True when each structured qualifier on the callable-name path has a real
10981/// `::` token. A macro-prefixed return type can make tree-sitter insert a
10982/// zero-width missing separator and parse `TYPE Result<T> method()` as the
10983/// false qualified declarator `Result<T>::method`.
10984pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
10985    let mut stack = vec![node];
10986    let mut found_separator = false;
10987    while let Some(current) = stack.pop() {
10988        if !matches!(
10989            current.kind(),
10990            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
10991        ) {
10992            continue;
10993        }
10994        let mut current_has_separator = false;
10995        for index in 0..current.child_count() {
10996            let Some(child) = current.child(index) else {
10997                continue;
10998            };
10999            if child.kind() == "::" {
11000                if child.is_missing() {
11001                    return false;
11002                }
11003                current_has_separator = true;
11004                found_separator = true;
11005            }
11006        }
11007        if !current_has_separator {
11008            return false;
11009        }
11010        for field in ["scope", "name"] {
11011            if let Some(child) = current.child_by_field_name(field)
11012                && matches!(
11013                    child.kind(),
11014                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
11015                )
11016            {
11017                stack.push(child);
11018            }
11019        }
11020    }
11021    found_separator
11022}
11023
11024pub fn qualified_owner_components<'tree>(
11025    node: Node<'tree>,
11026    source: &str,
11027) -> Option<QualifiedOwnerComponents<'tree>> {
11028    if !qualified_name_has_concrete_scope_separators(node) {
11029        return None;
11030    }
11031    let mut nodes = cpp_name_component_nodes(node)?;
11032    nodes.pop()?;
11033    if nodes.is_empty() {
11034        return None;
11035    }
11036    let names = nodes
11037        .iter()
11038        .map(|component| node_text(*component, source).to_string())
11039        .collect();
11040    Some(QualifiedOwnerComponents {
11041        nodes,
11042        names,
11043        global: is_globally_qualified_cpp_name(node),
11044    })
11045}
11046
11047/// Return the terminal type-name occurrence in an out-of-line destructor
11048/// declarator such as `endpoint::~endpoint`.  Unlike an ordinary terminal
11049/// method name, this identifier is a second reference to the owner type.
11050///
11051/// Every extra qualifier nests another `qualified_identifier` in the `name`
11052/// field, so `zmq::pair_t::~pair_t` reaches the destructor only two levels
11053/// down. Reading one level dropped the terminal occurrence for every
11054/// file-scope out-of-line member libzmq writes (#1831).
11055pub fn out_of_line_destructor_type_reference(node: Node<'_>) -> Option<Node<'_>> {
11056    if node.kind() != "qualified_identifier" {
11057        return None;
11058    }
11059    let mut qualified = node;
11060    let destructor = loop {
11061        let name = qualified.child_by_field_name("name")?;
11062        match name.kind() {
11063            "qualified_identifier" => qualified = name,
11064            "destructor_name" => break name,
11065            _ => return None,
11066        }
11067    };
11068    (0..destructor.named_child_count())
11069        .filter_map(|index| destructor.named_child(index))
11070        .find(|child| matches!(child.kind(), "identifier" | "type_identifier"))
11071}
11072
11073pub fn out_of_line_member_definition_owner<'tree>(
11074    analyzer: &CppGraphSource<'_>,
11075    visibility: &VisibilityIndex<'_>,
11076    file: &ProjectFile,
11077    source: &str,
11078    node: Node<'tree>,
11079) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
11080    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
11081        || !has_ancestor_kind(node, "function_definition")
11082        || !is_function_declarator_name_root(node)
11083    {
11084        return None;
11085    }
11086    let qualified = qualified_owner_components(node, source)?;
11087    let lexical_scope = enclosing_namespace_components(node, source)?;
11088    let mut owners = Vec::new();
11089    let mut innermost = None;
11090
11091    for component_count in 1..=qualified.names.len() {
11092        if let LexicalTypeResolution::Resolved { unit, .. } = visibility
11093            .resolve_type_components_lexically(
11094                analyzer,
11095                file,
11096                &qualified.names[..component_count],
11097                qualified.global,
11098                &lexical_scope,
11099            )
11100            && !owners
11101                .iter()
11102                .any(|(_, existing)| same_visible_symbol(existing, &unit))
11103        {
11104            if component_count == qualified.names.len() {
11105                innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
11106            }
11107            owners.push((qualified.nodes[component_count - 1], unit));
11108        }
11109    }
11110
11111    // The C++ analyzer has already reconciled an indexed out-of-line callable
11112    // against the include-visible class table. Consult that canonical owner
11113    // chain only when ordinary lexical lookup could not recover the innermost
11114    // owner.  A one-segment qualifier is safe here only when the enclosing
11115    // indexed callable has an authoritative class owner and the parser's
11116    // namespace path is a (possibly sparse) subsequence of that owner path.
11117    // The latter is what lets macro-wrapped namespace sentinels recover a
11118    // missing `time_internal`/`cord_internal` component without guessing an
11119    // unrelated short name.
11120    if innermost.is_none() {
11121        let indexed_owner_components = visibility
11122            .indexed_enclosing_owner_scope(analyzer, file, node)
11123            .or_else(|| {
11124                // Retain the legacy rendered-name fallback for the existing
11125                // multi-segment path when an enclosing owner chain is not
11126                // available (for example, cache-loaded units without parent
11127                // links).  One-segment recovery must stay canonical-only.
11128                if qualified.names.len() <= 1 {
11129                    return None;
11130                }
11131                let range = Range {
11132                    start_byte: node.start_byte(),
11133                    end_byte: node.end_byte(),
11134                    start_line: node.start_position().row,
11135                    end_line: node.end_position().row,
11136                };
11137                let start = analyzer.enclosing_code_unit(file, &range)?;
11138                let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11139                    brokk_bifrost_core::analyzer::Language::Cpp,
11140                    &cpp_name_for(&start),
11141                );
11142                components.pop();
11143                Some(components)
11144            });
11145        if let Some(indexed_owner_components) = indexed_owner_components
11146            && indexed_owner_components.len() > qualified.names.len()
11147            && indexed_owner_components.ends_with(&qualified.names)
11148            && indexed_namespace_path_is_recoverable(
11149                &lexical_scope,
11150                &indexed_owner_components,
11151                qualified.names.len(),
11152            )
11153            // A globally-qualified one-segment owner is an explicit request
11154            // for the top-level binding; do not reinterpret it as a missing
11155            // namespace component.  Existing multi-segment global lookups
11156            // retain their historical indexed recovery.
11157            && (qualified.names.len() > 1 || !qualified.global)
11158        {
11159            let namespace_count = indexed_owner_components.len() - qualified.names.len();
11160            for component_count in 1..=qualified.names.len() {
11161                let expected = &indexed_owner_components[..namespace_count + component_count];
11162                let owner_node = qualified.nodes[component_count - 1];
11163                for owner in visibility
11164                    .visible_identifier_candidates(file, &qualified.names[component_count - 1])
11165                    .filter(|candidate| candidate.is_class())
11166                    .filter(|candidate| {
11167                        canonical_cpp_scope_components(candidate) == expected
11168                            && visibility.external_type_candidate_visible_in_context(
11169                                analyzer, file, candidate, node,
11170                            )
11171                    })
11172                {
11173                    if component_count == qualified.names.len() && innermost.is_none() {
11174                        innermost = Some((owner_node, owner.clone()));
11175                    }
11176                    if !owners
11177                        .iter()
11178                        .any(|(_, existing)| same_symbol(existing, owner))
11179                    {
11180                        owners.push((owner_node, owner.clone()));
11181                    }
11182                }
11183            }
11184        }
11185    }
11186    (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
11187}
11188
11189fn is_function_declarator_name_root(node: Node<'_>) -> bool {
11190    let mut current = node;
11191    while let Some(parent) = current.parent() {
11192        if parent.kind() == "function_declarator" {
11193            return parent.child_by_field_name("declarator") == Some(current);
11194        }
11195        if matches!(
11196            parent.kind(),
11197            "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
11198        ) && parent.child_by_field_name("declarator") == Some(current)
11199        {
11200            current = parent;
11201            continue;
11202        }
11203        return false;
11204    }
11205    false
11206}
11207
11208pub fn append_cpp_name_components(
11209    node: Node<'_>,
11210    source: &str,
11211    out: &mut Vec<String>,
11212) -> Option<()> {
11213    out.extend(
11214        cpp_name_component_nodes(node)?
11215            .into_iter()
11216            .map(|component| node_text(component, source).to_string()),
11217    );
11218    Some(())
11219}
11220
11221pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
11222    let mut components = Vec::new();
11223    append_cpp_name_components(node, source, &mut components)?;
11224    Some(components)
11225}
11226
11227/// The base scopes named by member using-declarations for `member` in one
11228/// class source range.
11229///
11230/// The grammar supplies the qualified identifier and each component. Keep
11231/// this interpretation shared between forward overload lookup and inverse
11232/// owner routing rather than reparsing a rendered `Base::member` string at
11233/// either call site.
11234pub fn cpp_member_using_declaration_scopes(source: &str, member: &str) -> Vec<String> {
11235    let mut parser = Parser::new();
11236    if parser
11237        .set_language(&tree_sitter_cpp::LANGUAGE.into())
11238        .is_err()
11239    {
11240        return Vec::new();
11241    }
11242    let Some(tree) = parser.parse(source, None) else {
11243        return Vec::new();
11244    };
11245    let mut scopes = Vec::new();
11246    let mut pending = vec![tree.root_node()];
11247    while let Some(node) = pending.pop() {
11248        if node.kind() == "using_declaration" {
11249            let Some(imported) = node.named_child(0) else {
11250                continue;
11251            };
11252            let Some(mut components) = cpp_type_name_components(imported, source) else {
11253                continue;
11254            };
11255            if components.pop().as_deref() == Some(member) && !components.is_empty() {
11256                scopes.push(components.join("::"));
11257            }
11258            continue;
11259        }
11260        for index in (0..node.named_child_count()).rev() {
11261            if let Some(child) = node.named_child(index) {
11262                pending.push(child);
11263            }
11264        }
11265    }
11266    scopes
11267}
11268
11269/// Whether a structured using-declaration scope can name `qualified` as an
11270/// ancestor class. The boundary check prevents `Base` from matching
11271/// `OtherBase` while allowing a relative `Base` spelling to match `ns::Base`.
11272pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
11273    qualified == scope
11274        || qualified
11275            .strip_suffix(scope)
11276            .is_some_and(|prefix| prefix.ends_with("::"))
11277}
11278
11279/// Whether `node` is the direct structured type payload of a template
11280/// argument. This role remains meaningful even when a surrounding expression
11281/// is below tree-sitter recovery, because both the `template_argument_list`
11282/// and the `type_descriptor` retain their named fields.
11283pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
11284    let Some(type_descriptor) = node.parent() else {
11285        return false;
11286    };
11287    if type_descriptor.kind() != "type_descriptor"
11288        || type_descriptor.child_by_field_name("type") != Some(node)
11289    {
11290        return false;
11291    }
11292    let Some(arguments) = type_descriptor.parent() else {
11293        return false;
11294    };
11295    if arguments.kind() != "template_argument_list" {
11296        return false;
11297    }
11298    arguments.parent().is_some_and(|parent| {
11299        matches!(parent.kind(), "template_type" | "template_function")
11300            && parent.child_by_field_name("arguments") == Some(arguments)
11301    })
11302}
11303
11304pub fn cpp_template_reference_arguments(
11305    mut node: Node<'_>,
11306    source: &str,
11307) -> Option<Vec<CppTemplateExpression>> {
11308    loop {
11309        match node.kind() {
11310            "template_type" | "template_function" => {
11311                let arguments = node.child_by_field_name("arguments")?;
11312                let mut cursor = arguments.walk();
11313                return Some(
11314                    arguments
11315                        .named_children(&mut cursor)
11316                        .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
11317                        .map(|argument| CppTemplateExpression {
11318                            text: normalize_cpp_whitespace(node_text(argument, source)),
11319                            term: cpp_template_term(argument, source, &[]),
11320                        })
11321                        .collect(),
11322                );
11323            }
11324            "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
11325                node = node
11326                    .child_by_field_name("name")
11327                    .or_else(|| node.child_by_field_name("type"))?;
11328            }
11329            _ => return None,
11330        }
11331    }
11332}
11333
11334fn cpp_reconcile_primary_template_parameters(
11335    candidates: &[(&CodeUnit, &CppTemplateMetadata)],
11336    preferred: &CodeUnit,
11337) -> Option<Vec<CppTemplateParameterMetadata>> {
11338    let canonical = candidates
11339        .iter()
11340        .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
11341    let mut merged = canonical
11342        .parameters
11343        .iter()
11344        .map(|parameter| CppTemplateParameterMetadata {
11345            name: parameter.name.clone(),
11346            kind: parameter.kind,
11347            variadic: parameter.variadic,
11348            default: None,
11349        })
11350        .collect::<Vec<_>>();
11351
11352    for (_, metadata) in candidates {
11353        if metadata.parameters.len() != merged.len() {
11354            return None;
11355        }
11356        let rename_bindings = metadata
11357            .parameters
11358            .iter()
11359            .zip(&merged)
11360            .map(|(parameter, canonical)| {
11361                (
11362                    parameter.name.clone(),
11363                    CppTemplateTerm::Parameter(canonical.name.clone()),
11364                )
11365            })
11366            .collect::<HashMap<_, _>>();
11367        for ((parameter, canonical), merged_parameter) in metadata
11368            .parameters
11369            .iter()
11370            .zip(&canonical.parameters)
11371            .zip(&mut merged)
11372        {
11373            if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
11374                return None;
11375            }
11376            let Some(default) = &parameter.default else {
11377                continue;
11378            };
11379            let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
11380            if let Some(existing) = &merged_parameter.default {
11381                if !cpp_template_terms_equal(&existing.term, &normalized_term) {
11382                    return None;
11383                }
11384            } else {
11385                merged_parameter.default = Some(CppTemplateExpression {
11386                    text: default.text.clone(),
11387                    term: normalized_term,
11388                });
11389            }
11390        }
11391    }
11392    Some(merged)
11393}
11394
11395pub fn cpp_bind_template_arguments(
11396    parameters: &[CppTemplateParameterMetadata],
11397    explicit_arguments: &[CppTemplateExpression],
11398) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
11399    let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
11400    if variadic_index.is_some_and(|index| {
11401        index + 1 != parameters.len()
11402            || parameters[index + 1..]
11403                .iter()
11404                .any(|parameter| parameter.variadic)
11405    }) {
11406        return None;
11407    }
11408    let fixed_count = variadic_index.unwrap_or(parameters.len());
11409    if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
11410        return None;
11411    }
11412    let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
11413    let mut expanded = explicit_arguments[..explicit_fixed_count]
11414        .iter()
11415        .map(cpp_clone_template_expression_iterative)
11416        .collect::<Vec<_>>();
11417    let mut bindings = HashMap::default();
11418    for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
11419        bindings.insert(
11420            parameter.name.clone(),
11421            cpp_clone_template_term_iterative(&argument.term),
11422        );
11423    }
11424    for parameter in &parameters[explicit_fixed_count..fixed_count] {
11425        let default = parameter.default.as_ref()?;
11426        let term = cpp_substitute_template_term(&default.term, &bindings)?;
11427        bindings.insert(parameter.name.clone(), term.clone());
11428        expanded.push(CppTemplateExpression {
11429            text: default.text.clone(),
11430            term,
11431        });
11432    }
11433    if let Some(index) = variadic_index {
11434        let packed_arguments = &explicit_arguments[explicit_fixed_count..];
11435        expanded.extend(
11436            packed_arguments
11437                .iter()
11438                .map(cpp_clone_template_expression_iterative),
11439        );
11440        bindings.insert(
11441            parameters[index].name.clone(),
11442            CppTemplateTerm::Node {
11443                kind: "parameter_pack".to_string(),
11444                children: packed_arguments
11445                    .iter()
11446                    .map(|argument| cpp_clone_template_term_iterative(&argument.term))
11447                    .collect(),
11448            },
11449        );
11450    }
11451    Some((expanded, bindings))
11452}
11453
11454fn cpp_specialization_matches(
11455    metadata: &CppTemplateMetadata,
11456    arguments: &[CppTemplateExpression],
11457) -> bool {
11458    if metadata.specialization_arguments.len() != arguments.len() {
11459        return false;
11460    }
11461    let parameter_names = metadata
11462        .parameters
11463        .iter()
11464        .map(|parameter| parameter.name.as_str())
11465        .collect::<HashSet<_>>();
11466    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
11467    for (pattern, argument) in metadata.specialization_arguments.iter().zip(arguments) {
11468        if !cpp_unify_template_term(
11469            &pattern.term,
11470            &argument.term,
11471            &parameter_names,
11472            &mut bindings,
11473        ) {
11474            return false;
11475        }
11476    }
11477    true
11478}
11479
11480fn cpp_specialization_more_specialized(
11481    candidate: &CppTemplateMetadata,
11482    other: &CppTemplateMetadata,
11483) -> bool {
11484    cpp_specialization_pattern_accepts(other, candidate)
11485        && !cpp_specialization_pattern_accepts(candidate, other)
11486}
11487
11488fn cpp_specialization_pattern_accepts(
11489    broader: &CppTemplateMetadata,
11490    narrower: &CppTemplateMetadata,
11491) -> bool {
11492    if broader.specialization_arguments.len() != narrower.specialization_arguments.len() {
11493        return false;
11494    }
11495    let parameter_names = broader
11496        .parameters
11497        .iter()
11498        .map(|parameter| parameter.name.as_str())
11499        .collect::<HashSet<_>>();
11500    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
11501    broader
11502        .specialization_arguments
11503        .iter()
11504        .zip(&narrower.specialization_arguments)
11505        .all(|(pattern, argument)| {
11506            cpp_unify_template_term(
11507                &pattern.term,
11508                &argument.term,
11509                &parameter_names,
11510                &mut bindings,
11511            )
11512        })
11513}
11514
11515pub fn cpp_substitute_template_term(
11516    term: &CppTemplateTerm,
11517    bindings: &HashMap<String, CppTemplateTerm>,
11518) -> Option<CppTemplateTerm> {
11519    enum Work<'a> {
11520        Visit(&'a CppTemplateTerm),
11521        Build { kind: String, child_count: usize },
11522    }
11523
11524    let mut work = vec![Work::Visit(term)];
11525    let mut substituted = Vec::new();
11526    while let Some(next) = work.pop() {
11527        match next {
11528            Work::Visit(CppTemplateTerm::Parameter(name)) => {
11529                substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
11530            }
11531            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
11532                substituted.push(CppTemplateTerm::Atom {
11533                    kind: kind.clone(),
11534                    text: text.clone(),
11535                });
11536            }
11537            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
11538                work.push(Work::Build {
11539                    kind: kind.clone(),
11540                    child_count: children.len(),
11541                });
11542                work.extend(children.iter().rev().map(Work::Visit));
11543            }
11544            Work::Build { kind, child_count } => {
11545                let children = substituted.split_off(substituted.len() - child_count);
11546                substituted.push(CppTemplateTerm::Node { kind, children });
11547            }
11548        }
11549    }
11550    substituted.pop()
11551}
11552
11553pub fn cpp_substitute_template_arguments(
11554    arguments: &[CppTemplateExpression],
11555    bindings: &HashMap<String, CppTemplateTerm>,
11556) -> Option<Vec<CppTemplateExpression>> {
11557    let mut substituted = Vec::new();
11558    for argument in arguments {
11559        let CppTemplateTerm::Node { kind, children } = &argument.term else {
11560            substituted.push(CppTemplateExpression {
11561                text: argument.text.clone(),
11562                term: cpp_substitute_template_term(&argument.term, bindings)?,
11563            });
11564            continue;
11565        };
11566        if kind != "parameter_pack_expansion" {
11567            substituted.push(CppTemplateExpression {
11568                text: argument.text.clone(),
11569                term: cpp_substitute_template_term(&argument.term, bindings)?,
11570            });
11571            continue;
11572        }
11573        let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
11574            return None;
11575        };
11576        if ellipsis != "..." {
11577            return None;
11578        }
11579
11580        let mut pack_names = Vec::new();
11581        let mut work = vec![pattern];
11582        while let Some(term) = work.pop() {
11583            match term {
11584                CppTemplateTerm::Parameter(name)
11585                    if matches!(
11586                        bindings.get(name),
11587                        Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
11588                    ) =>
11589                {
11590                    if !pack_names.contains(name) {
11591                        pack_names.push(name.clone());
11592                    }
11593                }
11594                CppTemplateTerm::Node { children, .. } => work.extend(children),
11595                CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
11596            }
11597        }
11598        let first_pack = pack_names.first()?;
11599        let CppTemplateTerm::Node {
11600            children: first_elements,
11601            ..
11602        } = bindings.get(first_pack)?
11603        else {
11604            return None;
11605        };
11606        let pack_len = first_elements.len();
11607        for pack_name in &pack_names {
11608            let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
11609                return None;
11610            };
11611            if children.len() != pack_len {
11612                return None;
11613            }
11614        }
11615        for index in 0..pack_len {
11616            let mut element_bindings = bindings.clone();
11617            for pack_name in &pack_names {
11618                let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
11619                    return None;
11620                };
11621                element_bindings.insert(
11622                    pack_name.clone(),
11623                    cpp_clone_template_term_iterative(&children[index]),
11624                );
11625            }
11626            substituted.push(CppTemplateExpression {
11627                text: argument.text.clone(),
11628                term: cpp_substitute_template_term(pattern, &element_bindings)?,
11629            });
11630        }
11631    }
11632    Some(substituted)
11633}
11634
11635fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
11636    enum Work<'a> {
11637        Visit(&'a CppTemplateTerm),
11638        Build { kind: String, child_count: usize },
11639    }
11640
11641    let mut work = vec![Work::Visit(term)];
11642    let mut cloned = Vec::new();
11643    while let Some(next) = work.pop() {
11644        match next {
11645            Work::Visit(CppTemplateTerm::Parameter(name)) => {
11646                cloned.push(CppTemplateTerm::Parameter(name.clone()));
11647            }
11648            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
11649                cloned.push(CppTemplateTerm::Atom {
11650                    kind: kind.clone(),
11651                    text: text.clone(),
11652                });
11653            }
11654            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
11655                work.push(Work::Build {
11656                    kind: kind.clone(),
11657                    child_count: children.len(),
11658                });
11659                work.extend(children.iter().rev().map(Work::Visit));
11660            }
11661            Work::Build { kind, child_count } => {
11662                let children = cloned.split_off(cloned.len() - child_count);
11663                cloned.push(CppTemplateTerm::Node { kind, children });
11664            }
11665        }
11666    }
11667    cloned
11668        .pop()
11669        .expect("template term traversal emits one root")
11670}
11671
11672fn cpp_clone_template_expression_iterative(
11673    expression: &CppTemplateExpression,
11674) -> CppTemplateExpression {
11675    CppTemplateExpression {
11676        text: expression.text.clone(),
11677        term: cpp_clone_template_term_iterative(&expression.term),
11678    }
11679}
11680
11681pub fn cpp_unify_template_term(
11682    pattern: &CppTemplateTerm,
11683    argument: &CppTemplateTerm,
11684    parameters: &HashSet<&str>,
11685    bindings: &mut HashMap<String, CppTemplateTerm>,
11686) -> bool {
11687    let mut work = vec![(pattern, argument)];
11688    while let Some((pattern, argument)) = work.pop() {
11689        match pattern {
11690            CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
11691                if let Some(bound) = bindings.get(name) {
11692                    if !cpp_template_terms_equal(bound, argument) {
11693                        return false;
11694                    }
11695                } else {
11696                    bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
11697                }
11698            }
11699            CppTemplateTerm::Atom {
11700                kind: pattern_kind,
11701                text: pattern_text,
11702            } => {
11703                if !matches!(
11704                    argument,
11705                    CppTemplateTerm::Atom { kind, text }
11706                        if kind == pattern_kind && text == pattern_text
11707                ) {
11708                    return false;
11709                }
11710            }
11711            CppTemplateTerm::Node {
11712                kind: pattern_kind,
11713                children: pattern_children,
11714            } => {
11715                let CppTemplateTerm::Node { kind, children } = argument else {
11716                    return false;
11717                };
11718                if kind != pattern_kind || children.len() != pattern_children.len() {
11719                    return false;
11720                }
11721                work.extend(pattern_children.iter().zip(children).rev());
11722            }
11723            CppTemplateTerm::Parameter(_) => return false,
11724        }
11725    }
11726    true
11727}
11728
11729fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
11730    let mut work = vec![(left, right)];
11731    while let Some((left, right)) = work.pop() {
11732        match (left, right) {
11733            (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
11734                if left != right {
11735                    return false;
11736                }
11737            }
11738            (
11739                CppTemplateTerm::Atom {
11740                    kind: left_kind,
11741                    text: left_text,
11742                },
11743                CppTemplateTerm::Atom {
11744                    kind: right_kind,
11745                    text: right_text,
11746                },
11747            ) => {
11748                if left_kind != right_kind || left_text != right_text {
11749                    return false;
11750                }
11751            }
11752            (
11753                CppTemplateTerm::Node {
11754                    kind: left_kind,
11755                    children: left_children,
11756                },
11757                CppTemplateTerm::Node {
11758                    kind: right_kind,
11759                    children: right_children,
11760                },
11761            ) => {
11762                if left_kind != right_kind || left_children.len() != right_children.len() {
11763                    return false;
11764                }
11765                work.extend(left_children.iter().zip(right_children).rev());
11766            }
11767            _ => return false,
11768        }
11769    }
11770    true
11771}
11772
11773pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
11774    let mut components = Vec::new();
11775    let mut stack = vec![node];
11776    while let Some(current) = stack.pop() {
11777        match current.kind() {
11778            "identifier"
11779            | "field_identifier"
11780            | "namespace_identifier"
11781            | "type_identifier"
11782            | "operator_name"
11783            | "destructor_name" => components.push(current),
11784            "template_type" | "template_function" => {
11785                stack.push(current.child_by_field_name("name")?);
11786            }
11787            "dependent_name" => stack.push(current.named_child(0)?),
11788            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
11789                stack.push(current.child_by_field_name("name")?);
11790                if let Some(scope) = current.child_by_field_name("scope") {
11791                    stack.push(scope);
11792                }
11793            }
11794            "nested_namespace_specifier" => {
11795                for index in (0..current.named_child_count()).rev() {
11796                    stack.push(current.named_child(index)?);
11797                }
11798            }
11799            _ => return None,
11800        }
11801    }
11802    Some(components)
11803}
11804
11805pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
11806    node.child_by_field_name("scope").is_none()
11807        && node.child(0).is_some_and(|child| child.kind() == "::")
11808}
11809
11810fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
11811    let mut namespaces = Vec::new();
11812    let mut current = node.parent();
11813    while let Some(parent) = current {
11814        if parent.kind() == "namespace_definition"
11815            && let Some(name) = parent.child_by_field_name("name")
11816        {
11817            let mut components = Vec::new();
11818            append_cpp_name_components(name, source, &mut components)?;
11819            namespaces.push(components);
11820        }
11821        current = parent.parent();
11822    }
11823    namespaces.reverse();
11824    Some(namespaces.into_iter().flatten().collect())
11825}
11826
11827/// Whether a parser-derived namespace path can be reconciled with an indexed
11828/// owner scope without inventing an unrelated short-name binding.
11829///
11830/// Macro namespace sentinels can make tree-sitter omit one or more namespace
11831/// definitions from the ancestor chain. Preserve the order of every namespace
11832/// that did survive parsing, but allow indexed components between them. An
11833/// empty path is accepted only when the declarator itself supplies a nested
11834/// owner suffix such as `Outer::Inner`: together with the indexed enclosing
11835/// owner chain, that suffix is structural evidence that a namespace was lost.
11836/// A one-segment owner at the translation-unit root remains insufficient.
11837fn indexed_namespace_path_is_recoverable(
11838    lexical_scope: &[String],
11839    indexed_owner_scope: &[String],
11840    explicit_owner_component_count: usize,
11841) -> bool {
11842    if lexical_scope.is_empty() {
11843        return explicit_owner_component_count > 1;
11844    }
11845    if lexical_scope.len() >= indexed_owner_scope.len() {
11846        return false;
11847    }
11848    let mut indexed = indexed_owner_scope.iter();
11849    lexical_scope
11850        .iter()
11851        .all(|component| indexed.any(|candidate| candidate == component))
11852}
11853
11854pub fn has_ancestor_kind(node: Node<'_>, kind: &str) -> bool {
11855    let mut current = node.parent();
11856    while let Some(parent) = current {
11857        if parent.kind() == kind {
11858            return true;
11859        }
11860        current = parent.parent();
11861    }
11862    false
11863}
11864
11865/// Return the terminal identifier represented by a callable or type callee.
11866///
11867/// Qualified, scoped, template, and field wrappers are traversed through their
11868/// grammar fields so both function calls and type constructions emit the token
11869/// that names the referenced declaration.
11870pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
11871    loop {
11872        let next = match node.kind() {
11873            "qualified_identifier"
11874            | "scoped_identifier"
11875            | "template_method"
11876            | "template_function"
11877            | "template_type" => node.child_by_field_name("name"),
11878            "field_expression" => node.child_by_field_name("field"),
11879            _ => None,
11880        };
11881        let Some(next) = next else {
11882            return node;
11883        };
11884        node = next;
11885    }
11886}
11887
11888#[derive(Clone, Copy)]
11889pub struct RecoveredRelationalTemplateMemberCall<'tree> {
11890    pub receiver: Node<'tree>,
11891    pub member: Node<'tree>,
11892    pub arity: usize,
11893}
11894
11895/// Recover `receiver.member<argument>(call_arguments)` when tree-sitter chose
11896/// nested relational expressions instead of a `template_method` call.
11897///
11898/// The recovery uses only grammar fields: the selected field must be the left
11899/// side of `<`, that expression must be the left side of `>`, and the right
11900/// side of `>` must be the parenthesized call arguments. Semantic callers must
11901/// additionally prove the receiver owner and the member's template status.
11902pub fn recovered_relational_template_member_call(
11903    field: Node<'_>,
11904) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
11905    if field.kind() != "field_expression" {
11906        return None;
11907    }
11908    let receiver = field
11909        .child_by_field_name("argument")
11910        .or_else(|| field.child_by_field_name("object"))?;
11911    let member = field.child_by_field_name("field")?;
11912    let less = field.parent()?;
11913    if less.kind() != "binary_expression"
11914        || less.child_by_field_name("left") != Some(field)
11915        || less
11916            .child_by_field_name("operator")
11917            .is_none_or(|operator| operator.kind() != "<")
11918        || less.child_by_field_name("right").is_none()
11919    {
11920        return None;
11921    }
11922    let greater = less.parent()?;
11923    if greater.kind() != "binary_expression"
11924        || greater.child_by_field_name("left") != Some(less)
11925        || greater
11926            .child_by_field_name("operator")
11927            .is_none_or(|operator| operator.kind() != ">")
11928    {
11929        return None;
11930    }
11931    let arguments = greater.child_by_field_name("right")?;
11932    if arguments.kind() != "parenthesized_expression" {
11933        return None;
11934    }
11935    let arity = parenthesized_call_argument_arity(arguments)?;
11936    Some(RecoveredRelationalTemplateMemberCall {
11937        receiver,
11938        member,
11939        arity,
11940    })
11941}
11942
11943fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
11944    let expression = arguments.named_child(0)?;
11945    if expression.kind() != "comma_expression" {
11946        return Some(1);
11947    }
11948    let mut arity = 0usize;
11949    let mut stack = vec![expression];
11950    while let Some(node) = stack.pop() {
11951        if node.kind() == "comma_expression" {
11952            stack.push(node.child_by_field_name("right")?);
11953            stack.push(node.child_by_field_name("left")?);
11954        } else {
11955            arity += 1;
11956        }
11957    }
11958    Some(arity)
11959}
11960
11961/// Whether `node` is part of a call's callee expression, walking only through
11962/// the grammar wrappers that can structurally contain that callee.
11963pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
11964    while let Some(parent) = node.parent() {
11965        match parent.kind() {
11966            "call_expression" => {
11967                return parent
11968                    .child_by_field_name("function")
11969                    .or_else(|| parent.named_child(0))
11970                    == Some(node);
11971            }
11972            "qualified_identifier"
11973            | "scoped_identifier"
11974            | "template_function"
11975            | "template_type"
11976            | "field_expression" => node = parent,
11977            _ => return false,
11978        }
11979    }
11980    false
11981}
11982
11983pub fn type_reference_hit_node(node: Node<'_>) -> Node<'_> {
11984    if is_call_callee_node(node) {
11985        function_terminal_node(node)
11986    } else {
11987        node
11988    }
11989}
11990
11991pub fn normalize_type_text(value: &str) -> String {
11992    strip_tag_type_prefix(
11993        normalize_cpp_whitespace(value)
11994            .trim_start_matches("const ")
11995            .trim_end_matches('*')
11996            .trim_end_matches('&')
11997            .trim(),
11998    )
11999    .to_string()
12000}
12001
12002fn strip_tag_type_prefix(value: &str) -> &str {
12003    let value = value.trim_start_matches("const ");
12004    value
12005        .strip_prefix("struct ")
12006        .or_else(|| value.strip_prefix("class "))
12007        .or_else(|| value.strip_prefix("enum "))
12008        .unwrap_or(value)
12009        .trim()
12010}
12011
12012pub fn normalize_reference_name(value: &str) -> Option<String> {
12013    let normalized = normalize_cpp_reference_text(value);
12014    (!normalized.is_empty()).then_some(normalized)
12015}
12016
12017pub fn normalize_cpp_reference_text(value: &str) -> String {
12018    let mut text = normalize_cpp_whitespace(value)
12019        .trim_start_matches("new ")
12020        .trim()
12021        .to_string();
12022    if let Some(index) = text.find(['(', '{']) {
12023        text.truncate(index);
12024    }
12025    if let Some(index) = text.find('<') {
12026        text.truncate(index);
12027    }
12028    let normalized = text
12029        .trim()
12030        .trim_start_matches("const ")
12031        .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
12032        .trim_matches(':')
12033        .trim();
12034    strip_tag_type_prefix(normalized).to_string()
12035}
12036
12037pub fn cpp_name_for(unit: &CodeUnit) -> String {
12038    let short = unit.short_name().replace(['.', '$'], "::");
12039    if unit.package_name().is_empty() {
12040        short
12041    } else {
12042        format!("{}::{}", unit.package_name(), short)
12043    }
12044}
12045
12046/// Render an indexed C++ qualified name from its authoritative FqName
12047/// segments. Unlike the legacy `cpp_name_for` renderer, this preserves dots
12048/// that belong to a template argument (for example `Args...`).
12049fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
12050    let fq = unit.fq();
12051    if fq.is_empty() {
12052        return None;
12053    }
12054    let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
12055    Some(
12056        fq.segments()
12057            .iter()
12058            .map(|&segment| interner.resolve(segment).0)
12059            .collect::<Vec<_>>()
12060            .join("::"),
12061    )
12062}
12063
12064fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
12065    canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
12066        || unit.fq().is_empty() && cpp_name_for(unit) == expected
12067}
12068
12069/// Return the indexed C++ owner scope without reparsing its rendered name.
12070///
12071/// Template spellings are opaque within an indexed `FqName` segment.  In
12072/// particular, the ellipsis in a parameter pack (`Args...`) is part of the
12073/// `AtomicHook<...>` type segment; feeding the legacy all-`::` rendering back
12074/// through `parse_symbol_path` would mistake those dots for component
12075/// separators.  Cache-loaded/legacy units may still have an empty structured
12076/// name, so retain the parser only as that explicit fallback.
12077pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
12078    let fq = unit.fq();
12079    if !fq.is_empty() {
12080        let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
12081        let scope = fq
12082            .segments()
12083            .iter()
12084            .filter_map(|&segment| {
12085                let (text, kind) = interner.resolve(segment);
12086                matches!(
12087                    kind,
12088                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
12089                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
12090                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
12091                )
12092                .then(|| text.to_string())
12093            })
12094            .collect();
12095        return scope;
12096    }
12097    brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
12098        brokk_bifrost_core::analyzer::Language::Cpp,
12099        &cpp_name_for(unit),
12100    )
12101}
12102
12103// fqname-M4: the second stage splits on the individual chars '.', '-', '>'
12104// (not the substring "->"), which deliberately reduces an `operator->`-style
12105// terminal segment to an empty tail rather than keeping it intact; the shared
12106// structured splitter's cpp operator-token merge would keep `operator->`
12107// whole instead, changing this function's result — `name_matches_callable`'s
12108// `expected.starts_with("operator")` fallback exists specifically to
12109// compensate for that reduction, and a pinned regression test
12110// (`operator-> must not be reduced with terminal_name-style punctuation
12111// splitting`) asserts today's char-class behavior. Not equivalence-provable;
12112// revisit alongside that pinned test if it is ever relaxed.
12113pub fn terminal_name(value: &str) -> &str {
12114    value
12115        .rsplit("::")
12116        .next()
12117        .unwrap_or(value)
12118        .rsplit(['.', '-', '>'])
12119        .next()
12120        .unwrap_or(value)
12121        .trim()
12122}
12123
12124pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
12125    terminal_name(&normalize_cpp_reference_text(value)) == expected
12126}
12127
12128pub fn name_matches_callable(value: &str, expected: &str) -> bool {
12129    name_matches_terminal(value, expected)
12130        || expected.starts_with("operator")
12131            && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
12132}
12133
12134pub fn name_mentions(value: &str, expected: &str) -> bool {
12135    normalize_cpp_reference_text(value)
12136        .split("::")
12137        .any(|part| part == expected)
12138}
12139
12140pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
12141    let cpp_name = cpp_name_for(unit);
12142    if reference.contains("::") {
12143        return reference == cpp_name;
12144    }
12145    reference == cpp_name
12146        || terminal_name(reference) == unit.identifier()
12147            && (unit.package_name().is_empty() || reference == unit.identifier())
12148}
12149
12150pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
12151    match kind {
12152        TargetKind::Type
12153        | TargetKind::Constructor
12154        | TargetKind::Method
12155        | TargetKind::MemberField => true,
12156        TargetKind::FreeFunction => unit.is_function(),
12157        TargetKind::GlobalField => unit.is_field(),
12158        TargetKind::Macro => unit.is_macro(),
12159    }
12160}
12161
12162pub fn is_type_alias(unit: &CodeUnit) -> bool {
12163    unit.kind() == CodeUnitType::Field
12164        && unit.signature().is_some_and(|signature| {
12165            signature.starts_with("typedef ") || signature.starts_with("using ")
12166        })
12167}
12168
12169fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
12170    let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
12171    let target_name = cpp_name_for(target);
12172    if normalized.contains("::") {
12173        return normalized == target_name;
12174    }
12175    if let Some(namespace) = alias.namespace.as_deref() {
12176        return namespace_prefixes(namespace)
12177            .into_iter()
12178            .any(|prefix| format!("{prefix}::{normalized}") == target_name);
12179    }
12180    target.package_name().is_empty() && normalized == target.identifier()
12181}
12182
12183fn parser_alias_target_names(alias: &CppAlias) -> Vec<String> {
12184    let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
12185    if normalized.contains("::") {
12186        return vec![normalized];
12187    }
12188    alias
12189        .namespace
12190        .as_deref()
12191        .map(namespace_prefixes)
12192        .map(|prefixes| {
12193            prefixes
12194                .into_iter()
12195                .map(|prefix| format!("{prefix}::{normalized}"))
12196                .collect()
12197        })
12198        .unwrap_or_else(|| vec![normalized])
12199}
12200
12201/// The declared return type text of a C++ function unit, with leading declaration specifiers
12202/// stripped, e.g. `T*` for `T* operator->()`.
12203pub fn cpp_function_return_type_text(
12204    analyzer: &CppGraphSource<'_>,
12205    function: &CodeUnit,
12206) -> Option<String> {
12207    let metadata = analyzer.signature_metadata(function);
12208    if !metadata.is_empty() {
12209        let first = metadata.first()?.return_type_text()?;
12210        return metadata
12211            .iter()
12212            .all(|metadata| metadata.return_type_text() == Some(first))
12213            .then(|| first.to_string());
12214    }
12215    let signature = cpp_function_signature_text(analyzer, function)?;
12216    cpp_function_return_type_text_from_signature(&signature)
12217}
12218
12219fn cpp_function_signature_text(
12220    analyzer: &CppGraphSource<'_>,
12221    function: &CodeUnit,
12222) -> Option<String> {
12223    function
12224        .signature()
12225        .filter(|signature| signature.contains(function.identifier()))
12226        .map(str::to_string)
12227        .or_else(|| analyzer.signatures(function).first().cloned())
12228        .or_else(|| analyzer.get_source(function, false))
12229}
12230
12231fn cpp_function_return_type_text_from_signature(signature: &str) -> Option<String> {
12232    let open = signature.find('(')?;
12233    let name_at = cpp_function_name_start(signature, open)?;
12234    if let Some(return_type) = cpp_trailing_return_type(&signature[name_at..]) {
12235        return Some(return_type);
12236    }
12237    let type_text = cpp_strip_leading_template_clause(&signature[..name_at])
12238        .split_whitespace()
12239        .filter(|token| {
12240            !matches!(
12241                *token,
12242                "static" | "virtual" | "inline" | "constexpr" | "explicit" | "friend"
12243            )
12244        })
12245        .collect::<Vec<_>>()
12246        .join(" ");
12247    let type_text = type_text.trim();
12248    (!type_text.is_empty()).then(|| type_text.to_string())
12249}
12250
12251fn cpp_function_name_start(signature: &str, open: usize) -> Option<usize> {
12252    let before_parameters = &signature[..open];
12253    if let Some(operator_at) = before_parameters.rfind("operator") {
12254        let boundary = operator_at == 0
12255            || before_parameters[..operator_at]
12256                .chars()
12257                .next_back()
12258                .is_some_and(|ch| !(ch == '_' || ch.is_ascii_alphanumeric()));
12259        if boundary {
12260            return Some(operator_at);
12261        }
12262    }
12263    before_parameters
12264        .rfind(|ch: char| !(ch == '_' || ch.is_ascii_alphanumeric()))
12265        .map(|index| index + 1)
12266}
12267
12268fn cpp_trailing_return_type(signature_from_name: &str) -> Option<String> {
12269    let open = signature_from_name.find('(')?;
12270    let mut depth = 0i32;
12271    for (offset, ch) in signature_from_name[open..].char_indices() {
12272        match ch {
12273            '(' => depth += 1,
12274            ')' => {
12275                depth -= 1;
12276                if depth == 0 {
12277                    let rest = signature_from_name[open + offset + ch.len_utf8()..].trim_start();
12278                    let arrow = rest.find("->")?;
12279                    let return_type = rest[arrow + 2..].trim_start();
12280                    let return_type = return_type
12281                        .split(['{', ';'])
12282                        .next()
12283                        .unwrap_or(return_type)
12284                        .trim();
12285                    return (!return_type.is_empty()).then(|| return_type.to_string());
12286                }
12287            }
12288            _ => {}
12289        }
12290    }
12291    None
12292}
12293
12294/// Strip a leading `template <...>` parameter clause, leaving the declaration that follows.
12295/// Returns the input unchanged when there is no such clause.
12296fn cpp_strip_leading_template_clause(text: &str) -> &str {
12297    let trimmed = text.trim_start();
12298    let Some(rest) = trimmed.strip_prefix("template") else {
12299        return text;
12300    };
12301    let rest = rest.trim_start();
12302    if !rest.starts_with('<') {
12303        return text;
12304    }
12305    let mut depth = 0i32;
12306    for (offset, ch) in rest.char_indices() {
12307        match ch {
12308            '<' => depth += 1,
12309            '>' => {
12310                depth -= 1;
12311                if depth == 0 {
12312                    return rest[offset + ch.len_utf8()..].trim_start();
12313                }
12314            }
12315            _ => {}
12316        }
12317    }
12318    text
12319}
12320
12321pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
12322    // fqname-M4: `cpp_name_for` is a bespoke all-`::` rendering of the unit's
12323    // name (it replaces every `.`/`$` in `short_name` with `::`), which is NOT
12324    // the same string `default_parent_fq_name`/`fq().parent()` would render:
12325    // the structured `FqName`'s native cpp display deliberately keeps `.` (not
12326    // `::`) between a trailing `Package` segment and a following `Type`
12327    // segment (see `separator` in `fq_name.rs`, landed for issue #1163), so
12328    // popping the unit's own `fq()` segment would NOT reproduce this
12329    // fully-`::`-joined string. Left as a split on the locally-built
12330    // all-colon string rather than the unit's structured name.
12331    cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
12332        namespace
12333            .strip_prefix("anonymous_namespace::")
12334            .unwrap_or(namespace)
12335            .to_string()
12336    })
12337}
12338
12339fn namespace_prefixes(namespace: &str) -> Vec<String> {
12340    // `namespace` is built by `cpp_name_for`/`cpp_namespace_for` with every
12341    // non-`::` separator already converted to `::`, so re-tokenizing it with
12342    // the shared structured splitter and progressively popping the last
12343    // component reproduces the `rsplit_once("::")` outward walk exactly (same
12344    // shape as `cpp_qualifier_lookup_tiers`'s namespace-chain walk).
12345    let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
12346        brokk_bifrost_core::analyzer::Language::Cpp,
12347        namespace,
12348    );
12349    let mut prefixes = Vec::new();
12350    while !parts.is_empty() {
12351        prefixes.push(parts.join("::"));
12352        parts.pop();
12353    }
12354    prefixes
12355}
12356
12357fn nearest_namespace_candidates(
12358    candidates: Vec<CodeUnit>,
12359    normalized: &str,
12360    lexical_namespace: Option<&str>,
12361) -> Vec<CodeUnit> {
12362    if normalized.contains("::") {
12363        return candidates;
12364    }
12365    if let Some(namespace) = lexical_namespace {
12366        for prefix in namespace_prefixes(namespace) {
12367            let scoped = candidates
12368                .iter()
12369                .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
12370                .cloned()
12371                .collect::<Vec<_>>();
12372            if !scoped.is_empty() {
12373                return scoped;
12374            }
12375        }
12376    }
12377    candidates
12378        .into_iter()
12379        .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
12380        .collect()
12381}
12382
12383pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
12384    let mut namespaces = Vec::new();
12385    let mut current = node.parent();
12386    while let Some(parent) = current {
12387        if parent.kind() == "namespace_definition"
12388            && let Some(name) = parent.child_by_field_name("name")
12389        {
12390            let namespace = normalize_cpp_reference_text(node_text(name, source));
12391            if !namespace.is_empty() {
12392                namespaces.push(namespace);
12393            }
12394        }
12395        current = parent.parent();
12396    }
12397    if namespaces.is_empty() {
12398        None
12399    } else {
12400        namespaces.reverse();
12401        Some(namespaces.join("::"))
12402    }
12403}
12404
12405/// Like [`precise_parent_of`], but drops module (namespace) parents. A namespace is a scope, not a
12406/// type or receiver, so namespace-scoped functions and constants resolve as free functions and
12407/// globals rather than members.
12408pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
12409    type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
12410}
12411
12412fn type_owner_resolution(
12413    analyzer: &CppGraphSource<'_>,
12414    code_unit: &CodeUnit,
12415) -> Option<ResolvedTypeOwner> {
12416    precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
12417}
12418
12419fn target_type_owner_resolution(
12420    analyzer: &CppGraphSource<'_>,
12421    code_unit: &CodeUnit,
12422) -> Option<ResolvedTypeOwner> {
12423    match type_owner_resolution(analyzer, code_unit) {
12424        Some(owner) if !owner.is_forward_declaration => Some(owner),
12425        Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
12426    }
12427}
12428
12429/// Recover method identity for an indexed out-of-line definition when the
12430/// analyzer has retained only its unique include-visible class forward
12431/// declaration. This is deliberately target-only: canonical declaration
12432/// resolution must continue to prefer the callable definition rather than
12433/// replacing it with the forward owner.
12434fn target_forward_owner_resolution(
12435    analyzer: &CppGraphSource<'_>,
12436    code_unit: &CodeUnit,
12437) -> Option<ResolvedTypeOwner> {
12438    if !code_unit.is_function() {
12439        return None;
12440    }
12441    let owner_fqn = brokk_bifrost_core::analyzer::default_parent_fq_name(code_unit)?;
12442    let cpp = analyzer.cpp?;
12443    let mut visible_files = HashSet::default();
12444    collect_include_closure(
12445        analyzer,
12446        cpp.include_target_index(),
12447        code_unit.source(),
12448        &mut visible_files,
12449        None,
12450    );
12451    let mut forward = None;
12452    for candidate in analyzer
12453        .global_usage_definition_index()
12454        .fqn(&owner_fqn)
12455        .into_iter()
12456        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
12457    {
12458        match cpp_class_declaration_strength(analyzer, candidate) {
12459            CppClassDeclarationStrength::Forward if forward.is_none() => {
12460                forward = Some(candidate.clone());
12461            }
12462            CppClassDeclarationStrength::Forward
12463            | CppClassDeclarationStrength::Full
12464            | CppClassDeclarationStrength::Unknown => return None,
12465        }
12466    }
12467    forward.map(|unit| ResolvedTypeOwner {
12468        unit,
12469        is_forward_declaration: true,
12470    })
12471}
12472
12473pub fn precise_parent_of(
12474    analyzer: &CppGraphSource<'_>,
12475    visibility: &VisibilityIndex<'_>,
12476    code_unit: &CodeUnit,
12477) -> Option<CodeUnit> {
12478    visibility.cached_precise_parent_of(analyzer, code_unit)
12479}
12480
12481fn precise_parent_resolution(
12482    analyzer: &CppGraphSource<'_>,
12483    code_unit: &CodeUnit,
12484) -> Option<ResolvedTypeOwner> {
12485    #[cfg(any(test, feature = "test-support"))]
12486    if let Some(cpp) = analyzer.cpp {
12487        cpp.record_cpp_parent_resolution_for_test();
12488    }
12489    if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
12490        return Some(ResolvedTypeOwner {
12491            unit,
12492            is_forward_declaration: false,
12493        });
12494    }
12495    let fallback = analyzer.parent_of(code_unit);
12496    // fqname-M4: `owner_name` is used both bare (passed standalone to the
12497    // owner-resolution calls below) and manually recombined with
12498    // `package_name()` a few lines down, so this needs the package-less
12499    // `short_name` owner specifically; `default_parent_fq_name`/`fq.parent()`
12500    // would render the package-qualified owner instead, changing both uses.
12501    let Some(owner_name) = code_unit
12502        .short_name()
12503        .rsplit_once('.')
12504        .map(|(owner, _)| owner)
12505    else {
12506        return fallback.map(|unit| ResolvedTypeOwner {
12507            unit,
12508            is_forward_declaration: false,
12509        });
12510    };
12511    let owner_fqn = if code_unit.package_name().is_empty() {
12512        owner_name.to_string()
12513    } else {
12514        format!("{}.{}", code_unit.package_name(), owner_name)
12515    };
12516    match same_source_owner(analyzer, code_unit, &owner_fqn, owner_name) {
12517        DirectOwnerResolution::UniqueFull(owner) => {
12518            return Some(ResolvedTypeOwner {
12519                unit: owner,
12520                is_forward_declaration: false,
12521            });
12522        }
12523        DirectOwnerResolution::Ambiguous => return None,
12524        DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
12525    }
12526    match directly_included_owner(analyzer, code_unit, &owner_fqn, owner_name) {
12527        DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
12528            unit: owner,
12529            is_forward_declaration: false,
12530        }),
12531        DirectOwnerResolution::Ambiguous => None,
12532        DirectOwnerResolution::ForwardsOnly(forwards) => {
12533            match visible_full_cpp_owner(analyzer, code_unit, &owner_fqn, owner_name) {
12534                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
12535                    unit: owner,
12536                    is_forward_declaration: false,
12537                }),
12538                FullOwnerResolution::None => {
12539                    unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
12540                        unit,
12541                        is_forward_declaration: true,
12542                    })
12543                }
12544                FullOwnerResolution::Ambiguous => None,
12545            }
12546        }
12547        DirectOwnerResolution::None => {
12548            match visible_full_cpp_owner(analyzer, code_unit, &owner_fqn, owner_name) {
12549                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
12550                    unit: owner,
12551                    is_forward_declaration: false,
12552                }),
12553                FullOwnerResolution::Ambiguous => None,
12554                FullOwnerResolution::None => fallback
12555                    .filter(|parent| {
12556                        parent.source() == code_unit.source()
12557                            && parent.short_name() == owner_name
12558                            && parent.package_name() == code_unit.package_name()
12559                            && (!parent.is_class()
12560                                || cpp_class_declaration_strength(analyzer, parent)
12561                                    == CppClassDeclarationStrength::Full)
12562                    })
12563                    .map(|unit| ResolvedTypeOwner {
12564                        unit,
12565                        is_forward_declaration: false,
12566                    }),
12567            }
12568        }
12569    }
12570}
12571
12572fn exact_structural_type_parent(
12573    analyzer: &CppGraphSource<'_>,
12574    code_unit: &CodeUnit,
12575) -> Option<CodeUnit> {
12576    if !code_unit.is_function() && !code_unit.is_field() {
12577        return None;
12578    }
12579    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
12580    let cpp = analyzer.cpp?;
12581    let parent = cpp.structural_parent_of(code_unit)?;
12582    (!parent.is_module()
12583        && parent.source() == code_unit.source()
12584        && parent.package_name() == code_unit.package_name()
12585        && parent.short_name() == encoded_owner)
12586        .then_some(parent)
12587}
12588
12589fn same_source_owner(
12590    analyzer: &CppGraphSource<'_>,
12591    code_unit: &CodeUnit,
12592    owner_fqn: &str,
12593    owner_name: &str,
12594) -> DirectOwnerResolution {
12595    let candidates = analyzer
12596        .global_usage_definition_index()
12597        .fqn(owner_fqn)
12598        .into_iter()
12599        .filter(|candidate| {
12600            candidate.is_class()
12601                && candidate.source() == code_unit.source()
12602                && candidate.short_name() == owner_name
12603                && candidate.package_name() == code_unit.package_name()
12604        })
12605        .collect::<Vec<_>>();
12606    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
12607    classify_direct_owner_candidates(analyzer, candidates.into_iter())
12608}
12609
12610fn visible_full_cpp_owner(
12611    analyzer: &CppGraphSource<'_>,
12612    code_unit: &CodeUnit,
12613    owner_fqn: &str,
12614    owner_name: &str,
12615) -> FullOwnerResolution {
12616    let Some(cpp) = analyzer.cpp else {
12617        return FullOwnerResolution::None;
12618    };
12619    let mut visible_files = HashSet::default();
12620    collect_include_closure(
12621        analyzer,
12622        cpp.include_target_index(),
12623        code_unit.source(),
12624        &mut visible_files,
12625        None,
12626    );
12627    let candidates = analyzer
12628        .global_usage_definition_index()
12629        .fqn(owner_fqn)
12630        .into_iter()
12631        .filter(|candidate| {
12632            candidate.is_class()
12633                && candidate.short_name() == owner_name
12634                && candidate.package_name() == code_unit.package_name()
12635                && visible_files.contains(candidate.source())
12636        })
12637        .collect::<Vec<_>>();
12638    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
12639    let mut full_definition = None;
12640    for candidate in candidates {
12641        match cpp_class_declaration_strength(analyzer, candidate) {
12642            CppClassDeclarationStrength::Full if full_definition.is_some() => {
12643                return FullOwnerResolution::Ambiguous;
12644            }
12645            CppClassDeclarationStrength::Full => full_definition = Some(candidate.clone()),
12646            CppClassDeclarationStrength::Forward => {}
12647            CppClassDeclarationStrength::Unknown => return FullOwnerResolution::Ambiguous,
12648        }
12649    }
12650    full_definition.map_or(FullOwnerResolution::None, FullOwnerResolution::Unique)
12651}
12652
12653pub enum DirectOwnerResolution {
12654    None,
12655    ForwardsOnly(Vec<CodeUnit>),
12656    UniqueFull(CodeUnit),
12657    Ambiguous,
12658}
12659
12660enum FullOwnerResolution {
12661    None,
12662    Unique(CodeUnit),
12663    Ambiguous,
12664}
12665
12666#[derive(Clone, Copy, PartialEq, Eq)]
12667pub enum CppClassDeclarationStrength {
12668    Full,
12669    Forward,
12670    Unknown,
12671}
12672
12673fn directly_included_owner(
12674    analyzer: &CppGraphSource<'_>,
12675    code_unit: &CodeUnit,
12676    owner_fqn: &str,
12677    owner_name: &str,
12678) -> DirectOwnerResolution {
12679    let Some(cpp) = analyzer.cpp else {
12680        return DirectOwnerResolution::None;
12681    };
12682    let imports = analyzer.import_statements(code_unit.source());
12683    let direct_includes: HashSet<ProjectFile> = cpp_include_paths(&imports)
12684        .into_iter()
12685        .flat_map(|include| {
12686            resolve_include_targets_with_index(
12687                code_unit.source(),
12688                &include,
12689                cpp.include_target_index(),
12690            )
12691        })
12692        .collect();
12693    let candidates = analyzer
12694        .global_usage_definition_index()
12695        .fqn(owner_fqn)
12696        .into_iter()
12697        .filter(|candidate| {
12698            candidate.is_class()
12699                && candidate.short_name() == owner_name
12700                && candidate.package_name() == code_unit.package_name()
12701                && direct_includes.contains(candidate.source())
12702        })
12703        .collect::<Vec<_>>();
12704    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
12705    classify_direct_owner_candidates(analyzer, candidates.into_iter())
12706}
12707
12708fn prefer_member_declaring_owners<'a>(
12709    analyzer: &CppGraphSource<'_>,
12710    member: &CodeUnit,
12711    candidates: Vec<&'a CodeUnit>,
12712) -> Vec<&'a CodeUnit> {
12713    let matching = candidates
12714        .iter()
12715        .copied()
12716        .filter(|owner| owner_declares_member(analyzer, owner, member))
12717        .collect::<Vec<_>>();
12718    if matching.is_empty() {
12719        candidates
12720    } else {
12721        matching
12722    }
12723}
12724
12725fn owner_declares_member(
12726    analyzer: &CppGraphSource<'_>,
12727    owner: &CodeUnit,
12728    member: &CodeUnit,
12729) -> bool {
12730    analyzer.direct_children(owner).into_iter().any(|child| {
12731        child.kind() == member.kind()
12732            && child.identifier() == member.identifier()
12733            && child.signature() == member.signature()
12734    })
12735}
12736
12737fn classify_direct_owner_candidates<'a>(
12738    analyzer: &CppGraphSource<'_>,
12739    candidates: impl Iterator<Item = &'a CodeUnit>,
12740) -> DirectOwnerResolution {
12741    collapse_owner_candidates(candidates.map(|candidate| {
12742        (
12743            candidate.clone(),
12744            cpp_class_declaration_strength(analyzer, candidate),
12745        )
12746    }))
12747}
12748
12749pub fn collapse_owner_candidates(
12750    candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
12751) -> DirectOwnerResolution {
12752    let mut full_definition = None;
12753    let mut forwards = Vec::new();
12754    for (candidate, strength) in candidates {
12755        match strength {
12756            CppClassDeclarationStrength::Full if full_definition.is_some() => {
12757                return DirectOwnerResolution::Ambiguous;
12758            }
12759            CppClassDeclarationStrength::Full => full_definition = Some(candidate),
12760            CppClassDeclarationStrength::Forward => forwards.push(candidate),
12761            CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
12762        }
12763    }
12764    if let Some(owner) = full_definition {
12765        DirectOwnerResolution::UniqueFull(owner)
12766    } else if !forwards.is_empty() {
12767        DirectOwnerResolution::ForwardsOnly(forwards)
12768    } else {
12769        DirectOwnerResolution::None
12770    }
12771}
12772
12773#[cfg(any(test, feature = "test-support"))]
12774pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
12775    unique_logical_forward_owner(forwards)
12776}
12777
12778fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
12779    let first = forwards.pop()?;
12780    forwards
12781        .iter()
12782        .all(|forward| same_logical_symbol(forward, &first))
12783        .then_some(first)
12784}
12785
12786pub fn cpp_class_declaration_strength(
12787    analyzer: &CppGraphSource<'_>,
12788    candidate: &CodeUnit,
12789) -> CppClassDeclarationStrength {
12790    if let Some(prepared) = analyzer
12791        .cpp
12792        .and_then(|cpp| cpp.prepared_syntax(candidate.source()))
12793    {
12794        return cpp_class_declaration_strength_in_tree(
12795            analyzer,
12796            candidate,
12797            prepared.source(),
12798            prepared.tree().root_node(),
12799        );
12800    }
12801    let Some(source) = analyzer.indexed_source(candidate.source()) else {
12802        return CppClassDeclarationStrength::Unknown;
12803    };
12804    #[cfg(any(test, feature = "test-support"))]
12805    if let Some(cpp) = analyzer.cpp {
12806        cpp.record_cpp_class_strength_parse_for_test();
12807    }
12808    let mut parser = Parser::new();
12809    if parser
12810        .set_language(&tree_sitter_cpp::LANGUAGE.into())
12811        .is_err()
12812    {
12813        return CppClassDeclarationStrength::Unknown;
12814    }
12815    let Some(tree) = parser.parse(&source, None) else {
12816        return CppClassDeclarationStrength::Unknown;
12817    };
12818    cpp_class_declaration_strength_in_tree(analyzer, candidate, &source, tree.root_node())
12819}
12820
12821fn cpp_class_declaration_strength_in_tree(
12822    analyzer: &CppGraphSource<'_>,
12823    candidate: &CodeUnit,
12824    source: &str,
12825    root: Node<'_>,
12826) -> CppClassDeclarationStrength {
12827    let ranges = analyzer.ranges(candidate);
12828    let mut saw_forward = false;
12829    for range in ranges {
12830        let mut stack = vec![root];
12831        while let Some(node) = stack.pop() {
12832            if node.start_byte() == range.start_byte
12833                && recovered_fragmented_plain_class_has_body(
12834                    node,
12835                    source,
12836                    candidate.identifier(),
12837                    &range,
12838                )
12839            {
12840                return CppClassDeclarationStrength::Full;
12841            }
12842            if node.start_byte() > range.start_byte || node.end_byte() < range.start_byte {
12843                continue;
12844            }
12845            if node.start_byte() == range.start_byte && node.end_byte() == range.end_byte {
12846                if matches!(
12847                    node.kind(),
12848                    "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
12849                ) {
12850                    if cpp_class_node_has_body(node) {
12851                        return CppClassDeclarationStrength::Full;
12852                    }
12853                    saw_forward = true;
12854                } else if let Some(has_body) =
12855                    recovered_exported_class_has_body(node, source, candidate.identifier())
12856                {
12857                    if has_body {
12858                        return CppClassDeclarationStrength::Full;
12859                    }
12860                    saw_forward = true;
12861                }
12862            }
12863            let mut cursor = node.walk();
12864            stack.extend(node.named_children(&mut cursor));
12865        }
12866    }
12867    if saw_forward {
12868        CppClassDeclarationStrength::Forward
12869    } else {
12870        CppClassDeclarationStrength::Unknown
12871    }
12872}
12873
12874fn cpp_class_node_has_body(node: Node<'_>) -> bool {
12875    node.child_by_field_name("body").is_some() || {
12876        let mut cursor = node.walk();
12877        node.named_children(&mut cursor).any(|child| {
12878            matches!(
12879                child.kind(),
12880                "declaration_list" | "field_declaration_list" | "enumerator_list"
12881            )
12882        })
12883    }
12884}
12885
12886pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
12887    // fqname-M4: `owner_name` is used both bare and manually recombined with
12888    // `package_name()` below (same package-less short_name owner shape as
12889    // `precise_parent_resolution` above); `default_parent_fq_name` would
12890    // render the package-qualified owner instead, changing both uses.
12891    let owner_name = code_unit
12892        .short_name()
12893        .rsplit_once('.')
12894        .map(|(owner, _)| owner)?;
12895    let owner_fqn = if code_unit.package_name().is_empty() {
12896        owner_name.to_string()
12897    } else {
12898        format!("{}.{}", code_unit.package_name(), owner_name)
12899    };
12900    ctx.analyzer
12901        .global_usage_definition_index()
12902        .fqn(&owner_fqn)
12903        .into_iter()
12904        .find(|candidate| {
12905            candidate.is_class()
12906                && ctx.visibility.is_visible(ctx.file, candidate)
12907                && candidate.short_name() == owner_name
12908                && candidate.package_name() == code_unit.package_name()
12909        })
12910        .cloned()
12911}
12912
12913pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
12914    left.kind() == right.kind()
12915        && left.fq_name() == right.fq_name()
12916        && left.signature() == right.signature()
12917        && left.source() == right.source()
12918}
12919
12920pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
12921    same_symbol(left, right) || same_logical_symbol(left, right)
12922}
12923
12924pub fn same_visible_global_field_symbol(
12925    analyzer: &CppGraphSource<'_>,
12926    internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
12927    left: &CodeUnit,
12928    right: &CodeUnit,
12929) -> bool {
12930    if same_symbol(left, right) {
12931        return true;
12932    }
12933    if !same_logical_symbol(left, right) {
12934        return false;
12935    }
12936    if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
12937        || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
12938    {
12939        left.source() == right.source()
12940    } else {
12941        true
12942    }
12943}
12944
12945fn cpp_global_field_has_internal_linkage_cached(
12946    analyzer: &CppGraphSource<'_>,
12947    cache: &mut HashMap<CodeUnit, bool>,
12948    candidate: &CodeUnit,
12949) -> bool {
12950    if let Some(internal) = cache.get(candidate) {
12951        return *internal;
12952    }
12953    #[cfg(any(test, feature = "test-support"))]
12954    note_cpp_global_field_internal_linkage_classification_for_test();
12955    let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
12956    cache.insert(candidate.clone(), internal);
12957    internal
12958}
12959
12960#[cfg(any(test, feature = "test-support"))]
12961thread_local! {
12962    static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
12963}
12964
12965#[cfg(any(test, feature = "test-support"))]
12966fn note_cpp_global_field_internal_linkage_classification_for_test() {
12967    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
12968        count.set(count.get() + 1);
12969    });
12970}
12971
12972#[cfg(any(test, feature = "test-support"))]
12973pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
12974    body: impl FnOnce() -> T,
12975) -> (T, usize) {
12976    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
12977        count.set(0);
12978        let result = body();
12979        let observed = count.get();
12980        count.set(0);
12981        (result, observed)
12982    })
12983}
12984
12985pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
12986    left.kind() == right.kind()
12987        && left.fq_name() == right.fq_name()
12988        && left.signature() == right.signature()
12989}
12990
12991pub fn cpp_global_field_has_internal_linkage(
12992    analyzer: &CppGraphSource<'_>,
12993    candidate: &CodeUnit,
12994) -> bool {
12995    if !candidate.is_field() || candidate.short_name().contains('.') {
12996        return false;
12997    }
12998    let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
12999        return false;
13000    };
13001    match local_linkage {
13002        CppFieldLinkage::Internal => true,
13003        CppFieldLinkage::External => false,
13004        CppFieldLinkage::InternalUnlessExternalPeer => {
13005            !cpp_global_field_linkage_peers(analyzer, candidate)
13006                .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, peer))
13007                .any(|linkage| matches!(linkage, CppFieldLinkage::External))
13008        }
13009    }
13010}
13011
13012fn cpp_global_field_linkage_peers<'a>(
13013    analyzer: &CppGraphSource<'a>,
13014    candidate: &'a CodeUnit,
13015) -> impl Iterator<Item = &'a CodeUnit> + 'a {
13016    // These peers are returned to the caller, so they must borrow the analyzer
13017    // for `'a` rather than a handle that dies with this call. `fqn` reads the
13018    // shards directly for exactly that reason.
13019    let fq_name = candidate.fq_name();
13020    analyzer
13021        .global_usage_definition_index()
13022        .fqn(&fq_name)
13023        .into_iter()
13024        .filter(move |peer| {
13025            if *peer == candidate {
13026                return false;
13027            }
13028            #[cfg(any(test, feature = "test-support"))]
13029            note_cpp_global_field_linkage_peer_inspection_for_test();
13030            same_logical_symbol(peer, candidate)
13031        })
13032}
13033
13034#[cfg(any(test, feature = "test-support"))]
13035thread_local! {
13036    static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
13037}
13038
13039#[cfg(any(test, feature = "test-support"))]
13040fn note_cpp_global_field_linkage_peer_inspection_for_test() {
13041    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
13042        count.set(count.get() + 1);
13043    });
13044}
13045
13046#[cfg(any(test, feature = "test-support"))]
13047pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
13048    body: impl FnOnce() -> T,
13049) -> (T, usize) {
13050    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
13051        count.set(0);
13052        let result = body();
13053        let observed = count.get();
13054        count.set(0);
13055        (result, observed)
13056    })
13057}
13058
13059fn cpp_global_field_declaration_linkage(
13060    analyzer: &CppGraphSource<'_>,
13061    candidate: &CodeUnit,
13062) -> Option<CppFieldLinkage> {
13063    if let Some(linkage) = analyzer.cpp_field_linkage(candidate) {
13064        return Some(linkage);
13065    }
13066    let cpp = analyzer.cpp?;
13067    if let Some(prepared) = cpp.prepared_syntax(candidate.source()) {
13068        return cpp_global_field_declaration_linkage_in_tree(
13069            analyzer,
13070            candidate,
13071            prepared.source(),
13072            prepared.tree().root_node(),
13073        );
13074    }
13075    let source = analyzer.indexed_source(candidate.source())?;
13076    let mut parser = Parser::new();
13077    if parser
13078        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13079        .is_err()
13080    {
13081        return None;
13082    }
13083    let tree = parser.parse(&source, None)?;
13084    cpp_global_field_declaration_linkage_in_tree(analyzer, candidate, &source, tree.root_node())
13085}
13086
13087fn cpp_global_field_declaration_linkage_in_tree(
13088    analyzer: &CppGraphSource<'_>,
13089    candidate: &CodeUnit,
13090    source: &str,
13091    root: Node<'_>,
13092) -> Option<CppFieldLinkage> {
13093    analyzer.ranges(candidate).iter().find_map(|range| {
13094        node_for_exact_range(root, range)
13095            .and_then(enclosing_cpp_field_declaration)
13096            .map(|declaration| cpp_field_declaration_linkage(declaration, source))
13097    })
13098}
13099
13100fn enclosing_cpp_field_declaration(mut node: Node<'_>) -> Option<Node<'_>> {
13101    loop {
13102        if matches!(node.kind(), "declaration" | "field_declaration") {
13103            return Some(node);
13104        }
13105        node = node.parent()?;
13106    }
13107}
13108
13109#[cfg(test)]
13110mod tests {
13111    use super::*;
13112
13113    #[test]
13114    fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
13115        let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
13116        assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
13117        assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
13118        assert!(indexed_namespace_path_is_recoverable(
13119            &["cache".to_string()],
13120            &indexed,
13121            1,
13122        ));
13123    }
13124
13125    #[test]
13126    fn sort_lookup_units_totally_orders_every_identity_field() {
13127        let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
13128        let base = CodeUnit::with_signature(
13129            file.clone(),
13130            CodeUnitType::Function,
13131            "scope",
13132            "value",
13133            Some("()".to_string()),
13134            false,
13135        );
13136        let different_kind = CodeUnit::with_signature(
13137            file.clone(),
13138            CodeUnitType::Field,
13139            "scope",
13140            "value",
13141            Some("()".to_string()),
13142            false,
13143        );
13144        let synthetic = base.with_synthetic(true);
13145
13146        let interner = segment_interner();
13147        let mut member_fq = FqName::new();
13148        member_fq.push(interner.intern("scope", SegmentKind::Package));
13149        member_fq.push(interner.intern("value", SegmentKind::Member));
13150        let different_package_boundary = CodeUnit::from_fq(
13151            file.clone(),
13152            CodeUnitType::Function,
13153            member_fq,
13154            0,
13155            Some("()".to_string()),
13156            false,
13157        );
13158
13159        let mut unknown_fq = FqName::new();
13160        unknown_fq.push(interner.intern("scope", SegmentKind::Package));
13161        unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
13162        let different_segment_kind = CodeUnit::from_fq(
13163            file,
13164            CodeUnitType::Function,
13165            unknown_fq,
13166            1,
13167            Some("()".to_string()),
13168            false,
13169        );
13170
13171        let input = vec![
13172            base,
13173            different_kind,
13174            synthetic,
13175            different_package_boundary,
13176            different_segment_kind,
13177        ];
13178        let mut expected = input.clone();
13179        sort_lookup_units(&mut expected);
13180        assert!(expected.windows(2).all(|pair| {
13181            let mut ordered = pair.to_vec();
13182            sort_lookup_units(&mut ordered);
13183            ordered == pair && pair[0] != pair[1]
13184        }));
13185
13186        let mut reversed = input.clone();
13187        reversed.reverse();
13188        sort_lookup_units(&mut reversed);
13189        assert_eq!(reversed, expected);
13190
13191        let mut rotated = input;
13192        rotated.rotate_left(2);
13193        sort_lookup_units(&mut rotated);
13194        assert_eq!(rotated, expected);
13195    }
13196
13197    #[test]
13198    fn displaced_preprocessor_terminator_bounds_the_real_guard() {
13199        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";
13200        let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
13201        let parse = |source: &str| {
13202            let mut parser = Parser::new();
13203            parser
13204                .set_language(&tree_sitter_cpp::LANGUAGE.into())
13205                .expect("C++ grammar");
13206            parser.parse(source, None).expect("fixture tree")
13207        };
13208
13209        let tree = parse(damaged);
13210        let root = tree.root_node();
13211        let target = damaged.find("target").expect("target byte");
13212        let declaration = root
13213            .descendant_for_byte_range(target, target + "target".len())
13214            .and_then(|mut node| {
13215                loop {
13216                    if node.kind() == "declaration" {
13217                        break Some(node);
13218                    }
13219                    node = node.parent()?;
13220                }
13221            })
13222            .expect("declaration after the displaced terminator");
13223        let conditional = declaration
13224            .parent()
13225            .filter(|node| node.kind() == "preproc_ifdef")
13226            .expect("damaged inner conditional");
13227        let outer = conditional
13228            .parent()
13229            .filter(|node| node.kind() == "preproc_ifdef")
13230            .expect("ordinary outer include guard");
13231        let terminator = cpp_displaced_preprocessor_terminator(conditional)
13232            .expect("structured displaced #endif");
13233        assert_eq!(node_text(terminator, damaged), "#endif");
13234        assert!(terminator.end_byte() <= declaration.start_byte());
13235        assert!(!preprocessor_conditional_contains_descendant(
13236            conditional,
13237            declaration
13238        ));
13239        assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
13240        assert!(preprocessor_conditional_contains_descendant(
13241            outer,
13242            declaration
13243        ));
13244
13245        let tree = parse(guarded);
13246        let conditional = tree
13247            .root_node()
13248            .named_child(0)
13249            .filter(|node| node.kind() == "preproc_ifdef")
13250            .expect("ordinary conditional");
13251        let declaration = conditional
13252            .named_children(&mut conditional.walk())
13253            .find(|node| node.kind() == "declaration")
13254            .expect("guarded declaration");
13255        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
13256        assert!(preprocessor_conditional_contains_descendant(
13257            conditional,
13258            declaration
13259        ));
13260
13261        let damaged_alternative = format!(
13262            "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
13263            "UNUSED(value)\n".repeat(64)
13264        );
13265        let tree = parse(&damaged_alternative);
13266        let conditional = tree
13267            .root_node()
13268            .named_child(0)
13269            .filter(|node| node.kind() == "preproc_ifdef")
13270            .expect("outer conditional with an alternative");
13271        assert!(conditional.has_error());
13272        assert!(conditional.child_by_field_name("alternative").is_some());
13273        assert!(
13274            conditional
13275                .child(conditional.child_count() - 1)
13276                .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
13277        );
13278        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
13279
13280        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";
13281        let tree = parse(split_declaration);
13282        let root = tree.root_node();
13283        let conditional = root
13284            .named_children(&mut root.walk())
13285            .find(|node| node.kind() == "preproc_ifdef" && node.start_position().row == 3)
13286            .expect("split declaration conditional");
13287        let target = split_declaration
13288            .find("static int target")
13289            .expect("target byte");
13290        let boundary =
13291            cpp_displaced_preprocessor_boundary(conditional).expect("split declaration boundary");
13292        assert!(boundary.end_byte <= target, "{boundary:?}");
13293        assert_eq!(boundary.end_line, 9, "{boundary:?}");
13294        let target_node = root
13295            .descendant_for_byte_range(target, target + "static".len())
13296            .expect("target node");
13297        assert!(!preprocessor_conditional_contains_descendant(
13298            conditional,
13299            target_node
13300        ));
13301    }
13302
13303    #[test]
13304    fn fragmented_reference_guard_is_recovered() {
13305        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";
13306        let mut parser = Parser::new();
13307        parser
13308            .set_language(&tree_sitter_cpp::LANGUAGE.into())
13309            .expect("C++ grammar");
13310        let tree = parser.parse(source, None).expect("fixture tree");
13311        let start = source.rfind("helper").expect("reference byte");
13312        let node = tree
13313            .root_node()
13314            .descendant_for_byte_range(start, start + "helper".len())
13315            .expect("reference node");
13316        let mut expected = HashSet::default();
13317        expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
13318            vec![
13319                BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
13320                BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
13321            ],
13322        )));
13323        assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
13324    }
13325
13326    #[test]
13327    fn boolean_guard_normalization_proves_equivalence_and_implication() {
13328        let windows = BooleanGuardExpression::Defined("WIN32".to_string());
13329        let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
13330        let negated_windows_branch =
13331            BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
13332        let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
13333        assert_eq!(negated_windows_branch, portable);
13334
13335        let missing_a = BooleanGuardExpression::Undefined("A".to_string());
13336        let missing_b = BooleanGuardExpression::Undefined("B".to_string());
13337        let missing_c = BooleanGuardExpression::Undefined("C".to_string());
13338        let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
13339        let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
13340        assert!(fallback_branch.implies(&fallback_declaration));
13341        assert!(!fallback_declaration.implies(&fallback_branch));
13342    }
13343
13344    #[test]
13345    fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
13346        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";
13347        let mut parser = Parser::new();
13348        parser
13349            .set_language(&tree_sitter_cpp::LANGUAGE.into())
13350            .expect("C++ grammar");
13351        let tree = parser.parse(source, None).expect("fixture tree");
13352        let root = tree.root_node();
13353        let call = |marker: &str| {
13354            let start = source.find(marker).expect("call marker");
13355            let mut node = root
13356                .descendant_for_byte_range(start, start + "helper".len())
13357                .expect("call name node");
13358            loop {
13359                if node.kind() == "call_expression" {
13360                    break node;
13361                }
13362                node = node.parent().expect("call expression ancestor");
13363            }
13364        };
13365        let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
13366        let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
13367        let keyword_call = call("helper(NULL, template); /* bound */");
13368        let keyword_arguments = keyword_call
13369            .child_by_field_name("arguments")
13370            .expect("keyword argument list");
13371        assert_eq!(
13372            recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
13373            1
13374        );
13375        assert_eq!(
13376            recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
13377            0
13378        );
13379
13380        let unbound_call = call("helper(NULL, template); /* unbound */");
13381        let unbound_arguments = unbound_call
13382            .child_by_field_name("arguments")
13383            .expect("unbound argument list");
13384        assert_eq!(
13385            recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
13386            0
13387        );
13388    }
13389
13390    fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
13391        let mut parser = Parser::new();
13392        parser
13393            .set_language(&tree_sitter_cpp::LANGUAGE.into())
13394            .expect("C++ grammar");
13395        let tree = parser.parse(source, None).expect("C++ fixture tree");
13396        let mut stack = vec![tree.root_node()];
13397        while let Some(node) = stack.pop() {
13398            if node.kind() == "enum_specifier" {
13399                return flattened_macro_namespace_components(node, source);
13400            }
13401            let mut cursor = node.walk();
13402            let children = node.named_children(&mut cursor).collect::<Vec<_>>();
13403            stack.extend(children.into_iter().rev());
13404        }
13405        None
13406    }
13407
13408    #[test]
13409    fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
13410        let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
13411namespace detail
13412{
13413enum class value_t { null };
13414}
13415NLOHMANN_JSON_NAMESPACE_END
13416NLOHMANN_JSON_NAMESPACE_BEGIN
13417namespace next
13418{
13419struct next_type {};
13420}
13421NLOHMANN_JSON_NAMESPACE_END
13422"#;
13423        assert_eq!(
13424            first_enum_flattened_namespace(complete),
13425            Some(vec!["detail".to_string()])
13426        );
13427
13428        let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
13429        assert_eq!(
13430            first_enum_flattened_namespace(&stale_end),
13431            Some(vec!["detail".to_string()]),
13432            "a stale end marker before the begin marker must not replace the intended namespace"
13433        );
13434
13435        let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
13436namespace detail
13437{
13438enum class value_t { null };
13439}
13440struct next_type {};
13441"#;
13442        assert_eq!(first_enum_flattened_namespace(incomplete), None);
13443    }
13444}