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};
4#[cfg(test)]
5use crate::declarations::cpp_displaced_preprocessor_terminator;
6use crate::declarations::{
7    cpp_displaced_preprocessor_boundary, cpp_export_macro_token, cpp_field_declaration_linkage,
8    cpp_template_term, node_text, normalize_cpp_whitespace, recovered_exported_class_has_body,
9    recovered_fragmented_plain_class_has_body,
10};
11use crate::graph::CppGraphSource;
12use crate::graph::extractor::ScanCtx;
13use crate::graph_support::CppSource;
14use crate::imports::{
15    IncludeTargetIndex, include_paths as cpp_include_paths, resolve_include_targets_with_index,
16};
17use brokk_bifrost_core::analyzer::fq_name::{FqName, SegmentKind, segment_interner};
18use brokk_bifrost_core::analyzer::model::{
19    CallableArity, CodeUnitType, CppFieldLinkage, CppTemplateExpression, CppTemplateMetadata,
20    CppTemplateParameterMetadata, CppTemplateTerm,
21};
22use brokk_bifrost_core::analyzer::pool_memo::PoolSafeMemo;
23use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
24use brokk_bifrost_core::analyzer::tree_walk::node_for_exact_range;
25use brokk_bifrost_core::analyzer::usages::common::same_node;
26use brokk_bifrost_core::analyzer::usages::local_inference::LocalInferenceEngine;
27use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
28use brokk_bifrost_core::cancellation::CancellationToken;
29use brokk_bifrost_core::hash::{HashMap, HashSet};
30use std::borrow::Cow;
31#[cfg(any(test, feature = "test-support"))]
32use std::cell::Cell;
33use std::cell::OnceCell;
34use std::cmp::Ordering as CmpOrdering;
35use std::collections::BTreeSet;
36use std::hash::Hash;
37#[cfg(any(test, feature = "test-support"))]
38use std::sync::atomic::{AtomicUsize, Ordering};
39use std::sync::{Arc, Mutex, OnceLock, RwLock};
40use std::thread::ThreadId;
41use tree_sitter::{Node, Parser, Tree};
42
43#[derive(Clone, Copy, PartialEq, Eq)]
44pub enum TargetKind {
45    Type,
46    Constructor,
47    FreeFunction,
48    Method,
49    GlobalField,
50    MemberField,
51    Macro,
52}
53
54pub enum LexicalTypeResolution {
55    Resolved {
56        unit: CodeUnit,
57        components: Vec<String>,
58        candidates: Vec<CodeUnit>,
59    },
60    Ambiguous,
61    Missing,
62}
63
64#[derive(Clone, Copy)]
65enum TypeCandidateResolution<'a> {
66    Canonical,
67    PreserveAlias,
68    PreserveTarget(&'a CodeUnit),
69}
70
71/// Why a name did not reduce to one indexed type declaration.
72///
73/// The two answers are not interchangeable. `Ambiguous` means the index holds
74/// several declarations and the caller must choose; `Unresolvable` means the
75/// index holds none, which is a boundary the workspace cannot see past. A
76/// `using`/`typedef` alias to a template parameter or to a standard-library
77/// type is unresolvable, and reporting it as ambiguity produced an `ambiguous`
78/// answer with an empty candidate list (#1828).
79#[derive(Clone, Copy, Debug, PartialEq, Eq)]
80enum TypeCandidateFailure {
81    Ambiguous,
82    Unresolvable,
83}
84
85impl TypeCandidateFailure {
86    fn lexical_resolution(self) -> LexicalTypeResolution {
87        match self {
88            Self::Ambiguous => LexicalTypeResolution::Ambiguous,
89            Self::Unresolvable => LexicalTypeResolution::Missing,
90        }
91    }
92}
93
94pub enum LexicalCallableValueResolution {
95    Type(CodeUnit),
96    FreeFunction(CodeUnit),
97    Ambiguous,
98    Missing,
99}
100
101pub enum UsingEnumMemberResolution {
102    Resolved { owner: CodeUnit, member: CodeUnit },
103    Ambiguous,
104    Missing,
105}
106
107pub enum NamespaceValueResolution {
108    Resolved,
109    Ambiguous,
110    Missing,
111}
112
113#[derive(Clone, Debug, PartialEq, Eq)]
114pub enum OrdinaryMacroReferenceResolution {
115    Resolved(CodeUnit),
116    Ambiguous,
117    Missing,
118}
119
120#[derive(Clone, Debug, PartialEq, Eq)]
121pub enum RecoveredCReferenceRanges {
122    Complete(Vec<Range>),
123    LimitExceeded,
124}
125
126pub fn resolve_namespace_value(
127    analyzer: &CppGraphSource<'_>,
128    visibility: &VisibilityIndex<'_>,
129    file: &ProjectFile,
130    namespace: &str,
131    name: &str,
132    before_byte: usize,
133) -> NamespaceValueResolution {
134    let mut matches = Vec::new();
135    for candidate in visibility.visible_identifier_candidates(file, name) {
136        if type_owner_of(analyzer, candidate).is_some()
137            || candidate.package_name() != namespace
138            || (candidate.source() == file
139                && !analyzer
140                    .ranges(candidate)
141                    .iter()
142                    .any(|range| range.start_byte < before_byte))
143            || matches
144                .iter()
145                .any(|existing| same_visible_symbol(existing, candidate))
146        {
147            continue;
148        }
149        matches.push(candidate.clone());
150        if matches.len() > 1 {
151            return NamespaceValueResolution::Ambiguous;
152        }
153    }
154    matches
155        .pop()
156        .map(|_| NamespaceValueResolution::Resolved)
157        .unwrap_or(NamespaceValueResolution::Missing)
158}
159
160pub(crate) struct ScopedUsingEnumOwners {
161    scopes: Vec<Vec<CodeUnit>>,
162}
163
164/// Same-file class and namespace imports collected by the targeted scanner's AST prepass.
165/// Cross-file and inherited class imports are deliberately not inferred without persisted
166/// evidence; a missing imported enumerator therefore remains unproven rather than being
167/// misresolved.
168pub(crate) struct SemanticUsingEnumOwners {
169    class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
170    namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
171}
172
173pub(crate) enum SemanticUsingEnumMemberResolution {
174    Class(UsingEnumMemberResolution),
175    Namespace(UsingEnumMemberResolution),
176    Missing,
177}
178
179impl SemanticUsingEnumOwners {
180    pub(crate) fn new() -> Self {
181        Self {
182            class_imports: HashMap::default(),
183            namespace_imports: HashMap::default(),
184        }
185    }
186
187    pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
188        let imports = self.class_imports.entry(class).or_default();
189        if !imports
190            .iter()
191            .any(|existing| same_visible_symbol(existing, &enum_owner))
192        {
193            imports.push(enum_owner);
194        }
195    }
196
197    pub fn import_namespace(
198        &mut self,
199        namespace: Vec<String>,
200        declaration_byte: usize,
201        enum_owner: CodeUnit,
202    ) {
203        let imports = self.namespace_imports.entry(namespace).or_default();
204        if !imports
205            .iter()
206            .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
207        {
208            imports.push((declaration_byte, enum_owner));
209        }
210    }
211
212    pub fn resolve_member(
213        &self,
214        visibility: &VisibilityIndex<'_>,
215        file: &ProjectFile,
216        class: Option<&CodeUnit>,
217        namespace: &[String],
218        before_byte: usize,
219        name: &str,
220    ) -> SemanticUsingEnumMemberResolution {
221        if let Some(class) = class
222            && let Some((_, imports)) = self
223                .class_imports
224                .iter()
225                .find(|(owner, _)| same_visible_symbol(owner, class))
226        {
227            let resolution =
228                resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
229            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
230                return SemanticUsingEnumMemberResolution::Class(resolution);
231            }
232        }
233        for prefix_len in (0..=namespace.len()).rev() {
234            let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
235                continue;
236            };
237            let owners = imports
238                .iter()
239                .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
240                .map(|(_, owner)| owner);
241            let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
242            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
243                return SemanticUsingEnumMemberResolution::Namespace(resolution);
244            }
245        }
246        SemanticUsingEnumMemberResolution::Missing
247    }
248}
249
250fn resolve_using_enum_member_for_owners<'a>(
251    visibility: &VisibilityIndex<'_>,
252    file: &ProjectFile,
253    owners: impl IntoIterator<Item = &'a CodeUnit>,
254    name: &str,
255) -> UsingEnumMemberResolution {
256    let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
257    for owner in owners {
258        for member in visibility.visible_members_for_owner_name(file, owner, name) {
259            if !member.is_field()
260                || matches.iter().any(|(existing_owner, existing_member)| {
261                    same_visible_symbol(existing_owner, owner)
262                        && same_visible_symbol(existing_member, member)
263                })
264            {
265                continue;
266            }
267            matches.push((owner.clone(), member.clone()));
268        }
269    }
270    match matches.len() {
271        0 => UsingEnumMemberResolution::Missing,
272        1 => {
273            let (owner, member) = matches.pop().expect("one using-enum match");
274            UsingEnumMemberResolution::Resolved { owner, member }
275        }
276        _ => UsingEnumMemberResolution::Ambiguous,
277    }
278}
279
280impl ScopedUsingEnumOwners {
281    pub(crate) fn new() -> Self {
282        Self {
283            scopes: vec![Vec::new()],
284        }
285    }
286
287    pub fn enter_scope(&mut self) {
288        self.scopes.push(Vec::new());
289    }
290
291    pub fn exit_scope(&mut self) {
292        if self.scopes.len() > 1 {
293            self.scopes.pop();
294        }
295    }
296
297    pub fn import(&mut self, owner: CodeUnit) {
298        let scope = self
299            .scopes
300            .last_mut()
301            .expect("using-enum scope stack is never empty");
302        if !scope
303            .iter()
304            .any(|existing| same_visible_symbol(existing, &owner))
305        {
306            scope.push(owner);
307        }
308    }
309
310    pub fn resolve_member(
311        &self,
312        visibility: &VisibilityIndex<'_>,
313        file: &ProjectFile,
314        name: &str,
315    ) -> UsingEnumMemberResolution {
316        for scope in self.scopes.iter().rev() {
317            let resolution =
318                resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
319            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
320                return resolution;
321            }
322        }
323        UsingEnumMemberResolution::Missing
324    }
325}
326
327#[derive(Clone)]
328pub struct TargetSpec {
329    pub target: CodeUnit,
330    pub kind: TargetKind,
331    pub owner: Option<CodeUnit>,
332    pub member_name: String,
333    pub callable_arity: Option<CallableArity>,
334    pub activated_callable_arities: Vec<ActivatedCallableArity>,
335    pub param_types: Option<Vec<String>>,
336    pub enum_owner_kind: EnumOwnerKind,
337    pub owner_is_forward_declaration: bool,
338}
339
340#[derive(Clone, Copy)]
341pub struct ActivatedCallableArity {
342    pub activation_byte: usize,
343    pub arity: CallableArity,
344}
345
346#[derive(Debug, PartialEq, Eq, Hash)]
347pub struct TypeScanKey {
348    target: LogicalSymbolKey,
349    member_name: String,
350}
351
352#[derive(Clone, Debug, PartialEq, Eq, Hash)]
353struct LogicalSymbolKey {
354    kind: CodeUnitType,
355    fq_name: String,
356    signature: Option<String>,
357}
358
359struct ResolvedTypeOwner {
360    unit: CodeUnit,
361    is_forward_declaration: bool,
362}
363
364#[derive(Clone, Copy, PartialEq, Eq)]
365pub enum EnumOwnerKind {
366    Scoped,
367    Unscoped,
368    NonEnum,
369}
370
371impl TargetSpec {
372    pub fn type_scan_key(&self) -> Option<TypeScanKey> {
373        (self.kind == TargetKind::Type).then(|| TypeScanKey {
374            target: logical_symbol_key(&self.target),
375            member_name: self.member_name.clone(),
376        })
377    }
378
379    pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
380        if target.is_class() {
381            return Some(Self::new(
382                target.clone(),
383                TargetKind::Type,
384                Some(target.clone()),
385                target.identifier().to_string(),
386                None,
387                None,
388            ));
389        }
390
391        if target.is_field() {
392            // A namespace (module) is not a receiver: a namespace-scoped constant such as
393            // `example::DefaultPrefix` is referenced unqualified from inside the namespace and
394            // qualified from outside, exactly like a global. Treating a module owner as a
395            // member-field owner makes the receiver/owner-context match reject every valid
396            // reference, so resolve it as a global field instead.
397            let owner = type_owner_of(analyzer, target);
398            let kind = if owner.is_some() {
399                TargetKind::MemberField
400            } else {
401                TargetKind::GlobalField
402            };
403            let enum_owner_kind = owner
404                .as_ref()
405                .map(|owner| classify_enum_owner(analyzer, owner))
406                .unwrap_or(EnumOwnerKind::NonEnum);
407            let mut spec = Self::new(
408                target.clone(),
409                kind,
410                owner,
411                target.identifier().to_string(),
412                None,
413                None,
414            );
415            spec.enum_owner_kind = enum_owner_kind;
416            return Some(spec);
417        }
418
419        if target.is_function() {
420            // Free functions declared inside a namespace have a module owner; that namespace is
421            // not a call receiver, so resolve them as free functions rather than methods.
422            let owner_resolution = target_type_owner_resolution(analyzer, target);
423            let owner_is_forward_declaration = owner_resolution
424                .as_ref()
425                .is_some_and(|owner| owner.is_forward_declaration);
426            let owner = owner_resolution.map(|owner| owner.unit);
427            let kind = if owner.as_ref().is_some_and(|owner| {
428                target.identifier() == owner.identifier()
429                    || analyzer
430                        .cpp
431                        .and_then(|cpp| cpp.template_metadata(owner))
432                        .is_some_and(|metadata| metadata.primary_name == target.identifier())
433            }) {
434                TargetKind::Constructor
435            } else if owner.is_some() {
436                TargetKind::Method
437            } else {
438                TargetKind::FreeFunction
439            };
440            let mut spec = Self::new(
441                target.clone(),
442                kind,
443                owner,
444                target.identifier().to_string(),
445                Some(cpp_callable_arity(analyzer, target)),
446                cpp_callable_parameter_types(analyzer, target),
447            );
448            spec.owner_is_forward_declaration = owner_is_forward_declaration;
449            return Some(spec);
450        }
451
452        if target.is_macro() {
453            return Some(Self::new(
454                target.clone(),
455                TargetKind::Macro,
456                None,
457                target.identifier().to_string(),
458                None,
459                None,
460            ));
461        }
462
463        None
464    }
465
466    pub fn with_visible_callable_arities<'a>(
467        &'a self,
468        analyzer: &CppGraphSource<'_>,
469        cpp: &dyn CppSource,
470        visibility: &VisibilityIndex<'_>,
471        file: &ProjectFile,
472        prepared: &PreparedSyntaxTree,
473    ) -> Cow<'a, Self> {
474        let macro_parameter_arity =
475            visibility.callable_parameter_macro_arity(&self.target, self.target.signature());
476        let activated_callable_arities =
477            visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
478        if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
479            return Cow::Borrowed(self);
480        }
481        let mut effective = self.clone();
482        if let Some(macro_parameter_arity) = macro_parameter_arity {
483            effective.callable_arity = Some(macro_parameter_arity);
484        }
485        effective.activated_callable_arities = activated_callable_arities;
486        Cow::Owned(effective)
487    }
488
489    pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
490        let base = self.callable_arity?;
491        Some(
492            self.activated_callable_arities
493                .iter()
494                .filter(|candidate| candidate.activation_byte <= byte)
495                .fold(base, |arity, candidate| {
496                    merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
497                }),
498        )
499    }
500
501    pub fn new(
502        target: CodeUnit,
503        kind: TargetKind,
504        owner: Option<CodeUnit>,
505        member_name: String,
506        callable_arity: Option<CallableArity>,
507        param_types: Option<Vec<String>>,
508    ) -> Self {
509        Self {
510            target,
511            kind,
512            owner,
513            member_name,
514            callable_arity,
515            activated_callable_arities: Vec::new(),
516            param_types,
517            enum_owner_kind: EnumOwnerKind::NonEnum,
518            owner_is_forward_declaration: false,
519        }
520    }
521}
522
523fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
524    LogicalSymbolKey {
525        kind: unit.kind(),
526        fq_name: unit.fq_name(),
527        signature: unit.signature().map(str::to_string),
528    }
529}
530
531fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
532    let classify = |source: &str| {
533        let source = source.trim_start();
534        if source.starts_with("enum class ") || source.starts_with("enum struct ") {
535            Some(EnumOwnerKind::Scoped)
536        } else if source.starts_with("enum ") {
537            Some(EnumOwnerKind::Unscoped)
538        } else {
539            None
540        }
541    };
542    owner
543        .signature()
544        .and_then(classify)
545        .or_else(|| {
546            analyzer
547                .get_source(owner, false)
548                .as_deref()
549                .and_then(classify)
550        })
551        .unwrap_or(EnumOwnerKind::NonEnum)
552}
553
554#[derive(Clone, PartialEq, Eq, Hash)]
555pub struct CppScanBinding {
556    pub unit: Option<CodeUnit>,
557    pub type_name: Option<String>,
558    pub indirection: i32,
559}
560
561impl CppScanBinding {
562    pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
563        Self {
564            type_name: Some(cpp_name_for(&unit)),
565            unit: Some(unit),
566            indirection,
567        }
568    }
569
570    pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
571        Self {
572            type_name: Some(type_name),
573            unit,
574            indirection,
575        }
576    }
577
578    pub fn as_arg_type(&self) -> Option<CppArgType> {
579        let name = self
580            .type_name
581            .clone()
582            .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
583        Some(CppArgType {
584            name,
585            unit: self.unit.clone(),
586            indirection: self.indirection,
587            pointee_const: false,
588        })
589    }
590}
591
592type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
593type VisibleParserAliasTargetNamesCell = Arc<OnceLock<HashMap<String, HashSet<String>>>>;
594pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
595pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
596type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
597pub type MacroEnvironmentCursorCell = Arc<Mutex<MacroEnvironmentCursor>>;
598type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
599type MacroLocalBindingTemplateCache =
600    HashMap<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>;
601
602#[derive(Clone, Default)]
603pub struct MacroEnvironment {
604    bindings: HashMap<String, MacroBinding>,
605    known_undefined_names: HashSet<String>,
606    unknown_names: bool,
607    applied_pragma_once_files: HashSet<ProjectFile>,
608    maybe_applied_pragma_once_files: HashSet<ProjectFile>,
609}
610
611#[derive(Default)]
612pub struct MacroEnvironmentCursor {
613    frontier: usize,
614    environment: Arc<MacroEnvironment>,
615}
616
617impl MacroEnvironment {
618    fn binding(&self, name: &str) -> Option<&MacroBinding> {
619        self.bindings.get(name)
620    }
621
622    fn may_bind(&self, name: &str) -> bool {
623        self.bindings.contains_key(name) || self.unknown_names
624    }
625
626    fn insert(&mut self, name: String, binding: MacroBinding) {
627        self.known_undefined_names.remove(&name);
628        self.bindings.insert(name, binding);
629    }
630
631    fn remove(&mut self, name: &str) {
632        self.bindings.remove(name);
633        self.known_undefined_names.insert(name.to_string());
634    }
635
636    fn remove_known_undefined(&mut self, name: &str) {
637        self.known_undefined_names.remove(name);
638    }
639
640    fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
641        for binding in self.bindings.values_mut() {
642            *binding = MacroBinding::uncertain_from(binding, source, byte);
643        }
644        self.known_undefined_names.clear();
645        self.unknown_names = true;
646    }
647
648    fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
649        guards.iter().all(|guard| self.guard_may_hold(guard))
650    }
651
652    fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
653        let Some(expression) = guard.as_boolean_expression() else {
654            return true;
655        };
656        self.boolean_guard_may_hold(&expression)
657    }
658
659    fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
660        match expression {
661            BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
662            BooleanGuardExpression::Undefined(name) => self
663                .bindings
664                .get(name)
665                .is_none_or(|binding| !binding.is_exact()),
666            BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
667            BooleanGuardExpression::Opaque(_)
668            | BooleanGuardExpression::NegatedOpaque(_)
669            | BooleanGuardExpression::Constant(true) => true,
670            BooleanGuardExpression::Constant(false) => false,
671            BooleanGuardExpression::All(expressions) => expressions
672                .iter()
673                .all(|expression| self.boolean_guard_may_hold(expression)),
674            BooleanGuardExpression::Any(expressions) => expressions
675                .iter()
676                .any(|expression| self.boolean_guard_may_hold(expression)),
677        }
678    }
679}
680
681#[derive(Clone)]
682pub enum EffectiveUsingTarget {
683    Ordinary {
684        name: String,
685        target_components: Vec<String>,
686        global: bool,
687    },
688    Namespace {
689        namespace_components: Vec<String>,
690        global: bool,
691    },
692}
693
694#[derive(Clone)]
695pub struct OrdinaryTypeImport {
696    pub target: EffectiveUsingTarget,
697    pub source: ProjectFile,
698    pub declaration_byte: usize,
699    pub scope_start: usize,
700    pub scope_end: usize,
701    pub scope_depth: usize,
702    pub block_scope: bool,
703    pub lexical_depth: usize,
704    pub declaration_namespace: Vec<String>,
705    pub namespace_scope: Option<Vec<String>>,
706    pub resolved_target_components: Option<Vec<String>>,
707    pub required_guards: HashSet<PreprocessorGuard>,
708}
709
710#[derive(Clone)]
711pub struct ConditionalIncludeProjection {
712    pub activation_byte: usize,
713    pub required_guards: HashSet<PreprocessorGuard>,
714}
715
716#[derive(Default)]
717pub struct SourceUsingIndex {
718    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
719    pub directives: Vec<OrdinaryTypeImport>,
720}
721
722#[derive(Default)]
723pub struct ProjectUsingIndex {
724    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
725    pub directives: Vec<OrdinaryTypeImport>,
726}
727
728type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
729
730pub struct EffectiveUsingIndex {
731    projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
732}
733
734impl EffectiveUsingIndex {
735    fn new(_root: ProjectFile) -> Self {
736        Self {
737            projected_by_name: Mutex::new(HashMap::default()),
738        }
739    }
740
741    pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
742        self.projected_by_name
743            .lock()
744            .expect("C++ effective-using projection cache poisoned")
745            .entry(name.to_string())
746            .or_default()
747            .clone()
748    }
749}
750
751pub enum OrdinaryTypeImportResolution {
752    Resolved {
753        target: CodeUnit,
754        target_components: Vec<String>,
755        lexical_depth: usize,
756        is_direct: bool,
757    },
758    Ambiguous {
759        lexical_depth: usize,
760    },
761    Missing,
762}
763
764type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
765type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
766type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
767type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
768type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
769type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
770type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
771
772/// Per-query C++ visibility facts.
773///
774/// The analyzer is *borrowed*, never cloned: `TreeSitterAnalyzer::clone` gives
775/// the clone a fresh, empty `QueryReadCache` on purpose (clones cross
776/// generations and overlays, where another generation's hydrated states would
777/// be wrong). An index that owned a clone would therefore see an inactive read
778/// cache for every `prepared_syntax` call it makes, re-reading and re-parsing
779/// the same source from the store once per candidate instead of once per query
780/// — the #1175 blow-up, where one scan re-parsed a 4.8 MB generated header
781/// tens of thousands of times.
782pub struct VisibilityIndex<'a> {
783    cpp: &'a dyn CppSource,
784    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
785    visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
786    global_field_internal_linkage: HashMap<CodeUnit, bool>,
787    visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
788    alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
789    visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
790    visible_parser_alias_target_names:
791        Mutex<HashMap<ProjectFile, VisibleParserAliasTargetNamesCell>>,
792    ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
793    project_using_index: OnceLock<ProjectUsingIndex>,
794    callable_reference_specs:
795        Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
796    include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
797    conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
798    #[cfg(any(test, feature = "test-support"))]
799    conditional_include_projection_index_build_count: AtomicUsize,
800    #[cfg(any(test, feature = "test-support"))]
801    conditional_include_projection_state_count: AtomicUsize,
802    #[cfg(any(test, feature = "test-support"))]
803    include_activation_build_count: AtomicUsize,
804    #[cfg(any(test, feature = "test-support"))]
805    using_donor_activation_count: AtomicUsize,
806    #[cfg(any(test, feature = "test-support"))]
807    using_namespace_lookup_count: AtomicUsize,
808    #[cfg(any(test, feature = "test-support"))]
809    using_name_candidate_inspection_count: AtomicUsize,
810    #[cfg(any(test, feature = "test-support"))]
811    callable_reference_spec_build_count: AtomicUsize,
812    #[cfg(any(test, feature = "test-support"))]
813    alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
814    #[cfg(any(test, feature = "test-support"))]
815    visible_parser_alias_name_set_build_count: AtomicUsize,
816    #[cfg(any(test, feature = "test-support"))]
817    visible_parser_alias_target_names_build_count: AtomicUsize,
818    field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
819    structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
820    indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
821    indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
822    precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
823    macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
824    pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
825    // A forward cursor is useful only while its caller visits one source in byte order. The
826    // authoritative differential shares this index across target workers, whose frontiers can
827    // interleave arbitrarily, so sharing one cursor per file would serialize the include replay
828    // and repeatedly reset it. Keep one bounded cursor per participating worker instead; the
829    // immutable event and parse caches above remain shared.
830    pub macro_environment_cursors:
831        Mutex<HashMap<(ProjectFile, ThreadId), MacroEnvironmentCursorCell>>,
832    macro_replacements: Mutex<MacroReplacementCache>,
833    macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
834    callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
835    #[cfg(any(test, feature = "test-support"))]
836    pub macro_replacement_parse_count: AtomicUsize,
837    #[cfg(any(test, feature = "test-support"))]
838    pub macro_event_application_count: AtomicUsize,
839    #[cfg(any(test, feature = "test-support"))]
840    pub macro_environment_copy_count: AtomicUsize,
841    cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
842    cpp_template_families: HashMap<String, Vec<CodeUnit>>,
843    #[cfg(any(test, feature = "test-support"))]
844    qualified_candidate_inspections: AtomicUsize,
845    #[cfg(any(test, feature = "test-support"))]
846    target_preserving_type_resolution_count: AtomicUsize,
847}
848
849#[derive(Clone, Debug, PartialEq, Eq, Hash)]
850pub enum PreprocessorGuard {
851    Defined(String),
852    Undefined(String),
853    Boolean(BooleanGuardExpression),
854    Expression(String),
855    NegatedExpression(String),
856    Constant(bool),
857}
858
859#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
860pub enum BooleanGuardExpression {
861    Defined(String),
862    Undefined(String),
863    Truthy(String),
864    Falsy(String),
865    Opaque(String),
866    NegatedOpaque(String),
867    All(Vec<BooleanGuardExpression>),
868    Any(Vec<BooleanGuardExpression>),
869    Constant(bool),
870}
871
872impl BooleanGuardExpression {
873    fn negated(&self) -> Self {
874        match self {
875            Self::Defined(name) => Self::Undefined(name.clone()),
876            Self::Undefined(name) => Self::Defined(name.clone()),
877            Self::Truthy(name) => Self::Falsy(name.clone()),
878            Self::Falsy(name) => Self::Truthy(name.clone()),
879            Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
880            Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
881            Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
882            Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
883            Self::Constant(value) => Self::Constant(!value),
884        }
885    }
886
887    fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
888        Self::normalized(expressions, true)
889    }
890
891    fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
892        Self::normalized(expressions, false)
893    }
894
895    fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
896        let mut normalized = Vec::new();
897        for expression in expressions {
898            match expression {
899                Self::All(nested) if conjunction => normalized.extend(nested),
900                Self::Any(nested) if !conjunction => normalized.extend(nested),
901                Self::Constant(value) if value == conjunction => {}
902                Self::Constant(value) => return Self::Constant(value),
903                expression => normalized.push(expression),
904            }
905        }
906        normalized.sort_unstable();
907        normalized.dedup();
908        match normalized.len() {
909            0 => Self::Constant(conjunction),
910            1 => normalized.pop().expect("one Boolean guard expression"),
911            _ if conjunction => Self::All(normalized),
912            _ => Self::Any(normalized),
913        }
914    }
915
916    fn implies(&self, required: &Self) -> bool {
917        if self == required
918            || matches!(self, Self::Constant(false))
919            || matches!(required, Self::Constant(true))
920        {
921            return true;
922        }
923        match self {
924            Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
925            Self::All(active) => match required {
926                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
927                _ => active.iter().any(|expression| expression.implies(required)),
928            },
929            _ => match required {
930                Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
931                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
932                _ => false,
933            },
934        }
935    }
936
937    pub fn heap_size(&self) -> usize {
938        match self {
939            Self::Defined(value)
940            | Self::Undefined(value)
941            | Self::Truthy(value)
942            | Self::Falsy(value)
943            | Self::Opaque(value)
944            | Self::NegatedOpaque(value) => value.len(),
945            Self::All(expressions) | Self::Any(expressions) => {
946                expressions
947                    .iter()
948                    .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
949                        size.saturating_add(std::mem::size_of::<Self>())
950                            .saturating_add(expression.heap_size())
951                    })
952            }
953            Self::Constant(_) => 0,
954        }
955    }
956}
957
958impl PreprocessorGuard {
959    fn as_boolean_expression(&self) -> Option<BooleanGuardExpression> {
960        match self {
961            Self::Defined(name) => Some(BooleanGuardExpression::Defined(name.clone())),
962            Self::Undefined(name) => Some(BooleanGuardExpression::Undefined(name.clone())),
963            Self::Boolean(expression) => Some(expression.clone()),
964            Self::Constant(value) => Some(BooleanGuardExpression::Constant(*value)),
965            Self::Expression(_) | Self::NegatedExpression(_) => None,
966        }
967    }
968
969    fn negated(&self) -> Self {
970        match self {
971            Self::Defined(name) => Self::Undefined(name.clone()),
972            Self::Undefined(name) => Self::Defined(name.clone()),
973            Self::Boolean(expression) => Self::Boolean(expression.negated()),
974            Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
975            Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
976            Self::Constant(value) => Self::Constant(!value),
977        }
978    }
979
980    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
981        match self {
982            Self::Defined(name) | Self::Undefined(name) => name == macro_name,
983            // The expression has already been isolated structurally by
984            // tree-sitter, but its full preprocessor semantics are outside the
985            // analyzer's guard model. Any macro mutation can therefore change
986            // its truth value.
987            Self::Boolean(_) | Self::Expression(_) | Self::NegatedExpression(_) => true,
988            Self::Constant(_) => false,
989        }
990    }
991}
992
993#[derive(Clone, PartialEq, Eq)]
994pub enum MacroDefinition {
995    Object {
996        replacement: String,
997    },
998    Function {
999        parameters: Vec<String>,
1000        replacement: String,
1001    },
1002    Unsupported,
1003}
1004
1005#[derive(Clone, Debug, PartialEq, Eq)]
1006pub enum MacroIncludeProtection {
1007    MacroGuard(String),
1008    PragmaOnce,
1009    None,
1010}
1011
1012enum ParsedMacroReplacement {
1013    Parsed { source: String, tree: Tree },
1014    Unsupported,
1015}
1016
1017#[derive(Clone)]
1018enum MacroLocalBindingTypeTemplate {
1019    Parameter(usize),
1020    Fixed(String),
1021}
1022
1023#[derive(Clone)]
1024struct MacroLocalBindingTemplate {
1025    name: String,
1026    declared_type: MacroLocalBindingTypeTemplate,
1027    pointer_depth: i32,
1028}
1029
1030/// A local declaration contributed by one structurally known function-like macro.
1031///
1032/// `type_node` points into the invocation syntax when the replacement's type
1033/// is one of the macro parameters. Consumers can therefore use their normal
1034/// lexical type resolver without parsing replacement text themselves.
1035pub struct MacroLocalBinding<'tree> {
1036    pub name: String,
1037    pub type_name: String,
1038    pub type_node: Option<Node<'tree>>,
1039    pub pointer_depth: i32,
1040}
1041
1042#[derive(Clone, PartialEq, Eq)]
1043pub struct MacroBinding {
1044    source: ProjectFile,
1045    declaration_byte: usize,
1046    definition: MacroDefinition,
1047    exact: bool,
1048}
1049
1050impl MacroBinding {
1051    fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1052        Self {
1053            source: source.clone(),
1054            declaration_byte,
1055            definition: MacroDefinition::Unsupported,
1056            exact: false,
1057        }
1058    }
1059
1060    fn is_exact(&self) -> bool {
1061        self.exact
1062    }
1063
1064    fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1065        Self {
1066            source: source.clone(),
1067            declaration_byte,
1068            definition: current.definition.clone(),
1069            exact: false,
1070        }
1071    }
1072}
1073
1074#[derive(Clone)]
1075pub enum MacroEvent {
1076    Define {
1077        name: String,
1078        binding: MacroBinding,
1079        byte: usize,
1080        conditional: bool,
1081    },
1082    Undef {
1083        name: String,
1084        byte: usize,
1085        conditional: bool,
1086    },
1087    Include {
1088        targets: Vec<ProjectFile>,
1089        byte: usize,
1090        conditional: bool,
1091    },
1092    Invalidate {
1093        byte: usize,
1094    },
1095}
1096
1097impl MacroEvent {
1098    pub fn byte(&self) -> usize {
1099        match self {
1100            Self::Define { byte, .. }
1101            | Self::Undef { byte, .. }
1102            | Self::Include { byte, .. }
1103            | Self::Invalidate { byte } => *byte,
1104        }
1105    }
1106}
1107
1108#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1109pub enum CallArityEvidence {
1110    Exact(usize),
1111    Unknown,
1112}
1113
1114impl CallArityEvidence {
1115    pub fn exact(self) -> Option<usize> {
1116        match self {
1117            Self::Exact(arity) => Some(arity),
1118            Self::Unknown => None,
1119        }
1120    }
1121
1122    pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1123        self.exact().map(|arity| expected.accepts(arity))
1124    }
1125}
1126
1127#[derive(Clone)]
1128struct DeclaredFieldTypeFact {
1129    type_text: String,
1130    indirection: i32,
1131    template_arguments: Option<Vec<CppTemplateExpression>>,
1132}
1133
1134#[derive(Clone)]
1135enum StructuredAliasTarget {
1136    Builtin,
1137    Named {
1138        components: Vec<String>,
1139        global: bool,
1140        arguments: Option<Vec<CppTemplateExpression>>,
1141    },
1142}
1143
1144struct CppAlias {
1145    name: String,
1146    target: String,
1147    namespace: Option<String>,
1148}
1149
1150type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1151
1152/// Why template-argument resolution failed. Definition diagnostics render
1153/// each mode differently; graph scans only care that the resolution is
1154/// unproven and match `Err(_)`.
1155#[derive(Debug, Clone, PartialEq, Eq)]
1156pub enum CppTemplateResolutionError {
1157    /// A template alias expansion revisited `alias`.
1158    AliasCycle { alias: CodeUnit },
1159    /// The explicit arguments do not bind to the declared template parameters.
1160    ArgumentBinding,
1161    /// Bound arguments do not substitute into the alias target's arguments.
1162    Substitution,
1163    /// No visible primary template declaration could be selected and
1164    /// reconciled for the specialization family.
1165    PrimarySelection,
1166    /// More than one applicable specialization remains and none is strictly
1167    /// more specialized than every other candidate.
1168    AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1169}
1170
1171/// The ambiguity candidates, deduplicated to one representative per visible
1172/// symbol so a diagnostic lists each contender once.
1173fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1174    let mut distinct: Vec<CodeUnit> = Vec::new();
1175    for unit in units {
1176        if !distinct
1177            .iter()
1178            .any(|existing| same_visible_symbol(existing, unit))
1179        {
1180            distinct.push(unit.clone());
1181        }
1182    }
1183    distinct
1184}
1185
1186impl<'a> VisibilityIndex<'a> {
1187    pub fn cpp(&self) -> &'a dyn CppSource {
1188        self.cpp
1189    }
1190
1191    /// A [`VisibilityIndex`] over a caller-supplied visible-declaration map,
1192    /// bypassing the include-closure walk [`Self::build`] performs.
1193    ///
1194    /// The resolver's own unit tests drive the type-resolution paths against a
1195    /// hand-written visibility table; they live in `brokk-bifrost-analysis`
1196    /// because they need a real `CppAnalyzer`, so the struct literal they used
1197    /// to write inline is here instead of thirty-three public fields.
1198    #[cfg(any(test, feature = "test-support"))]
1199    pub fn from_visible_files_for_test(
1200        cpp: &'a dyn CppSource,
1201        visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1202    ) -> Self {
1203        let visible_source_files_by_root = visible_by_file
1204            .iter()
1205            .map(|(file, visible)| {
1206                (
1207                    file.clone(),
1208                    visible
1209                        .iter()
1210                        .map(|unit| unit.source().clone())
1211                        .chain(std::iter::once(file.clone()))
1212                        .collect(),
1213                )
1214            })
1215            .collect();
1216        let mut global_field_internal_linkage = HashMap::default();
1217        Self {
1218            cpp,
1219            visible_by_identifier: build_visible_identifier_index(
1220                &CppGraphSource::from_source(cpp),
1221                &visible_by_file,
1222                &visible_source_files_by_root,
1223                &mut global_field_internal_linkage,
1224            ),
1225            global_field_internal_linkage,
1226            visible_by_file,
1227            visible_source_files_by_root,
1228            alias_cells: Mutex::new(HashMap::default()),
1229            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1230            visible_parser_alias_target_names: Mutex::new(HashMap::default()),
1231            ordinary_type_import_cells: Mutex::new(HashMap::default()),
1232            project_using_index: OnceLock::new(),
1233            callable_reference_specs: Mutex::new(HashMap::default()),
1234            include_activation_cells: Mutex::new(HashMap::default()),
1235            conditional_include_projection_cells: Mutex::new(HashMap::default()),
1236            conditional_include_projection_index_build_count: AtomicUsize::new(0),
1237            conditional_include_projection_state_count: AtomicUsize::new(0),
1238            include_activation_build_count: AtomicUsize::new(0),
1239            using_donor_activation_count: AtomicUsize::new(0),
1240            using_namespace_lookup_count: AtomicUsize::new(0),
1241            using_name_candidate_inspection_count: AtomicUsize::new(0),
1242            callable_reference_spec_build_count: AtomicUsize::new(0),
1243            alias_source_parse_counts: Mutex::new(HashMap::default()),
1244            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1245            visible_parser_alias_target_names_build_count: AtomicUsize::new(0),
1246            field_type_facts: Mutex::new(HashMap::default()),
1247            structured_alias_targets: Mutex::new(HashMap::default()),
1248            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1249            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1250            precise_parent_cache: Mutex::new(HashMap::default()),
1251            macro_event_cells: Mutex::new(HashMap::default()),
1252            macro_include_protection_cells: Mutex::new(HashMap::default()),
1253            macro_environment_cursors: Mutex::new(HashMap::default()),
1254            macro_replacements: Mutex::new(HashMap::default()),
1255            macro_local_binding_templates: Mutex::new(HashMap::default()),
1256            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1257            macro_replacement_parse_count: AtomicUsize::new(0),
1258            macro_event_application_count: AtomicUsize::new(0),
1259            macro_environment_copy_count: AtomicUsize::new(0),
1260            cpp_template_metadata: HashMap::default(),
1261            cpp_template_families: HashMap::default(),
1262            qualified_candidate_inspections: AtomicUsize::new(0),
1263            target_preserving_type_resolution_count: AtomicUsize::new(0),
1264        }
1265    }
1266
1267    /// The index's own C++ source, in the dispatching-analyzer shape.
1268    ///
1269    /// Four resolution paths reach the workspace through the C++ analyzer they
1270    /// already hold rather than through the analyzer the query was issued
1271    /// against; before the move they passed `&CppAnalyzer` straight into a
1272    /// `&dyn IAnalyzer` parameter. See [`CppGraphSource::from_source`].
1273    fn cpp_source(&self) -> CppGraphSource<'a> {
1274        CppGraphSource::from_source(self.cpp)
1275    }
1276
1277    pub fn build(
1278        cpp: &'a dyn CppSource,
1279        analyzer: &CppGraphSource<'_>,
1280        roots: &HashSet<ProjectFile>,
1281    ) -> Self {
1282        Self::build_with_cancellation(cpp, analyzer, roots, None)
1283    }
1284
1285    pub fn build_with_cancellation(
1286        cpp: &'a dyn CppSource,
1287        analyzer: &CppGraphSource<'_>,
1288        roots: &HashSet<ProjectFile>,
1289        cancellation: Option<&CancellationToken>,
1290    ) -> Self {
1291        let include_targets = cpp.include_target_index();
1292        let VisibilityData {
1293            mut visible_by_file,
1294            visible_source_files_by_root,
1295        } = build_visibility_data(
1296            roots,
1297            cancellation,
1298            |file| {
1299                let imports = analyzer.import_statements(file);
1300                cpp_include_paths(&imports)
1301                    .into_iter()
1302                    .flat_map(|include| {
1303                        resolve_include_targets_with_index(file, &include, include_targets)
1304                    })
1305                    .collect()
1306            },
1307            |file| analyzer.declarations(file),
1308        );
1309        extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
1310        let mut global_field_internal_linkage = HashMap::default();
1311        let visible_by_identifier = build_visible_identifier_index(
1312            analyzer,
1313            &visible_by_file,
1314            &visible_source_files_by_root,
1315            &mut global_field_internal_linkage,
1316        );
1317        let mut cpp_template_metadata = HashMap::default();
1318        for unit in visible_by_file
1319            .values()
1320            .flatten()
1321            .filter(|unit| unit.is_class())
1322        {
1323            if cpp_template_metadata.contains_key(unit) {
1324                continue;
1325            }
1326            if let Some(metadata) = cpp.template_metadata(unit) {
1327                cpp_template_metadata.insert(unit.clone(), metadata);
1328            }
1329        }
1330        let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
1331        for (unit, metadata) in &cpp_template_metadata {
1332            cpp_template_families
1333                .entry(metadata.primary_fq_name.clone())
1334                .or_default()
1335                .push(unit.clone());
1336        }
1337        // `cpp_template_metadata` is hash-keyed on `CodeUnit`, so the push
1338        // order above is a function of those hashes. Two mirrored headers can
1339        // declare one specialization; `select_template_specialization` treats
1340        // them as interchangeable and returns the family's first entry, so an
1341        // unsorted family made the reported declaration depend on the
1342        // workspace's absolute path and on unrelated files (#1836). Order the
1343        // family exactly as `build_visible_identifier_index` orders its
1344        // per-identifier candidate lists.
1345        for family in cpp_template_families.values_mut() {
1346            sort_lookup_units(family);
1347        }
1348        Self {
1349            cpp,
1350            visible_by_file,
1351            visible_by_identifier,
1352            global_field_internal_linkage,
1353            visible_source_files_by_root,
1354            alias_cells: Mutex::new(HashMap::default()),
1355            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1356            visible_parser_alias_target_names: Mutex::new(HashMap::default()),
1357            ordinary_type_import_cells: Mutex::new(HashMap::default()),
1358            project_using_index: OnceLock::new(),
1359            callable_reference_specs: Mutex::new(HashMap::default()),
1360            include_activation_cells: Mutex::new(HashMap::default()),
1361            conditional_include_projection_cells: Mutex::new(HashMap::default()),
1362            #[cfg(any(test, feature = "test-support"))]
1363            conditional_include_projection_index_build_count: AtomicUsize::new(0),
1364            #[cfg(any(test, feature = "test-support"))]
1365            conditional_include_projection_state_count: AtomicUsize::new(0),
1366            #[cfg(any(test, feature = "test-support"))]
1367            include_activation_build_count: AtomicUsize::new(0),
1368            #[cfg(any(test, feature = "test-support"))]
1369            using_donor_activation_count: AtomicUsize::new(0),
1370            #[cfg(any(test, feature = "test-support"))]
1371            using_namespace_lookup_count: AtomicUsize::new(0),
1372            #[cfg(any(test, feature = "test-support"))]
1373            using_name_candidate_inspection_count: AtomicUsize::new(0),
1374            #[cfg(any(test, feature = "test-support"))]
1375            callable_reference_spec_build_count: AtomicUsize::new(0),
1376            #[cfg(any(test, feature = "test-support"))]
1377            alias_source_parse_counts: Mutex::new(HashMap::default()),
1378            #[cfg(any(test, feature = "test-support"))]
1379            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1380            #[cfg(any(test, feature = "test-support"))]
1381            visible_parser_alias_target_names_build_count: AtomicUsize::new(0),
1382            field_type_facts: Mutex::new(HashMap::default()),
1383            structured_alias_targets: Mutex::new(HashMap::default()),
1384            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1385            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1386            precise_parent_cache: Mutex::new(HashMap::default()),
1387            macro_event_cells: Mutex::new(HashMap::default()),
1388            macro_include_protection_cells: Mutex::new(HashMap::default()),
1389            macro_environment_cursors: Mutex::new(HashMap::default()),
1390            macro_replacements: Mutex::new(HashMap::default()),
1391            macro_local_binding_templates: Mutex::new(HashMap::default()),
1392            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1393            #[cfg(any(test, feature = "test-support"))]
1394            macro_replacement_parse_count: AtomicUsize::new(0),
1395            #[cfg(any(test, feature = "test-support"))]
1396            macro_event_application_count: AtomicUsize::new(0),
1397            #[cfg(any(test, feature = "test-support"))]
1398            macro_environment_copy_count: AtomicUsize::new(0),
1399            cpp_template_metadata,
1400            cpp_template_families,
1401            #[cfg(any(test, feature = "test-support"))]
1402            qualified_candidate_inspections: AtomicUsize::new(0),
1403            #[cfg(any(test, feature = "test-support"))]
1404            target_preserving_type_resolution_count: AtomicUsize::new(0),
1405        }
1406    }
1407
1408    pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
1409        if file == target.source() {
1410            return true;
1411        }
1412        if self.global_field_has_internal_linkage(target) {
1413            return self
1414                .visible_source_files_by_root
1415                .get(file)
1416                .is_some_and(|sources| sources.contains(target.source()));
1417        }
1418        self.visible_by_file
1419            .get(file)
1420            .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
1421    }
1422
1423    fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
1424        self.global_field_internal_linkage
1425            .get(unit)
1426            .copied()
1427            .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
1428    }
1429
1430    pub fn call_arity_evidence(
1431        &self,
1432        file: &ProjectFile,
1433        call: Node<'_>,
1434        source: &str,
1435    ) -> CallArityEvidence {
1436        let Some(arguments) = call
1437            .child_by_field_name("arguments")
1438            .or_else(|| call.child_by_field_name("parameters"))
1439            .or_else(|| call.child_by_field_name("value"))
1440            .or_else(|| first_named_child_of_kind(call, "argument_list"))
1441            .or_else(|| first_named_child_of_kind(call, "initializer_list"))
1442        else {
1443            return CallArityEvidence::Exact(0);
1444        };
1445        let recovered_c_keyword_arguments =
1446            recovered_c_keyword_argument_count(file, call, arguments, source);
1447        let arguments = argument_children(arguments).collect::<Vec<_>>();
1448        if arguments
1449            .iter()
1450            .all(|argument| !argument_shape_may_change_arity(*argument))
1451        {
1452            return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
1453        }
1454        let environment = self.macro_environment(file, call.start_byte());
1455        let mut stack = Vec::new();
1456        let mut total = recovered_c_keyword_arguments;
1457        for argument in arguments {
1458            if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
1459                return CallArityEvidence::Unknown;
1460            }
1461            let CallArityEvidence::Exact(spread) =
1462                self.argument_arity_evidence(argument, source, &environment, &mut stack)
1463            else {
1464                return CallArityEvidence::Unknown;
1465            };
1466            total += spread;
1467        }
1468        CallArityEvidence::Exact(total)
1469    }
1470
1471    fn argument_arity_evidence(
1472        &self,
1473        argument: Node<'_>,
1474        source: &str,
1475        environment: &MacroEnvironment,
1476        stack: &mut Vec<(ProjectFile, usize)>,
1477    ) -> CallArityEvidence {
1478        let (name, invocation_arguments, function_like) = match argument.kind() {
1479            "identifier" => (node_text(argument, source), None, false),
1480            "call_expression" => {
1481                let Some(function) = argument.child_by_field_name("function") else {
1482                    return CallArityEvidence::Exact(1);
1483                };
1484                if function.kind() != "identifier" {
1485                    return CallArityEvidence::Exact(1);
1486                }
1487                let Some(arguments) = argument.child_by_field_name("arguments") else {
1488                    return CallArityEvidence::Exact(1);
1489                };
1490                (node_text(function, source), Some(arguments), true)
1491            }
1492            _ => return CallArityEvidence::Exact(1),
1493        };
1494        let Some(binding) = environment.binding(name) else {
1495            return if environment.unknown_names {
1496                CallArityEvidence::Unknown
1497            } else {
1498                CallArityEvidence::Exact(1)
1499            };
1500        };
1501        if !binding.is_exact() {
1502            return CallArityEvidence::Unknown;
1503        }
1504        match (&binding.definition, invocation_arguments, function_like) {
1505            (MacroDefinition::Object { replacement }, None, false) => self
1506                .replacement_arity_evidence(
1507                    replacement,
1508                    &[],
1509                    &[],
1510                    source,
1511                    environment,
1512                    stack,
1513                    binding,
1514                ),
1515            (
1516                MacroDefinition::Function {
1517                    parameters,
1518                    replacement,
1519                },
1520                Some(arguments),
1521                true,
1522            ) => {
1523                let actuals = argument_children(arguments).collect::<Vec<_>>();
1524                if actuals.len() != parameters.len() {
1525                    CallArityEvidence::Unknown
1526                } else {
1527                    self.replacement_arity_evidence(
1528                        replacement,
1529                        parameters,
1530                        &actuals,
1531                        source,
1532                        environment,
1533                        stack,
1534                        binding,
1535                    )
1536                }
1537            }
1538            (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
1539            _ => CallArityEvidence::Unknown,
1540        }
1541    }
1542
1543    #[allow(clippy::too_many_arguments)]
1544    fn replacement_arity_evidence(
1545        &self,
1546        replacement: &str,
1547        parameters: &[String],
1548        actuals: &[Node<'_>],
1549        actual_source: &str,
1550        environment: &MacroEnvironment,
1551        stack: &mut Vec<(ProjectFile, usize)>,
1552        binding: &MacroBinding,
1553    ) -> CallArityEvidence {
1554        let identity = (binding.source.clone(), binding.declaration_byte);
1555        if stack.contains(&identity) || replacement.trim().is_empty() {
1556            return CallArityEvidence::Unknown;
1557        }
1558        stack.push(identity);
1559        let parsed = self.parsed_macro_replacement(binding, replacement);
1560        let evidence = (|| {
1561            let ParsedMacroReplacement::Parsed {
1562                source: sentinel,
1563                tree,
1564            } = parsed.as_ref()
1565            else {
1566                return None;
1567            };
1568            let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
1569            let arguments = call.child_by_field_name("arguments")?;
1570            let mut total = 0usize;
1571            for argument in argument_children(arguments) {
1572                if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
1573                    return None;
1574                }
1575                if argument.kind() == "identifier"
1576                    && let Some(parameter_index) = parameters
1577                        .iter()
1578                        .position(|parameter| parameter == node_text(argument, sentinel))
1579                {
1580                    if !macro_expansion_shape_is_safe(
1581                        actuals[parameter_index],
1582                        actual_source,
1583                        &[],
1584                        environment,
1585                    ) {
1586                        return None;
1587                    }
1588                    let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
1589                        actuals[parameter_index],
1590                        actual_source,
1591                        environment,
1592                        stack,
1593                    ) else {
1594                        return None;
1595                    };
1596                    total += spread;
1597                    continue;
1598                }
1599                let CallArityEvidence::Exact(spread) =
1600                    self.argument_arity_evidence(argument, sentinel, environment, stack)
1601                else {
1602                    return None;
1603                };
1604                total += spread;
1605            }
1606            Some(CallArityEvidence::Exact(total))
1607        })()
1608        .unwrap_or(CallArityEvidence::Unknown);
1609        stack.pop();
1610        evidence
1611    }
1612
1613    fn parsed_macro_replacement(
1614        &self,
1615        binding: &MacroBinding,
1616        replacement: &str,
1617    ) -> Arc<ParsedMacroReplacement> {
1618        let key = (binding.source.clone(), binding.declaration_byte);
1619        let mut cache = self
1620            .macro_replacements
1621            .lock()
1622            .expect("C++ macro replacement cache poisoned");
1623        if let Some(parsed) = cache.get(&key) {
1624            return Arc::clone(parsed);
1625        }
1626        #[cfg(any(test, feature = "test-support"))]
1627        self.macro_replacement_parse_count
1628            .fetch_add(1, Ordering::Relaxed);
1629        let source =
1630            format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
1631        let mut parser = Parser::new();
1632        let parsed = parser
1633            .set_language(&tree_sitter_cpp::LANGUAGE.into())
1634            .ok()
1635            .and_then(|()| parser.parse(&source, None))
1636            .filter(|tree| !tree.root_node().has_error())
1637            .map_or(ParsedMacroReplacement::Unsupported, |tree| {
1638                ParsedMacroReplacement::Parsed { source, tree }
1639            });
1640        let parsed = Arc::new(parsed);
1641        cache.insert(key, Arc::clone(&parsed));
1642        parsed
1643    }
1644
1645    /// Recover a typed local declared by an active C function-like macro.
1646    ///
1647    /// This is intentionally narrower than macro expansion. The replacement
1648    /// must parse as one declaration, and the invocation must bind every
1649    /// formal parameter to one structured argument. That is sufficient for
1650    /// declaration macros such as `THIS(StorageAzure)`. An unavailable include
1651    /// can make the binding provisional without erasing its last known
1652    /// definition; an explicit conflicting definition still replaces it with
1653    /// Unsupported. Malformed and statement-producing macros also fail closed.
1654    pub fn function_macro_local_binding<'tree>(
1655        &self,
1656        file: &ProjectFile,
1657        statement: Node<'tree>,
1658        source: &str,
1659    ) -> Option<MacroLocalBinding<'tree>> {
1660        if !is_c_source_file(file) {
1661            return None;
1662        }
1663        let call = match statement.kind() {
1664            "call_expression" => statement,
1665            "expression_statement" if statement.named_child_count() == 1 => {
1666                statement.named_child(0)?
1667            }
1668            _ => return None,
1669        };
1670        if call.kind() != "call_expression" {
1671            return None;
1672        }
1673        let function = call.child_by_field_name("function")?;
1674        if function.kind() != "identifier" {
1675            return None;
1676        }
1677        let arguments = call.child_by_field_name("arguments")?;
1678        let actuals = argument_children(arguments).collect::<Vec<_>>();
1679        let environment = self.macro_environment(file, call.start_byte());
1680        let function_name = node_text(function, source);
1681        let binding = environment.binding(function_name)?;
1682        let MacroDefinition::Function {
1683            parameters,
1684            replacement,
1685        } = &binding.definition
1686        else {
1687            return None;
1688        };
1689        if actuals.len() != parameters.len() {
1690            return None;
1691        }
1692        let template = self.macro_local_binding_template(binding, parameters, replacement)?;
1693        let (type_name, type_node) = match &template.declared_type {
1694            MacroLocalBindingTypeTemplate::Parameter(index) => {
1695                let actual = *actuals.get(*index)?;
1696                if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
1697                    return None;
1698                }
1699                (node_text(actual, source).trim().to_string(), Some(actual))
1700            }
1701            MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
1702        };
1703        if type_name.is_empty() {
1704            return None;
1705        }
1706        Some(MacroLocalBinding {
1707            name: template.name.clone(),
1708            type_name,
1709            type_node,
1710            pointer_depth: template.pointer_depth,
1711        })
1712    }
1713
1714    fn macro_local_binding_template(
1715        &self,
1716        binding: &MacroBinding,
1717        parameters: &[String],
1718        replacement: &str,
1719    ) -> Option<Arc<MacroLocalBindingTemplate>> {
1720        let key = (binding.source.clone(), binding.declaration_byte);
1721        let mut cache = self
1722            .macro_local_binding_templates
1723            .lock()
1724            .expect("C++ macro local-binding cache poisoned");
1725        if let Some(template) = cache.get(&key) {
1726            return template.clone();
1727        }
1728        let sentinel = format!("void __bifrost_macro_local() {{ {replacement}; }}");
1729        let template = (|| {
1730            let mut parser = Parser::new();
1731            parser
1732                .set_language(&tree_sitter_cpp::LANGUAGE.into())
1733                .ok()?;
1734            let tree = parser.parse(&sentinel, None)?;
1735            if tree.root_node().has_error() {
1736                return None;
1737            }
1738            let function = first_descendant_of_kind(tree.root_node(), "function_definition")?;
1739            let body = function.child_by_field_name("body")?;
1740            if body.named_child_count() != 1 {
1741                return None;
1742            }
1743            let declaration = body.named_child(0)?;
1744            if declaration.kind() != "declaration" {
1745                return None;
1746            }
1747            let type_node = declaration
1748                .child_by_field_name("type")
1749                .or_else(|| first_type_child(declaration))?;
1750            let declarator = declaration.child_by_field_name("declarator").or_else(|| {
1751                let mut cursor = declaration.walk();
1752                declaration.named_children(&mut cursor).find_map(|child| {
1753                    if child.kind() == "init_declarator" {
1754                        child.child_by_field_name("declarator")
1755                    } else {
1756                        is_declarator_node(child).then_some(child)
1757                    }
1758                })
1759            })?;
1760            let name = extract_variable_name(declarator, &sentinel)?;
1761            let pointer_depth =
1762                declared_name_indirection(declaration, type_node, &name, &sentinel)?;
1763            let type_text = node_text(type_node, &sentinel).trim();
1764            let declared_type = parameters
1765                .iter()
1766                .position(|parameter| parameter == type_text)
1767                .map(MacroLocalBindingTypeTemplate::Parameter)
1768                .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
1769            Some(Arc::new(MacroLocalBindingTemplate {
1770                name,
1771                declared_type,
1772                pointer_depth,
1773            }))
1774        })();
1775        cache.insert(key, template.clone());
1776        template
1777    }
1778
1779    fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
1780        let Some(value) = node.child_by_field_name("value") else {
1781            return MacroDefinition::Unsupported;
1782        };
1783        let replacement = node_text(value, source).to_string();
1784        if node.kind() == "preproc_def" {
1785            return MacroDefinition::Object { replacement };
1786        }
1787        let Some(parameters) = node.child_by_field_name("parameters") else {
1788            return MacroDefinition::Unsupported;
1789        };
1790        if (0..parameters.child_count()).any(|index| {
1791            parameters
1792                .child(index)
1793                .is_some_and(|child| child.kind() == "...")
1794        }) {
1795            return MacroDefinition::Unsupported;
1796        }
1797        let parameters = (0..parameters.named_child_count())
1798            .filter_map(|index| parameters.named_child(index))
1799            .map(|parameter| node_text(parameter, source).to_string())
1800            .collect();
1801        MacroDefinition::Function {
1802            parameters,
1803            replacement,
1804        }
1805    }
1806
1807    pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
1808        self.macro_event_cells
1809            .lock()
1810            .expect("C++ macro event cache poisoned")
1811            .entry(file.clone())
1812            .or_default()
1813            .clone()
1814    }
1815
1816    pub fn macro_environment_cursor_cell(&self, file: &ProjectFile) -> MacroEnvironmentCursorCell {
1817        let key = (file.clone(), std::thread::current().id());
1818        self.macro_environment_cursors
1819            .lock()
1820            .expect("C++ macro environment cursor cache poisoned")
1821            .entry(key)
1822            .or_default()
1823            .clone()
1824    }
1825
1826    pub fn macro_environment(
1827        &self,
1828        file: &ProjectFile,
1829        before_byte: usize,
1830    ) -> Arc<MacroEnvironment> {
1831        let cell = self.macro_event_cell(file);
1832        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
1833        let frontier = events.partition_point(|event| event.byte() < before_byte);
1834        let cursor_cell = self.macro_environment_cursor_cell(file);
1835        let mut cursor = cursor_cell
1836            .lock()
1837            .expect("C++ macro environment cursor poisoned");
1838        if frontier < cursor.frontier {
1839            *cursor = MacroEnvironmentCursor::default();
1840        }
1841        if frontier > cursor.frontier {
1842            #[cfg(any(test, feature = "test-support"))]
1843            if Arc::strong_count(&cursor.environment) > 1 {
1844                self.macro_environment_copy_count
1845                    .fetch_add(1, Ordering::Relaxed);
1846            }
1847            let start = cursor.frontier;
1848            let environment = Arc::make_mut(&mut cursor.environment);
1849            let mut include_stack = HashSet::from_iter([file.clone()]);
1850            for event in &events[start..frontier] {
1851                self.apply_macro_event(file, event, environment, &mut include_stack);
1852            }
1853            cursor.frontier = frontier;
1854        }
1855        Arc::clone(&cursor.environment)
1856    }
1857
1858    /// Whether `name` is bound as a macro at `before_byte` in `file`,
1859    /// including a binding this environment cannot pin to one replacement
1860    /// (a conditional `#define`, or a function-like macro).
1861    ///
1862    /// [`Self::object_macro_replacement_at`] collapses every such binding to
1863    /// `None`, which is indistinguishable from "not a macro at all". A caller
1864    /// that must not read a macro token as an ordinary type name needs the two
1865    /// apart: an unexpandable macro is an unknown, a plain identifier is not.
1866    pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
1867        self.macro_environment(file, before_byte)
1868            .binding(name)
1869            .is_some()
1870    }
1871
1872    pub fn macro_name_may_be_bound_at(
1873        &self,
1874        file: &ProjectFile,
1875        name: &str,
1876        before_byte: usize,
1877    ) -> bool {
1878        self.macro_environment(file, before_byte).may_bind(name)
1879    }
1880
1881    /// Whether the active macro binding at this reference is the requested
1882    /// indexed definition. Name equality alone is not enough because two
1883    /// headers can define the same macro for different translation units.
1884    pub fn macro_binding_matches_target_at(
1885        &self,
1886        analyzer: &CppGraphSource<'_>,
1887        file: &ProjectFile,
1888        name: &str,
1889        before_byte: usize,
1890        target: &CodeUnit,
1891    ) -> bool {
1892        let environment = self.macro_environment(file, before_byte);
1893        let Some(binding) = environment.binding(name) else {
1894            return false;
1895        };
1896        // A normal header guard makes the replacement text conditional, but
1897        // it does not erase the definition site's source and byte identity.
1898        // Keep that identity even when expansion details are not exact.
1899        if binding.source != *target.source() {
1900            return false;
1901        }
1902        let Some(prepared) = self.cpp.prepared_syntax(target.source()) else {
1903            return false;
1904        };
1905        analyzer.ranges(target).iter().any(|range| {
1906            let Some(mut node) = node_for_exact_range(prepared.tree().root_node(), range) else {
1907                return false;
1908            };
1909            while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
1910                let Some(parent) = node.parent() else {
1911                    return false;
1912                };
1913                node = parent;
1914            }
1915            node.start_byte() == binding.declaration_byte
1916        })
1917    }
1918
1919    /// Resolve an ordinary expression-position macro token at its exact byte.
1920    ///
1921    /// Calls and preprocessor-condition tokens have separate resolution
1922    /// surfaces. Declaration names, macro parameters, and labels are not
1923    /// references. Keeping that role policy here makes forward and both
1924    /// inverse graph builders consume the same activation verdict (#2093).
1925    pub fn resolve_ordinary_macro_reference(
1926        &self,
1927        analyzer: &CppGraphSource<'_>,
1928        file: &ProjectFile,
1929        node: Node<'_>,
1930        source: &str,
1931    ) -> OrdinaryMacroReferenceResolution {
1932        if !is_ordinary_macro_reference_node(node) {
1933            return OrdinaryMacroReferenceResolution::Missing;
1934        }
1935        let name = node_text(node, source);
1936        if name.is_empty() {
1937            return OrdinaryMacroReferenceResolution::Missing;
1938        }
1939        let visible = self
1940            .visible_identifier_candidates(file, name)
1941            .filter(|candidate| candidate.is_macro())
1942            .cloned()
1943            .collect::<Vec<_>>();
1944        let mut exact = Vec::new();
1945        for candidate in &visible {
1946            if self.macro_binding_matches_target_at(
1947                analyzer,
1948                file,
1949                name,
1950                node.start_byte(),
1951                candidate,
1952            ) && !exact
1953                .iter()
1954                .any(|existing| same_visible_symbol(existing, candidate))
1955            {
1956                exact.push(candidate.clone());
1957            }
1958        }
1959        match exact.len() {
1960            1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
1961            2.. => OrdinaryMacroReferenceResolution::Ambiguous,
1962            0 if !visible.is_empty()
1963                && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
1964            {
1965                OrdinaryMacroReferenceResolution::Ambiguous
1966            }
1967            0 => OrdinaryMacroReferenceResolution::Missing,
1968        }
1969    }
1970
1971    /// Collect reference-capable C tokens beneath tree-sitter recovery nodes.
1972    ///
1973    /// The ordinary census deliberately skips every `ERROR` subtree. This
1974    /// separate, precision-only frontier admits only roles that retain enough
1975    /// structure for the C usage graph to interpret independently (#2089).
1976    /// Macro evidence comes from this visibility index at the exact byte; no
1977    /// source-text parsing or terminal-name fallback is used.
1978    pub fn recovered_c_reference_ranges(
1979        &self,
1980        file: &ProjectFile,
1981        root: Node<'_>,
1982        source: &str,
1983        limit: usize,
1984    ) -> RecoveredCReferenceRanges {
1985        if !is_c_source_file(file) {
1986            return RecoveredCReferenceRanges::Complete(Vec::new());
1987        }
1988        let mut ranges = Vec::new();
1989        let mut seen = HashSet::default();
1990        let mut stack = vec![(root, root.is_error())];
1991        while let Some((node, inside_error)) = stack.pop() {
1992            let inside_error = inside_error || node.is_error();
1993            if inside_error
1994                && recovered_c_reference_node(self, file, node, source)
1995                && seen.insert((node.start_byte(), node.end_byte()))
1996            {
1997                if ranges.len() == limit {
1998                    return RecoveredCReferenceRanges::LimitExceeded;
1999                }
2000                ranges.push(Range {
2001                    start_byte: node.start_byte(),
2002                    end_byte: node.end_byte(),
2003                    start_line: node.start_position().row,
2004                    end_line: node.end_position().row,
2005                });
2006            }
2007            let mut cursor = node.walk();
2008            for child in node.named_children(&mut cursor) {
2009                stack.push((child, inside_error));
2010            }
2011        }
2012        ranges.sort_unstable();
2013        RecoveredCReferenceRanges::Complete(ranges)
2014    }
2015
2016    /// Whether this target is an indexed macro visible from this file.
2017    ///
2018    /// An unresolved conditional can make more than one same-name macro a
2019    /// possible active binding. Each possible target can keep the site as an
2020    /// unproven hit. A macro in an unrelated translation unit stays excluded.
2021    pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2022        self.visible_identifier_candidates(file, target.identifier())
2023            .filter(|candidate| candidate.is_macro())
2024            .any(|candidate| {
2025                candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
2026            })
2027    }
2028
2029    pub fn object_macro_replacement_at(
2030        &self,
2031        file: &ProjectFile,
2032        name: &str,
2033        before_byte: usize,
2034    ) -> Option<String> {
2035        let environment = self.macro_environment(file, before_byte);
2036        let binding = environment.binding(name)?;
2037        if !binding.exact {
2038            return None;
2039        }
2040        match &binding.definition {
2041            MacroDefinition::Object { replacement } => Some(replacement.clone()),
2042            MacroDefinition::Function { .. } | MacroDefinition::Unsupported => None,
2043        }
2044    }
2045
2046    fn apply_macro_events(
2047        &self,
2048        file: &ProjectFile,
2049        before_byte: Option<usize>,
2050        environment: &mut MacroEnvironment,
2051        include_stack: &mut HashSet<ProjectFile>,
2052    ) {
2053        if !include_stack.insert(file.clone()) {
2054            return;
2055        }
2056        if self.cpp.prepared_syntax(file).is_none() {
2057            environment.mark_unknown_names(file, before_byte.unwrap_or_default());
2058            include_stack.remove(file);
2059            return;
2060        }
2061        match self.macro_include_protection(file) {
2062            MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
2063                Some(binding) if binding.is_exact() => {
2064                    include_stack.remove(file);
2065                    return;
2066                }
2067                Some(_) | None if environment.unknown_names => {
2068                    let mut ambiguous_seen = HashSet::default();
2069                    self.mark_macro_events_ambiguous(
2070                        file,
2071                        environment,
2072                        &mut ambiguous_seen,
2073                        file,
2074                        before_byte.unwrap_or_default(),
2075                    );
2076                    include_stack.remove(file);
2077                    return;
2078                }
2079                Some(_) => {
2080                    let mut ambiguous_seen = HashSet::default();
2081                    self.mark_macro_events_ambiguous(
2082                        file,
2083                        environment,
2084                        &mut ambiguous_seen,
2085                        file,
2086                        before_byte.unwrap_or_default(),
2087                    );
2088                    include_stack.remove(file);
2089                    return;
2090                }
2091                None => {}
2092            },
2093            MacroIncludeProtection::PragmaOnce => {
2094                if !environment.applied_pragma_once_files.insert(file.clone()) {
2095                    include_stack.remove(file);
2096                    return;
2097                }
2098                if environment.maybe_applied_pragma_once_files.remove(file) {
2099                    // A prior conditional include may already have consumed the pragma-once
2100                    // header. This unconditional include guarantees it is consumed now, but
2101                    // cannot prove whether its events occur before or after intervening local
2102                    // macro changes, so preserve the union as ambiguous.
2103                    let mut ambiguous_seen = HashSet::default();
2104                    environment.applied_pragma_once_files.remove(file);
2105                    self.mark_macro_events_ambiguous(
2106                        file,
2107                        environment,
2108                        &mut ambiguous_seen,
2109                        file,
2110                        before_byte.unwrap_or_default(),
2111                    );
2112                    environment.maybe_applied_pragma_once_files.remove(file);
2113                    environment.applied_pragma_once_files.insert(file.clone());
2114                    include_stack.remove(file);
2115                    return;
2116                }
2117            }
2118            MacroIncludeProtection::None => {}
2119        }
2120        let cell = self.macro_event_cell(file);
2121        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2122        for event in events {
2123            if before_byte.is_some_and(|limit| event.byte() >= limit) {
2124                break;
2125            }
2126            self.apply_macro_event(file, event, environment, include_stack);
2127        }
2128        include_stack.remove(file);
2129    }
2130
2131    fn apply_macro_event(
2132        &self,
2133        file: &ProjectFile,
2134        event: &MacroEvent,
2135        environment: &mut MacroEnvironment,
2136        include_stack: &mut HashSet<ProjectFile>,
2137    ) {
2138        #[cfg(any(test, feature = "test-support"))]
2139        self.macro_event_application_count
2140            .fetch_add(1, Ordering::Relaxed);
2141        match event {
2142            MacroEvent::Define {
2143                name,
2144                binding,
2145                conditional,
2146                byte,
2147            } => {
2148                if *conditional {
2149                    Self::merge_conditional_macro_definition(
2150                        environment,
2151                        name,
2152                        binding,
2153                        file,
2154                        *byte,
2155                    );
2156                } else {
2157                    environment.insert(name.clone(), binding.clone());
2158                }
2159            }
2160            MacroEvent::Undef {
2161                name,
2162                conditional,
2163                byte,
2164            } => {
2165                if *conditional {
2166                    if environment.binding(name).is_some() {
2167                        environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
2168                    }
2169                } else {
2170                    environment.remove(name);
2171                }
2172            }
2173            MacroEvent::Include {
2174                targets,
2175                conditional,
2176                byte,
2177            } => {
2178                if targets.is_empty() {
2179                    environment.mark_unknown_names(file, *byte);
2180                    return;
2181                }
2182                if *conditional || targets.len() > 1 {
2183                    let mut ambiguous_seen = HashSet::default();
2184                    for target in targets {
2185                        self.mark_macro_events_ambiguous(
2186                            target,
2187                            environment,
2188                            &mut ambiguous_seen,
2189                            file,
2190                            *byte,
2191                        );
2192                    }
2193                } else if let Some(target) = targets.first() {
2194                    self.apply_macro_events(target, None, environment, include_stack);
2195                }
2196            }
2197            MacroEvent::Invalidate { byte } => {
2198                for binding in environment.bindings.values_mut() {
2199                    *binding = MacroBinding::uncertain_from(binding, file, *byte);
2200                }
2201            }
2202        }
2203    }
2204
2205    fn mark_macro_events_ambiguous(
2206        &self,
2207        file: &ProjectFile,
2208        environment: &mut MacroEnvironment,
2209        include_stack: &mut HashSet<ProjectFile>,
2210        conditional_file: &ProjectFile,
2211        conditional_byte: usize,
2212    ) {
2213        if !include_stack.insert(file.clone()) {
2214            return;
2215        }
2216        if self.cpp.prepared_syntax(file).is_none() {
2217            environment.mark_unknown_names(conditional_file, conditional_byte);
2218            return;
2219        }
2220        match self.macro_include_protection(file) {
2221            MacroIncludeProtection::MacroGuard(guard) => {
2222                if environment
2223                    .binding(&guard)
2224                    .is_some_and(MacroBinding::is_exact)
2225                {
2226                    return;
2227                }
2228            }
2229            MacroIncludeProtection::PragmaOnce => {
2230                if environment.applied_pragma_once_files.contains(file) {
2231                    return;
2232                }
2233                environment
2234                    .maybe_applied_pragma_once_files
2235                    .insert(file.clone());
2236            }
2237            MacroIncludeProtection::None => {}
2238        }
2239        let cell = self.macro_event_cell(file);
2240        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2241        for event in events {
2242            #[cfg(any(test, feature = "test-support"))]
2243            self.macro_event_application_count
2244                .fetch_add(1, Ordering::Relaxed);
2245            match event {
2246                MacroEvent::Define { name, binding, .. } => {
2247                    Self::merge_conditional_macro_definition(
2248                        environment,
2249                        name,
2250                        binding,
2251                        conditional_file,
2252                        conditional_byte,
2253                    );
2254                }
2255                MacroEvent::Undef { name, .. } => {
2256                    if environment.binding(name).is_some() {
2257                        environment.insert(
2258                            name.clone(),
2259                            MacroBinding::ambiguous(conditional_file, conditional_byte),
2260                        );
2261                    } else {
2262                        environment.remove_known_undefined(name);
2263                    }
2264                }
2265                MacroEvent::Include { targets, .. } => {
2266                    if targets.is_empty() {
2267                        environment.mark_unknown_names(conditional_file, conditional_byte);
2268                        continue;
2269                    }
2270                    for target in targets {
2271                        self.mark_macro_events_ambiguous(
2272                            target,
2273                            environment,
2274                            include_stack,
2275                            conditional_file,
2276                            conditional_byte,
2277                        );
2278                    }
2279                }
2280                MacroEvent::Invalidate { .. } => {
2281                    for binding in environment.bindings.values_mut() {
2282                        *binding = MacroBinding::uncertain_from(
2283                            binding,
2284                            conditional_file,
2285                            conditional_byte,
2286                        );
2287                    }
2288                }
2289            }
2290        }
2291    }
2292
2293    fn merge_conditional_macro_definition(
2294        environment: &mut MacroEnvironment,
2295        name: &str,
2296        possible_binding: &MacroBinding,
2297        conditional_file: &ProjectFile,
2298        conditional_byte: usize,
2299    ) {
2300        // A conditional include can revisit an already-active guarded header.
2301        // If the possible branch defines the exact same macro, both outcomes
2302        // leave the binding unchanged; degrading it to Unknown would discard
2303        // proof because of an unrelated unresolved macro name (#2092).
2304        if environment.binding(name).is_some_and(|current| {
2305            current.definition != MacroDefinition::Unsupported
2306                && current.definition == possible_binding.definition
2307        }) {
2308            return;
2309        }
2310        environment.insert(
2311            name.to_string(),
2312            MacroBinding::ambiguous(conditional_file, conditional_byte),
2313        );
2314    }
2315
2316    pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
2317        let cell = self
2318            .macro_include_protection_cells
2319            .lock()
2320            .expect("C++ include protection cache poisoned")
2321            .entry(file.clone())
2322            .or_default()
2323            .clone();
2324        cell.get_or_init(|| {
2325            self.cpp
2326                .prepared_syntax(file)
2327                .map_or(MacroIncludeProtection::None, |prepared| {
2328                    top_level_macro_include_protection(
2329                        prepared.tree().root_node(),
2330                        prepared.source(),
2331                    )
2332                })
2333        })
2334        .clone()
2335    }
2336
2337    fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
2338        let Some(prepared) = self.cpp.prepared_syntax(file) else {
2339            return Vec::new();
2340        };
2341        let source = prepared.source();
2342        let mut events = Vec::new();
2343        let mut stack = vec![prepared.tree().root_node()];
2344        while let Some(node) = stack.pop() {
2345            let conditional = has_preprocessor_conditional_ancestor(node, source);
2346            match node.kind() {
2347                "preproc_def" | "preproc_function_def" => {
2348                    let Some(name) = node.child_by_field_name("name") else {
2349                        continue;
2350                    };
2351                    let name = node_text(name, source).to_string();
2352                    events.push(MacroEvent::Define {
2353                        name,
2354                        binding: MacroBinding {
2355                            source: file.clone(),
2356                            declaration_byte: node.start_byte(),
2357                            definition: Self::decode_macro_definition(node, source),
2358                            exact: true,
2359                        },
2360                        byte: node.start_byte(),
2361                        conditional,
2362                    });
2363                    continue;
2364                }
2365                "preproc_include" => {
2366                    let Some(path) = node.child_by_field_name("path") else {
2367                        events.push(MacroEvent::Include {
2368                            targets: Vec::new(),
2369                            byte: node.start_byte(),
2370                            conditional,
2371                        });
2372                        continue;
2373                    };
2374                    let targets =
2375                        structured_include_path(path, source).map_or_else(Vec::new, |path| {
2376                            resolve_include_targets_with_index(
2377                                file,
2378                                path,
2379                                self.cpp.include_target_index(),
2380                            )
2381                        });
2382                    // An unresolved angle-bracket include crosses into an external system
2383                    // boundary that is absent from the source index. It must not poison all
2384                    // later local macro evidence. Quoted/project-local and computed includes,
2385                    // by contrast, may hide indexed macro state and therefore fail closed.
2386                    if targets.is_empty() && path.kind() == "system_lib_string" {
2387                        continue;
2388                    }
2389                    events.push(MacroEvent::Include {
2390                        targets,
2391                        byte: node.start_byte(),
2392                        conditional,
2393                    });
2394                    continue;
2395                }
2396                "preproc_call" => {
2397                    let Some(directive) = node.child_by_field_name("directive") else {
2398                        continue;
2399                    };
2400                    if node_text(directive, source) != "#undef" {
2401                        continue;
2402                    }
2403                    let name = node
2404                        .child_by_field_name("argument")
2405                        .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
2406                    if let Some(name) = name {
2407                        events.push(MacroEvent::Undef {
2408                            name,
2409                            byte: node.start_byte(),
2410                            conditional,
2411                        });
2412                    } else {
2413                        events.push(MacroEvent::Invalidate {
2414                            byte: node.start_byte(),
2415                        });
2416                    }
2417                    continue;
2418                }
2419                _ => {}
2420            }
2421            for index in (0..node.named_child_count()).rev() {
2422                if let Some(child) = node.named_child(index) {
2423                    stack.push(child);
2424                }
2425            }
2426        }
2427        events.sort_by_key(MacroEvent::byte);
2428        events
2429    }
2430
2431    pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
2432        self.ordinary_type_import_cells
2433            .lock()
2434            .expect("C++ ordinary type import cache poisoned")
2435            .entry(file.clone())
2436            .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
2437            .clone()
2438    }
2439
2440    pub fn project_using_index(
2441        &self,
2442        build: impl FnOnce() -> ProjectUsingIndex,
2443    ) -> &ProjectUsingIndex {
2444        self.project_using_index.get_or_init(build)
2445    }
2446
2447    pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
2448        let mut files = self
2449            .visible_source_files_by_root
2450            .values()
2451            .flatten()
2452            .cloned()
2453            .collect::<HashSet<_>>()
2454            .into_iter()
2455            .collect::<Vec<_>>();
2456        files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
2457        files
2458    }
2459
2460    pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
2461        self.visible_source_files_by_root
2462            .get(root)
2463            .is_some_and(|files| files.contains(source))
2464    }
2465
2466    fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
2467        let cached = self
2468            .visible_parser_alias_name_sets
2469            .read()
2470            .expect("visible parser alias-name cache poisoned")
2471            .get(file)
2472            .cloned();
2473        let cell = if let Some(cached) = cached {
2474            cached
2475        } else {
2476            let mut cells = self
2477                .visible_parser_alias_name_sets
2478                .write()
2479                .expect("visible parser alias-name cache poisoned");
2480            Arc::clone(
2481                cells
2482                    .entry(file.clone())
2483                    .or_insert_with(|| Arc::new(OnceLock::new())),
2484            )
2485        };
2486        cell.get_or_init(|| {
2487            #[cfg(any(test, feature = "test-support"))]
2488            self.visible_parser_alias_name_set_build_count
2489                .fetch_add(1, Ordering::Relaxed);
2490            let mut names = HashSet::default();
2491            let visible_files = self
2492                .visible_source_files_by_root
2493                .get(file)
2494                .cloned()
2495                .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
2496            for visible_file in visible_files {
2497                let aliases = {
2498                    let mut cells = self.alias_cells.lock().expect("alias cell map lock");
2499                    Arc::clone(
2500                        cells
2501                            .entry(visible_file.clone())
2502                            .or_insert_with(|| Arc::new(OnceLock::new())),
2503                    )
2504                };
2505                for alias in aliases
2506                    .get_or_init(|| {
2507                        #[cfg(any(test, feature = "test-support"))]
2508                        {
2509                            *self
2510                                .alias_source_parse_counts
2511                                .lock()
2512                                .expect("alias source parse count lock")
2513                                .entry(visible_file.clone())
2514                                .or_default() += 1;
2515                        }
2516                        aliases_from_prepared_source(self.cpp, &visible_file).into_boxed_slice()
2517                    })
2518                    .iter()
2519                {
2520                    names.insert(alias.name.clone());
2521                }
2522            }
2523            names
2524        })
2525        .contains(name)
2526    }
2527
2528    fn visible_parser_alias_names_for_target(
2529        &self,
2530        file: &ProjectFile,
2531        target: &CodeUnit,
2532    ) -> HashSet<String> {
2533        let cell = {
2534            let mut cells = self
2535                .visible_parser_alias_target_names
2536                .lock()
2537                .expect("visible parser alias-target cache poisoned");
2538            Arc::clone(
2539                cells
2540                    .entry(file.clone())
2541                    .or_insert_with(|| Arc::new(OnceLock::new())),
2542            )
2543        };
2544        let target_name = cpp_name_for(target);
2545        cell.get_or_init(|| {
2546            #[cfg(any(test, feature = "test-support"))]
2547            self.visible_parser_alias_target_names_build_count
2548                .fetch_add(1, Ordering::Relaxed);
2549            let visible_files = self
2550                .visible_source_files_by_root
2551                .get(file)
2552                .cloned()
2553                .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
2554            let mut names_by_target = HashMap::<String, HashSet<String>>::default();
2555            for visible_file in visible_files {
2556                let aliases = {
2557                    let mut cells = self.alias_cells.lock().expect("alias cell map lock");
2558                    Arc::clone(
2559                        cells
2560                            .entry(visible_file.clone())
2561                            .or_insert_with(|| Arc::new(OnceLock::new())),
2562                    )
2563                };
2564                for alias in aliases
2565                    .get_or_init(|| {
2566                        #[cfg(any(test, feature = "test-support"))]
2567                        {
2568                            *self
2569                                .alias_source_parse_counts
2570                                .lock()
2571                                .expect("alias source parse count lock")
2572                                .entry(visible_file.clone())
2573                                .or_default() += 1;
2574                        }
2575                        aliases_from_prepared_source(self.cpp, &visible_file).into_boxed_slice()
2576                    })
2577                    .iter()
2578                {
2579                    for target_name in parser_alias_target_names(alias) {
2580                        names_by_target
2581                            .entry(target_name)
2582                            .or_default()
2583                            .insert(alias.name.clone());
2584                    }
2585                }
2586            }
2587            names_by_target
2588        })
2589        .get(&target_name)
2590        .cloned()
2591        .unwrap_or_default()
2592    }
2593
2594    fn callable_arities_for_target(
2595        &self,
2596        analyzer: &CppGraphSource<'_>,
2597        cpp: &dyn CppSource,
2598        file: &ProjectFile,
2599        prepared: &PreparedSyntaxTree,
2600        spec: &TargetSpec,
2601    ) -> Vec<ActivatedCallableArity> {
2602        let Some(signature) = spec.target.signature() else {
2603            return Vec::new();
2604        };
2605        let Some(candidates) = self
2606            .visible_by_identifier
2607            .get(file)
2608            .and_then(|by_name| by_name.get(&spec.member_name))
2609        else {
2610            return Vec::new();
2611        };
2612        let differing_candidates = candidates
2613            .iter()
2614            .filter(|candidate| {
2615                candidate.is_function()
2616                    && candidate.fq_name() == spec.target.fq_name()
2617                    && candidate.signature() == Some(signature)
2618            })
2619            .filter_map(|candidate| {
2620                analyzer
2621                    .signature_metadata(candidate)
2622                    .into_iter()
2623                    .find_map(|metadata| metadata.callable_arity())
2624                    .filter(|arity| Some(*arity) != spec.callable_arity)
2625                    .map(|arity| (candidate, arity))
2626            })
2627            .collect::<Vec<_>>();
2628        if differing_candidates.is_empty() {
2629            return Vec::new();
2630        }
2631        let mut arities = Vec::with_capacity(differing_candidates.len());
2632        // The activation ranges here describe the whole file rather than one
2633        // reference, so there is no reference guard environment to consult.
2634        let reference = CallableReferenceContext {
2635            file,
2636            position: None,
2637        };
2638        for (candidate, candidate_arity) in differing_candidates {
2639            let declaration_activation = if candidate.source() == file {
2640                callable_declaration_activation_in_file(analyzer, prepared, candidate, &reference)
2641            } else {
2642                cpp.prepared_syntax(candidate.source()).and_then(|syntax| {
2643                    callable_declaration_activation_in_file(
2644                        analyzer,
2645                        syntax.as_ref(),
2646                        candidate,
2647                        &reference,
2648                    )
2649                })
2650            };
2651            let Some(declaration_activation) = declaration_activation else {
2652                continue;
2653            };
2654            let activation_byte = if candidate.source() == file {
2655                Some(declaration_activation)
2656            } else {
2657                self.include_activation_for_source(cpp, file, prepared, candidate.source())
2658            };
2659            if let Some(activation_byte) = activation_byte {
2660                arities.push(ActivatedCallableArity {
2661                    activation_byte,
2662                    arity: candidate_arity,
2663                });
2664            }
2665        }
2666        arities
2667    }
2668
2669    fn callable_parameter_macro_arity(
2670        &self,
2671        target: &CodeUnit,
2672        signature: Option<&str>,
2673    ) -> Option<CallableArity> {
2674        let parameter_types = cpp_signature_param_types(signature?)?;
2675        let [macro_name] = parameter_types.as_slice() else {
2676            return None;
2677        };
2678        if macro_name.is_empty()
2679            || !macro_name
2680                .chars()
2681                .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
2682        {
2683            return None;
2684        }
2685        let cache_key = (target.source().clone(), macro_name.clone());
2686        if let Some(cached) = self
2687            .callable_parameter_macro_arities
2688            .lock()
2689            .expect("C++ callable parameter-macro arity cache poisoned")
2690            .get(&cache_key)
2691            .copied()
2692        {
2693            return cached;
2694        }
2695        let mut visible_files = HashSet::default();
2696        collect_include_closure(
2697            &self.cpp_source(),
2698            self.cpp.include_target_index(),
2699            target.source(),
2700            &mut visible_files,
2701            None,
2702        );
2703        let mut arities = Vec::new();
2704        for visible_file in visible_files {
2705            let cell = self.macro_event_cell(&visible_file);
2706            for event in
2707                cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
2708            {
2709                let MacroEvent::Define { name, binding, .. } = event else {
2710                    continue;
2711                };
2712                if name != macro_name {
2713                    continue;
2714                }
2715                let MacroDefinition::Object { replacement } = &binding.definition else {
2716                    continue;
2717                };
2718                let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
2719                    continue;
2720                };
2721                if !arities.contains(&arity) {
2722                    arities.push(arity);
2723                }
2724            }
2725        }
2726        let resolved = (|| {
2727            let required = arities
2728                .iter()
2729                .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
2730                .min()?;
2731            let total = arities.iter().map(|arity| arity.total()).max()?;
2732            let repeated = arities
2733                .iter()
2734                .any(|arity| arity.accepts(arity.total().saturating_add(1)));
2735            // Preprocessor conditions can leave more than one object-like parameter
2736            // bundle active in the target header's include closure. Preserve their
2737            // conservative callable envelope instead of choosing whichever definition
2738            // happened to be visited first.
2739            Some(CallableArity::new(required, total, repeated))
2740        })();
2741        self.callable_parameter_macro_arities
2742            .lock()
2743            .expect("C++ callable parameter-macro arity cache poisoned")
2744            .insert(cache_key, resolved);
2745        resolved
2746    }
2747
2748    pub fn include_activation_for_source(
2749        &self,
2750        cpp: &dyn CppSource,
2751        file: &ProjectFile,
2752        prepared: &PreparedSyntaxTree,
2753        donor_source: &ProjectFile,
2754    ) -> Option<usize> {
2755        let key = (file.clone(), donor_source.clone());
2756        if let Some(cached) = self
2757            .include_activation_cells
2758            .lock()
2759            .expect("C++ include activation cache poisoned")
2760            .get(&key)
2761            .copied()
2762        {
2763            return cached;
2764        }
2765        #[cfg(any(test, feature = "test-support"))]
2766        self.include_activation_build_count
2767            .fetch_add(1, Ordering::Relaxed);
2768        let activation = find_include_activation(cpp, file, prepared, donor_source);
2769        let mut cells = self
2770            .include_activation_cells
2771            .lock()
2772            .expect("C++ include activation cache poisoned");
2773        *cells.entry(key).or_insert(activation)
2774    }
2775
2776    pub fn conditional_include_projections_for_source(
2777        &self,
2778        file: &ProjectFile,
2779        prepared: &PreparedSyntaxTree,
2780        donor_source: &ProjectFile,
2781    ) -> Arc<[ConditionalIncludeProjection]> {
2782        static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
2783        let cell = self
2784            .conditional_include_projection_cells
2785            .lock()
2786            .expect("C++ conditional include projection cache poisoned")
2787            .entry(file.clone())
2788            .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
2789            .clone();
2790        let index = cell.get_or_build_pool_independent(|| {
2791            #[cfg(any(test, feature = "test-support"))]
2792            self.conditional_include_projection_index_build_count
2793                .fetch_add(1, Ordering::Relaxed);
2794            find_conditional_include_projection_index(self.cpp, file, prepared, &|| {
2795                #[cfg(any(test, feature = "test-support"))]
2796                self.conditional_include_projection_state_count
2797                    .fetch_add(1, Ordering::Relaxed);
2798            })
2799        });
2800        index
2801            .get(donor_source)
2802            .cloned()
2803            .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
2804    }
2805
2806    #[cfg(any(test, feature = "test-support"))]
2807    pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
2808        (
2809            self.conditional_include_projection_index_build_count
2810                .load(Ordering::Relaxed),
2811            self.conditional_include_projection_state_count
2812                .load(Ordering::Relaxed),
2813        )
2814    }
2815
2816    #[cfg(any(test, feature = "test-support"))]
2817    pub fn include_activation_build_count_for_test(&self) -> usize {
2818        self.include_activation_build_count.load(Ordering::Relaxed)
2819    }
2820
2821    #[cfg(any(test, feature = "test-support"))]
2822    pub fn note_using_donor_activation_for_test(&self) {
2823        self.using_donor_activation_count
2824            .fetch_add(1, Ordering::Relaxed);
2825    }
2826
2827    #[cfg(not(any(test, feature = "test-support")))]
2828    pub fn note_using_donor_activation_for_test(&self) {}
2829
2830    #[cfg(any(test, feature = "test-support"))]
2831    pub fn note_using_namespace_lookup_for_test(&self) {
2832        self.using_namespace_lookup_count
2833            .fetch_add(1, Ordering::Relaxed);
2834    }
2835
2836    #[cfg(not(any(test, feature = "test-support")))]
2837    pub fn note_using_namespace_lookup_for_test(&self) {}
2838
2839    #[cfg(any(test, feature = "test-support"))]
2840    pub fn note_using_name_candidate_inspection_for_test(&self) {
2841        self.using_name_candidate_inspection_count
2842            .fetch_add(1, Ordering::Relaxed);
2843    }
2844
2845    #[cfg(not(any(test, feature = "test-support")))]
2846    pub fn note_using_name_candidate_inspection_for_test(&self) {}
2847
2848    #[cfg(any(test, feature = "test-support"))]
2849    pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
2850        (
2851            self.using_donor_activation_count.load(Ordering::Relaxed),
2852            self.using_namespace_lookup_count.load(Ordering::Relaxed),
2853            self.callable_reference_spec_build_count
2854                .load(Ordering::Relaxed),
2855            self.using_name_candidate_inspection_count
2856                .load(Ordering::Relaxed),
2857        )
2858    }
2859
2860    pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2861        file == target.source()
2862            || self
2863                .visible_by_file
2864                .get(file)
2865                .is_some_and(|visible| visible.contains(target))
2866    }
2867
2868    pub fn declaration_visible_at(
2869        &self,
2870        analyzer: &CppGraphSource<'_>,
2871        file: &ProjectFile,
2872        declaration: &CodeUnit,
2873        reference_byte: usize,
2874    ) -> bool {
2875        let reference_guards = OnceCell::new();
2876        self.visible_identifier_candidates(file, declaration.identifier())
2877            .filter(|candidate| {
2878                same_logical_symbol(candidate, declaration)
2879                    || flattened_macro_namespace_declaration_matches(
2880                        analyzer,
2881                        self.cpp,
2882                        file,
2883                        candidate,
2884                        declaration,
2885                        reference_byte,
2886                    )
2887            })
2888            .any(|candidate| {
2889                self.physical_declaration_visible_at(
2890                    analyzer,
2891                    file,
2892                    candidate,
2893                    reference_byte,
2894                    &reference_guards,
2895                )
2896            })
2897    }
2898
2899    pub fn callable_arity_at_reference(
2900        &self,
2901        analyzer: &CppGraphSource<'_>,
2902        file: &ProjectFile,
2903        candidate: &CodeUnit,
2904        reference_byte: usize,
2905    ) -> Option<CallableArity> {
2906        let key = (file.clone(), logical_symbol_key(candidate));
2907        let cell = self
2908            .callable_reference_specs
2909            .lock()
2910            .expect("C++ callable reference-spec cache poisoned")
2911            .entry(key)
2912            .or_default()
2913            .clone();
2914        let spec = cell.get_or_init(|| {
2915            let prepared = self.cpp.prepared_syntax(file)?;
2916            let spec = TargetSpec::from_target(analyzer, candidate)?;
2917            let spec = spec
2918                .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
2919                .into_owned();
2920            #[cfg(any(test, feature = "test-support"))]
2921            self.callable_reference_spec_build_count
2922                .fetch_add(1, Ordering::Relaxed);
2923            Some(spec)
2924        });
2925        spec.as_ref()?.callable_arity_at(reference_byte)
2926    }
2927
2928    fn physical_declaration_visible_at(
2929        &self,
2930        analyzer: &CppGraphSource<'_>,
2931        file: &ProjectFile,
2932        declaration: &CodeUnit,
2933        reference_byte: usize,
2934        reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
2935    ) -> bool {
2936        let Some(prepared) = self.cpp.prepared_syntax(file) else {
2937            return false;
2938        };
2939        let reference = CallableReferenceContext {
2940            file,
2941            position: Some(CallableReferencePosition {
2942                prepared: prepared.as_ref(),
2943                byte: reference_byte,
2944                guards: reference_guards,
2945            }),
2946        };
2947        if declaration.source() == file {
2948            return callable_declaration_activation_in_file(
2949                analyzer,
2950                prepared.as_ref(),
2951                declaration,
2952                &reference,
2953            )
2954            .or_else(|| {
2955                self.exhaustive_guard_family_activation(
2956                    analyzer,
2957                    prepared.as_ref(),
2958                    declaration,
2959                    &reference,
2960                )
2961            })
2962            .is_some_and(|activation| activation < reference_byte);
2963        }
2964        let Some(donor_syntax) = self.cpp.prepared_syntax(declaration.source()) else {
2965            return false;
2966        };
2967        if callable_declaration_activation_in_file(
2968            analyzer,
2969            donor_syntax.as_ref(),
2970            declaration,
2971            &reference,
2972        )
2973        .or_else(|| {
2974            self.exhaustive_guard_family_activation(
2975                analyzer,
2976                donor_syntax.as_ref(),
2977                declaration,
2978                &reference,
2979            )
2980        })
2981        .is_none()
2982        {
2983            return false;
2984        }
2985        declaration_guard_requirements(analyzer, self.cpp, declaration)
2986            .into_iter()
2987            .any(|(_, declaration_guards)| {
2988                self.foreign_declaration_reachable_at_reference(
2989                    file,
2990                    prepared.as_ref(),
2991                    declaration.source(),
2992                    &declaration_guards,
2993                    reference.guards(),
2994                    reference_byte,
2995                )
2996            })
2997    }
2998
2999    pub fn external_type_candidate_visible_at(
3000        &self,
3001        file: &ProjectFile,
3002        candidate: &CodeUnit,
3003        reference_byte: usize,
3004    ) -> bool {
3005        if candidate.source() == file {
3006            return true;
3007        }
3008        let Some(prepared) = self.cpp.prepared_syntax(file) else {
3009            return false;
3010        };
3011        self.visible_identifier_candidates(file, candidate.identifier())
3012            .filter(|peer| same_logical_symbol(candidate, peer))
3013            .any(|peer| {
3014                peer.source() == file
3015                    || self
3016                        .include_activation_for_source(
3017                            self.cpp,
3018                            file,
3019                            prepared.as_ref(),
3020                            peer.source(),
3021                        )
3022                        .is_some_and(|activation| activation <= reference_byte)
3023            })
3024    }
3025
3026    pub fn external_type_declaration_visible_at(
3027        &self,
3028        file: &ProjectFile,
3029        candidate: &CodeUnit,
3030        reference_byte: usize,
3031    ) -> bool {
3032        if candidate.source() == file {
3033            return true;
3034        }
3035        let Some(prepared) = self.cpp.prepared_syntax(file) else {
3036            return false;
3037        };
3038        self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
3039            .is_some_and(|activation| activation <= reference_byte)
3040    }
3041
3042    /// Decide whether a declaration that lives in another file reaches a
3043    /// reference in `file`.
3044    ///
3045    /// An external header selects its declaration branch before the reference
3046    /// file is parsed. Require compatible reference guards, but do not test
3047    /// the header's guard expression for stability in the reference file: a
3048    /// `.c` translation unit can never satisfy the `#ifdef __cplusplus` that
3049    /// wraps every declaration of a portable C header, and demanding it would
3050    /// hide the whole header. Guards that the reference file imposes on its
3051    /// own `#include` still have to hold, and still have to be stable.
3052    fn foreign_declaration_reachable_at_reference(
3053        &self,
3054        file: &ProjectFile,
3055        prepared: &PreparedSyntaxTree,
3056        declaration_source: &ProjectFile,
3057        declaration_guards: &HashSet<PreprocessorGuard>,
3058        reference_guards: Option<&HashSet<PreprocessorGuard>>,
3059        reference_byte: usize,
3060    ) -> bool {
3061        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
3062            return false;
3063        }
3064        if self
3065            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
3066            .is_some_and(|activation| activation <= reference_byte)
3067        {
3068            return true;
3069        }
3070        self.conditional_include_projections_for_source(file, prepared, declaration_source)
3071            .iter()
3072            .any(|projection| {
3073                projection.activation_byte <= reference_byte
3074                    && guard_requirements_hold_at_reference(
3075                        &projection.required_guards,
3076                        reference_guards,
3077                    )
3078                    && self.preprocessor_guards_stable_between(
3079                        file,
3080                        projection.activation_byte,
3081                        reference_byte,
3082                        &projection.required_guards,
3083                    )
3084            })
3085    }
3086
3087    pub fn external_type_candidate_visible_in_context(
3088        &self,
3089        analyzer: &CppGraphSource<'_>,
3090        file: &ProjectFile,
3091        candidate: &CodeUnit,
3092        reference: Node<'_>,
3093    ) -> bool {
3094        let Some(prepared) = self.cpp.prepared_syntax(file) else {
3095            return false;
3096        };
3097        let macro_environment = self.macro_environment(file, reference.start_byte());
3098        let reference_guards = preprocessor_guard_environment(reference, prepared.source())
3099            .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
3100
3101        let directly_visible = self
3102            .visible_identifier_candidates(file, candidate.identifier())
3103            .filter(|peer| same_logical_symbol(candidate, peer))
3104            .any(|peer| {
3105                declaration_guard_requirements(analyzer, self.cpp, peer)
3106                    .into_iter()
3107                    .any(|(declaration_byte, declaration_guards)| {
3108                        if peer.source() == file {
3109                            return declaration_byte < reference.start_byte()
3110                                && guard_requirements_hold_at_reference(
3111                                    &declaration_guards,
3112                                    reference_guards.as_ref(),
3113                                )
3114                                && self.preprocessor_guards_stable_between(
3115                                    file,
3116                                    declaration_byte,
3117                                    reference.start_byte(),
3118                                    &declaration_guards,
3119                                );
3120                        }
3121                        self.foreign_declaration_reachable_at_reference(
3122                            file,
3123                            prepared.as_ref(),
3124                            peer.source(),
3125                            &declaration_guards,
3126                            reference_guards.as_ref(),
3127                            reference.start_byte(),
3128                        )
3129                    })
3130            });
3131        let complementary = self
3132            .visible_identifier_candidates(file, candidate.identifier())
3133            .filter(|peer| {
3134                peer.kind() == candidate.kind()
3135                    && peer.fq_name() == candidate.fq_name()
3136                    && peer.source() == candidate.source()
3137            })
3138            .collect::<Vec<_>>();
3139        // A completed #if/#else family declares the shared source-level name
3140        // before this reference. A later macro mutation cannot revoke that
3141        // declaration. The family gate below rejects declarations split across
3142        // separate conditional blocks, where mutation can change coverage.
3143        let candidate_branch_compatible = reference_guards.as_ref().is_some_and(|active| {
3144            declaration_guard_requirements(analyzer, self.cpp, candidate)
3145                .iter()
3146                .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
3147        });
3148        let complementary_visible = candidate_branch_compatible
3149            && self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate)
3150            && if candidate.source() == file {
3151                declaration_guard_requirements(analyzer, self.cpp, candidate)
3152                    .iter()
3153                    .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
3154            } else {
3155                self.include_activation_for_source(
3156                    self.cpp,
3157                    file,
3158                    prepared.as_ref(),
3159                    candidate.source(),
3160                )
3161                .is_some_and(|activation| activation <= reference.start_byte())
3162            };
3163        directly_visible || complementary_visible
3164    }
3165
3166    pub fn is_exhaustive_same_fqn_type_declaration_family(
3167        &self,
3168        analyzer: &CppGraphSource<'_>,
3169        file: &ProjectFile,
3170        candidate: &CodeUnit,
3171    ) -> bool {
3172        let candidates = self
3173            .visible_identifier_candidates(file, candidate.identifier())
3174            .filter(|peer| {
3175                peer.kind() == candidate.kind()
3176                    && peer.fq_name() == candidate.fq_name()
3177                    && peer.source() == candidate.source()
3178            })
3179            .collect::<Vec<_>>();
3180        self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
3181    }
3182
3183    /// Prove a nested type alias used as a dependent member-pointer owner when
3184    /// its owning class has mutually-exclusive declarations.  A common C++11
3185    /// compatibility shape provides the owning class in one preprocessor
3186    /// branch and aliases it to a standard-library type in the other branch;
3187    /// the nested fallback alias is therefore not itself active in every
3188    /// branch even though the qualified owner API is.
3189    ///
3190    /// This is deliberately narrower than ordinary type visibility.  The
3191    /// caller has already recovered a member-pointer owner path from the CST;
3192    /// this helper additionally requires the target's structured parent to
3193    /// match that path, physical source visibility, and exact preprocessor
3194    /// guard agreement with the parent declaration.  Only then may the
3195    /// parent's direct/complementary same-FQN visibility stand in for the
3196    /// nested terminal's active-branch check.
3197    pub fn dependent_member_pointer_alias_visible_in_context(
3198        &self,
3199        analyzer: &CppGraphSource<'_>,
3200        file: &ProjectFile,
3201        candidate: &CodeUnit,
3202        owner_components: &[String],
3203        reference: Node<'_>,
3204    ) -> bool {
3205        if !analyzer
3206            .type_alias_provider()
3207            .is_some_and(|provider| provider.is_type_alias(candidate))
3208        {
3209            return false;
3210        }
3211        let Some((terminal, owner_prefix)) = owner_components.split_last() else {
3212            return false;
3213        };
3214        if terminal != candidate.identifier()
3215            || canonical_cpp_scope_components(candidate) != owner_components
3216        {
3217            return false;
3218        }
3219        let Some(expected_parent_fq_name) =
3220            brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
3221        else {
3222            return false;
3223        };
3224        let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
3225            return false;
3226        };
3227        if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
3228            || parent_anchor.source() != candidate.source()
3229            || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
3230        {
3231            return false;
3232        }
3233
3234        // The ordinary path already handles unguarded aliases (and preserves
3235        // same-file declaration ordering).  This fallback is only for a
3236        // physically visible declaration whose guard is the owning branch's
3237        // guard, so reject a same-file declaration that appears after the
3238        // reference before considering guard compatibility.
3239        if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
3240            || candidate.source() == file
3241                && !analyzer
3242                    .ranges(candidate)
3243                    .iter()
3244                    .any(|range| range.start_byte < reference.start_byte())
3245        {
3246            return false;
3247        }
3248
3249        let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
3250        if candidate_guards.is_empty() {
3251            return false;
3252        }
3253        let same_guard_sets =
3254            |left: &[(usize, HashSet<PreprocessorGuard>)],
3255             right: &[(usize, HashSet<PreprocessorGuard>)]| {
3256                left.iter().all(|(_, left_guards)| {
3257                    right
3258                        .iter()
3259                        .any(|(_, right_guards)| left_guards == right_guards)
3260                })
3261            };
3262        let parent_candidates = self
3263            .visible_identifier_candidates(file, parent_anchor.identifier())
3264            .filter(|peer| {
3265                peer.kind() == parent_anchor.kind()
3266                    && peer.fq_name() == expected_parent_fq_name.as_str()
3267                    && peer.source() == parent_anchor.source()
3268                    && canonical_cpp_scope_components(peer) == owner_prefix
3269            })
3270            .filter_map(|peer| {
3271                let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
3272                (candidate_guards.len() == parent_guards.len()
3273                    && same_guard_sets(&candidate_guards, &parent_guards)
3274                    && same_guard_sets(&parent_guards, &candidate_guards))
3275                .then(|| (peer.clone(), parent_guards))
3276            })
3277            .collect::<Vec<_>>();
3278        let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
3279            return false;
3280        };
3281
3282        let Some(prepared) = self.cpp.prepared_syntax(file) else {
3283            return false;
3284        };
3285        let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
3286        else {
3287            return false;
3288        };
3289        // An external header selects its declaration branch before the
3290        // reference file is parsed. Require compatible reference guards, but
3291        // do not test the header's guard expression for stability in the
3292        // reference file. Same-file aliases still require that stability.
3293        if !candidate_guards.iter().any(|(_, target_guards)| {
3294            guards_compatible_at_reference(target_guards, Some(&reference_guards))
3295                && (candidate.source() != file
3296                    || self.preprocessor_guards_stable_between(
3297                        file,
3298                        0,
3299                        reference.start_byte(),
3300                        target_guards,
3301                    ))
3302        }) {
3303            return false;
3304        }
3305
3306        self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
3307    }
3308
3309    /// Check a type candidate's preprocessor/import context without imposing
3310    /// ordinary declaration-before-reference ordering for same-file peers.
3311    ///
3312    /// C++ class scope makes member names visible throughout the complete
3313    /// class, including a trailing return type that appears before the member
3314    /// alias declaration in source order. Callers must first prove that the
3315    /// reference is inside the candidate's indexed class owner; this helper
3316    /// only relaxes the byte-order predicate while retaining guard and include
3317    /// activation checks.
3318    pub fn external_type_candidate_guard_compatible_in_context(
3319        &self,
3320        analyzer: &CppGraphSource<'_>,
3321        file: &ProjectFile,
3322        candidate: &CodeUnit,
3323        reference: Node<'_>,
3324    ) -> bool {
3325        let Some(prepared) = self.cpp.prepared_syntax(file) else {
3326            return false;
3327        };
3328        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
3329
3330        self.visible_identifier_candidates(file, candidate.identifier())
3331            .filter(|peer| same_logical_symbol(candidate, peer))
3332            .any(|peer| {
3333                declaration_guard_requirements(analyzer, self.cpp, peer)
3334                    .into_iter()
3335                    .any(|(declaration_byte, declaration_guards)| {
3336                        if peer.source() == file {
3337                            let (start, end) = if declaration_byte <= reference.start_byte() {
3338                                (declaration_byte, reference.start_byte())
3339                            } else {
3340                                (reference.start_byte(), declaration_byte)
3341                            };
3342                            return guard_requirements_hold_at_reference(
3343                                &declaration_guards,
3344                                reference_guards.as_ref(),
3345                            ) && self.preprocessor_guards_stable_between(
3346                                file,
3347                                start,
3348                                end,
3349                                &declaration_guards,
3350                            );
3351                        }
3352                        self.foreign_declaration_reachable_at_reference(
3353                            file,
3354                            prepared.as_ref(),
3355                            peer.source(),
3356                            &declaration_guards,
3357                            reference_guards.as_ref(),
3358                            reference.start_byte(),
3359                        )
3360                    })
3361            })
3362    }
3363
3364    pub fn type_candidate_may_be_visible_before_reference(
3365        &self,
3366        analyzer: &CppGraphSource<'_>,
3367        file: &ProjectFile,
3368        candidate: &CodeUnit,
3369        reference_byte: usize,
3370    ) -> bool {
3371        let Some(prepared) = self.cpp.prepared_syntax(file) else {
3372            return false;
3373        };
3374        let root = prepared.tree().root_node();
3375        let end_byte = reference_byte
3376            .saturating_add(1)
3377            .min(prepared.source().len());
3378        let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
3379            return false;
3380        };
3381        self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
3382    }
3383
3384    pub fn preprocessor_guards_stable_between(
3385        &self,
3386        file: &ProjectFile,
3387        start_byte: usize,
3388        end_byte: usize,
3389        guards: &HashSet<PreprocessorGuard>,
3390    ) -> bool {
3391        if guards.is_empty() || start_byte >= end_byte {
3392            return true;
3393        }
3394        let cell = self.macro_event_cell(file);
3395        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3396        let mut visited = HashSet::from_iter([file.clone()]);
3397        !events.iter().any(|event| {
3398            event.byte() >= start_byte
3399                && event.byte() < end_byte
3400                && self.macro_event_may_mutate_guards(event, guards, &mut visited)
3401        })
3402    }
3403
3404    fn macro_event_may_mutate_guards(
3405        &self,
3406        event: &MacroEvent,
3407        guards: &HashSet<PreprocessorGuard>,
3408        visited: &mut HashSet<ProjectFile>,
3409    ) -> bool {
3410        match event {
3411            MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
3412                guards.iter().any(|guard| guard.may_depend_on_macro(name))
3413            }
3414            MacroEvent::Include { targets, .. } => {
3415                targets.is_empty()
3416                    || targets
3417                        .iter()
3418                        .any(|target| self.source_may_mutate_guards(target, guards, visited))
3419            }
3420            MacroEvent::Invalidate { .. } => true,
3421        }
3422    }
3423
3424    fn source_may_mutate_guards(
3425        &self,
3426        file: &ProjectFile,
3427        guards: &HashSet<PreprocessorGuard>,
3428        visited: &mut HashSet<ProjectFile>,
3429    ) -> bool {
3430        if !visited.insert(file.clone()) {
3431            return false;
3432        }
3433        let cell = self.macro_event_cell(file);
3434        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3435        events
3436            .iter()
3437            .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
3438    }
3439
3440    pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
3441        let normalized = normalize_reference_name(raw_name)?;
3442        self.type_candidates(file, &normalized)
3443            .into_iter()
3444            .next()
3445            .cloned()
3446    }
3447
3448    /// Mirror forward navigation's visible-name fallback for a bare parameter
3449    /// type after lexical owner and inheritance lookup is exhausted.
3450    ///
3451    /// Generated or otherwise unindexed base classes can hide the alias that
3452    /// makes a parameter type valid C++. Accept the fallback only when every
3453    /// include-visible class or alias with that spelling canonicalizes to one
3454    /// logical type. A shadowing local type resolves lexically before this
3455    /// path, while distinct visible types keep the result ambiguous.
3456    pub fn unique_visible_parameter_type_fallback(
3457        &self,
3458        analyzer: &CppGraphSource<'_>,
3459        file: &ProjectFile,
3460        node: Node<'_>,
3461        source: &str,
3462    ) -> Option<CodeUnit> {
3463        if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
3464            return None;
3465        }
3466        let name = node_text(node, source);
3467        let candidates = self
3468            .visible_identifier_candidates(file, name)
3469            .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
3470            .filter(|candidate| {
3471                self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
3472            })
3473            .collect::<Vec<_>>();
3474        self.unique_canonical_type_candidate(analyzer, file, &candidates)
3475    }
3476
3477    pub fn resolve_type_node_result(
3478        &self,
3479        file: &ProjectFile,
3480        node: Node<'_>,
3481        source: &str,
3482    ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
3483        let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
3484            return Ok(None);
3485        };
3486        let Some(arguments) = cpp_template_reference_arguments(node, source) else {
3487            return Ok(Some(primary));
3488        };
3489        self.resolve_template_arguments(file, primary, &arguments)
3490            .map(Some)
3491    }
3492
3493    pub fn resolve_type_node_primary(
3494        &self,
3495        file: &ProjectFile,
3496        node: Node<'_>,
3497        source: &str,
3498    ) -> Option<CodeUnit> {
3499        let components = cpp_type_name_components(node, source)?;
3500        self.resolve_type(file, &components.join("::"))
3501    }
3502
3503    pub fn resolve_template_arguments(
3504        &self,
3505        file: &ProjectFile,
3506        primary: CodeUnit,
3507        arguments: &[CppTemplateExpression],
3508    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
3509        self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
3510    }
3511
3512    fn resolve_template_arguments_inner(
3513        &self,
3514        file: &ProjectFile,
3515        primary: CodeUnit,
3516        arguments: &[CppTemplateExpression],
3517        seen_aliases: &mut HashSet<CodeUnit>,
3518    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
3519        if let Some(metadata) = self.cpp_template_metadata.get(&primary)
3520            && let Some(alias_target) = &metadata.alias_target
3521        {
3522            if !seen_aliases.insert(primary.clone()) {
3523                return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
3524            }
3525            let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
3526                .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
3527            let target_name = alias_target.components.join("::");
3528            let target_primary = if alias_target.global {
3529                unique_logical_type_candidate(self.type_candidates(file, &target_name))
3530            } else {
3531                self.resolve_unique_type_for_declaration(file, &primary, &target_name)
3532            };
3533            let Some(target_primary) = target_primary else {
3534                // A dependent or external RHS cannot be canonicalized from the
3535                // indexed graph. Preserve the alias's direct identity instead
3536                // of inventing a target from its source spelling.
3537                return Ok(primary);
3538            };
3539            let Some(target_arguments) = &alias_target.arguments else {
3540                return Ok(target_primary);
3541            };
3542            let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
3543                .ok_or(CppTemplateResolutionError::Substitution)?;
3544            return self.resolve_template_arguments_inner(
3545                file,
3546                target_primary,
3547                &target_arguments,
3548                seen_aliases,
3549            );
3550        }
3551
3552        let primary_fq_name = self
3553            .cpp_template_metadata
3554            .get(&primary)
3555            .map(|metadata| metadata.primary_fq_name.clone())
3556            .unwrap_or_else(|| primary.fq_name());
3557        let has_specialization_metadata = self
3558            .cpp_template_families
3559            .get(&primary_fq_name)
3560            .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
3561        if !has_specialization_metadata {
3562            return Ok(primary);
3563        }
3564        self.select_template_specialization(file, &primary, arguments)
3565    }
3566
3567    fn select_template_specialization(
3568        &self,
3569        file: &ProjectFile,
3570        resolved: &CodeUnit,
3571        explicit_arguments: &[CppTemplateExpression],
3572    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
3573        let primary_fq_name = self
3574            .cpp_template_metadata
3575            .get(resolved)
3576            .map(|metadata| metadata.primary_fq_name.clone())
3577            .unwrap_or_else(|| resolved.fq_name());
3578        let family = self
3579            .cpp_template_families
3580            .get(&primary_fq_name)
3581            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
3582        let primary_candidates = family
3583            .iter()
3584            .filter_map(|unit| {
3585                let metadata = self.cpp_template_metadata.get(unit)?;
3586                (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
3587            })
3588            .collect::<Vec<_>>();
3589        let primary_unit = primary_candidates
3590            .iter()
3591            .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
3592            .or_else(|| {
3593                primary_candidates
3594                    .iter()
3595                    .map(|(unit, _)| *unit)
3596                    .min_by_key(|unit| {
3597                        (
3598                            unit.source().to_string(),
3599                            unit.signature().unwrap_or_default(),
3600                        )
3601                    })
3602            })
3603            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
3604        let primary_parameters =
3605            cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
3606                .ok_or(CppTemplateResolutionError::PrimarySelection)?;
3607        let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
3608            .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
3609
3610        let mut applicable = Vec::new();
3611        for unit in family {
3612            let Some(metadata) = self.cpp_template_metadata.get(unit) else {
3613                continue;
3614            };
3615            if metadata.is_primary() || !self.is_visible(file, unit) {
3616                continue;
3617            }
3618            if !cpp_specialization_matches(metadata, &expanded) {
3619                continue;
3620            }
3621            applicable.push((unit, metadata));
3622        }
3623        if applicable.is_empty() {
3624            return Ok(primary_unit.clone());
3625        }
3626
3627        // A scalar constraint count cannot represent C++ partial ordering:
3628        // e.g. `<T*, U>` and `<T, int>` are incomparable for `<int*, int>`.
3629        // Select only a logical candidate whose structural pattern is strictly
3630        // more specialized than every other distinct applicable candidate.
3631        let winners = applicable
3632            .iter()
3633            .filter(|(candidate, candidate_metadata)| {
3634                applicable.iter().all(|(other, other_metadata)| {
3635                    same_visible_symbol(candidate, other)
3636                        || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
3637                })
3638            })
3639            .copied()
3640            .collect::<Vec<_>>();
3641        let Some((selected, _)) = winners.first() else {
3642            // Mutually incomparable applicable candidates: every one of them
3643            // is a live contender.
3644            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
3645                candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
3646            });
3647        };
3648        if winners
3649            .iter()
3650            .any(|(unit, _)| !same_visible_symbol(unit, selected))
3651        {
3652            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
3653                candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
3654            });
3655        }
3656        Ok((*selected).clone())
3657    }
3658
3659    pub fn resolve_type_components_lexically(
3660        &self,
3661        analyzer: &CppGraphSource<'_>,
3662        file: &ProjectFile,
3663        components: &[String],
3664        global: bool,
3665        lexical_scope: &[String],
3666    ) -> LexicalTypeResolution {
3667        self.resolve_type_components_lexically_inner(
3668            analyzer,
3669            file,
3670            components,
3671            global,
3672            lexical_scope,
3673            TypeCandidateResolution::Canonical,
3674        )
3675    }
3676
3677    pub fn resolve_type_components_lexically_for_forward(
3678        &self,
3679        analyzer: &CppGraphSource<'_>,
3680        file: &ProjectFile,
3681        components: &[String],
3682        global: bool,
3683        lexical_scope: &[String],
3684    ) -> LexicalTypeResolution {
3685        self.resolve_type_components_lexically_inner(
3686            analyzer,
3687            file,
3688            components,
3689            global,
3690            lexical_scope,
3691            TypeCandidateResolution::PreserveAlias,
3692        )
3693    }
3694
3695    pub fn resolve_type_components_lexically_for_target(
3696        &self,
3697        analyzer: &CppGraphSource<'_>,
3698        file: &ProjectFile,
3699        components: &[String],
3700        global: bool,
3701        lexical_scope: &[String],
3702        target: &CodeUnit,
3703    ) -> LexicalTypeResolution {
3704        #[cfg(any(test, feature = "test-support"))]
3705        self.target_preserving_type_resolution_count
3706            .fetch_add(1, Ordering::Relaxed);
3707        self.resolve_type_components_lexically_inner(
3708            analyzer,
3709            file,
3710            components,
3711            global,
3712            lexical_scope,
3713            TypeCandidateResolution::PreserveTarget(target),
3714        )
3715    }
3716
3717    pub fn coarse_unqualified_type_reference_may_resolve(
3718        &self,
3719        file: &ProjectFile,
3720        name: &str,
3721    ) -> bool {
3722        if name.is_empty() {
3723            return true;
3724        }
3725        self.visible_identifier_candidates(file, name)
3726            .any(|candidate| candidate.kind() == CodeUnitType::Class || is_type_alias(candidate))
3727            || self.visible_parser_alias_name_is_visible(file, name)
3728    }
3729
3730    #[allow(clippy::too_many_arguments)]
3731    pub fn structured_type_reference_may_resolve_to_target(
3732        &self,
3733        analyzer: &CppGraphSource<'_>,
3734        file: &ProjectFile,
3735        components: &[String],
3736        global: bool,
3737        lexical_scope: &[String],
3738        target: &CodeUnit,
3739    ) -> bool {
3740        if components.is_empty() {
3741            return true;
3742        }
3743        let Some(terminal) = components.last() else {
3744            return true;
3745        };
3746        let parser_alias_visible = self.visible_parser_alias_name_is_visible(file, terminal);
3747        if parser_alias_visible
3748            && self.parser_alias_resolves_to_type(analyzer, file, terminal, target)
3749        {
3750            return true;
3751        }
3752        let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
3753            .map(|qualified| qualified.join("::"))
3754            .collect::<Vec<_>>();
3755        let target_name = cpp_name_for(target);
3756        if qualified_tiers
3757            .iter()
3758            .any(|qualified| qualified == &target_name)
3759        {
3760            return true;
3761        }
3762
3763        let mut saw_shape_candidate = parser_alias_visible;
3764        for candidate in self.visible_identifier_candidates(file, terminal) {
3765            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
3766            {
3767                continue;
3768            }
3769            let candidate_name = cpp_name_for(candidate);
3770            let shape_matches = if global || components.len() > 1 {
3771                qualified_tiers
3772                    .iter()
3773                    .any(|qualified| qualified == &candidate_name)
3774            } else {
3775                true
3776            };
3777            if !shape_matches {
3778                continue;
3779            }
3780            saw_shape_candidate = true;
3781            if same_visible_symbol(candidate, target)
3782                || self.compatible_primary_template_redeclarations(candidate, target)
3783                || (declared_type_alias(analyzer, candidate)
3784                    && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
3785            {
3786                return true;
3787            }
3788        }
3789
3790        !saw_shape_candidate
3791    }
3792
3793    pub fn target_preserving_reference_namespace(
3794        &self,
3795        analyzer: &CppGraphSource<'_>,
3796        file: &ProjectFile,
3797        identifier: &str,
3798        target: &CodeUnit,
3799    ) -> Option<Vec<String>> {
3800        let mut namespace = None;
3801        for candidate in self.visible_identifier_candidates(file, identifier) {
3802            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
3803            {
3804                continue;
3805            }
3806            if !(same_visible_symbol(candidate, target)
3807                || self.compatible_primary_template_redeclarations(candidate, target)
3808                || declared_type_alias(analyzer, candidate)
3809                    && self.structured_alias_primary_preserves_target(
3810                        analyzer, file, candidate, target,
3811                    ))
3812            {
3813                continue;
3814            }
3815            if namespace
3816                .as_ref()
3817                .is_some_and(|existing| existing != candidate.package_name())
3818            {
3819                return None;
3820            }
3821            namespace = Some(candidate.package_name().to_string());
3822        }
3823        let namespace = namespace?;
3824        Some(
3825            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
3826                brokk_bifrost_core::analyzer::Language::Cpp,
3827                &namespace,
3828            ),
3829        )
3830    }
3831
3832    pub fn resolve_imported_type_candidate(
3833        &self,
3834        analyzer: &CppGraphSource<'_>,
3835        file: &ProjectFile,
3836        target: &CodeUnit,
3837        target_components: &[String],
3838        direct_target: Option<&CodeUnit>,
3839        preserve_alias: bool,
3840    ) -> LexicalTypeResolution {
3841        let candidates = [target];
3842        let resolution = if preserve_alias {
3843            TypeCandidateResolution::PreserveAlias
3844        } else {
3845            direct_target.map_or(
3846                TypeCandidateResolution::Canonical,
3847                TypeCandidateResolution::PreserveTarget,
3848            )
3849        };
3850        // One candidate goes in, so a failure here is never "choose one of
3851        // these": it is the alias chain leaving the index, which must answer
3852        // missing rather than ambiguous (#1828).
3853        match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
3854            Ok(unit) => LexicalTypeResolution::Resolved {
3855                unit,
3856                components: target_components.to_vec(),
3857                candidates: vec![target.clone()],
3858            },
3859            Err(failure) => failure.lexical_resolution(),
3860        }
3861    }
3862
3863    fn resolve_type_components_lexically_inner(
3864        &self,
3865        analyzer: &CppGraphSource<'_>,
3866        file: &ProjectFile,
3867        components: &[String],
3868        global: bool,
3869        lexical_scope: &[String],
3870        resolution: TypeCandidateResolution<'_>,
3871    ) -> LexicalTypeResolution {
3872        if components.is_empty() {
3873            return LexicalTypeResolution::Missing;
3874        }
3875        // A C++ class injects its own name into the class scope.  The indexed
3876        // FqName for that declaration is the class path itself (for example,
3877        // `n::raw_hash_set`), not a synthetic child named
3878        // `n::raw_hash_set::raw_hash_set`.  Ordinary lexical tiers append the
3879        // requested identifier to every scope component, so they cannot
3880        // represent that injected binding when the enclosing class is the
3881        // closest scope.  Recover the binding from the structured class path
3882        // before allowing lookup to fall through to an outer same-spelled
3883        // declaration.
3884        let mut injected = self.resolve_injected_class_name(
3885            analyzer,
3886            file,
3887            components,
3888            global,
3889            lexical_scope,
3890            resolution,
3891        );
3892        for qualified in lexical_component_tiers(components, global, lexical_scope) {
3893            let prefix_len = qualified.len().saturating_sub(components.len());
3894            if injected
3895                .as_ref()
3896                .is_some_and(|(owner_len, _)| prefix_len < *owner_len)
3897            {
3898                return injected
3899                    .take()
3900                    .expect("injected class resolution was just present")
3901                    .1;
3902            }
3903            let qualified_name = qualified.join("::");
3904            let candidates = self
3905                .type_candidates(file, &qualified_name)
3906                .into_iter()
3907                .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
3908                .collect::<Vec<_>>();
3909            if candidates.is_empty() {
3910                if !global && components.len() == 1 {
3911                    match self.resolve_inherited_type_for_lexical_scope(
3912                        analyzer,
3913                        file,
3914                        &qualified[..prefix_len],
3915                        &components[0],
3916                        resolution,
3917                    ) {
3918                        LexicalTypeResolution::Missing => {}
3919                        inherited => return inherited,
3920                    }
3921                }
3922                continue;
3923            }
3924            let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
3925                Ok(unit) => unit,
3926                Err(failure) => return failure.lexical_resolution(),
3927            };
3928            return LexicalTypeResolution::Resolved {
3929                unit,
3930                components: qualified,
3931                candidates: candidates.into_iter().cloned().collect(),
3932            };
3933        }
3934        LexicalTypeResolution::Missing
3935    }
3936
3937    fn resolve_injected_class_name(
3938        &self,
3939        analyzer: &CppGraphSource<'_>,
3940        file: &ProjectFile,
3941        components: &[String],
3942        global: bool,
3943        lexical_scope: &[String],
3944        resolution: TypeCandidateResolution<'_>,
3945    ) -> Option<(usize, LexicalTypeResolution)> {
3946        if global
3947            || components.len() != 1
3948            || file.rel_path().extension().is_some_and(|ext| ext == "c")
3949            || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
3950        {
3951            return None;
3952        }
3953        let name = components.first()?;
3954        let mut matches: Vec<&CodeUnit> = Vec::new();
3955        let mut owner_len = 0;
3956        for candidate in self.visible_identifier_candidates(file, name) {
3957            if !candidate.is_class()
3958                || declared_type_alias(analyzer, candidate)
3959                || candidate.identifier() != name
3960            {
3961                continue;
3962            }
3963            let candidate_scope = canonical_cpp_scope_components(candidate);
3964            if candidate_scope.len() > lexical_scope.len()
3965                || !lexical_scope.starts_with(&candidate_scope)
3966                || candidate_scope.last().is_none_or(|last| last != name)
3967            {
3968                continue;
3969            }
3970            if candidate_scope.len() > owner_len {
3971                owner_len = candidate_scope.len();
3972                matches.clear();
3973            }
3974            if candidate_scope.len() == owner_len
3975                && !matches
3976                    .iter()
3977                    .any(|existing| same_logical_symbol(existing, candidate))
3978            {
3979                matches.push(candidate);
3980            }
3981        }
3982        if matches.is_empty() {
3983            return None;
3984        }
3985        // A same-named class at the current lexical boundary is already
3986        // represented by the ordinary namespace/class tier.  The injected
3987        // recovery is only needed when lookup is occurring inside a nested
3988        // class, where the enclosing class name is injected across that
3989        // additional class boundary.  Keeping this boundary strict avoids
3990        // treating qualified receiver/static-qualifier context as an
3991        // injected-name reference.
3992        if owner_len >= lexical_scope.len() {
3993            return None;
3994        }
3995        let owner_components = lexical_scope[..owner_len].to_vec();
3996        let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
3997            Ok(unit) => LexicalTypeResolution::Resolved {
3998                unit,
3999                components: owner_components,
4000                candidates: matches.into_iter().cloned().collect(),
4001            },
4002            Err(failure) => failure.lexical_resolution(),
4003        };
4004        Some((owner_len, resolution))
4005    }
4006
4007    fn resolve_inherited_type_for_lexical_scope(
4008        &self,
4009        analyzer: &CppGraphSource<'_>,
4010        file: &ProjectFile,
4011        lexical_scope: &[String],
4012        name: &str,
4013        resolution: TypeCandidateResolution<'_>,
4014    ) -> LexicalTypeResolution {
4015        let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
4016            return LexicalTypeResolution::Missing;
4017        };
4018        let lexical_owner_name = lexical_scope.join("::");
4019        if lexical_owner_name.is_empty() {
4020            return LexicalTypeResolution::Missing;
4021        }
4022        let owner_candidates = self
4023            .type_candidates(file, &lexical_owner_name)
4024            .into_iter()
4025            .filter(|candidate| {
4026                canonical_cpp_name_matches(candidate, &lexical_owner_name)
4027                    && !declared_type_alias(analyzer, candidate)
4028            })
4029            .collect::<Vec<_>>();
4030        if owner_candidates.is_empty() {
4031            return LexicalTypeResolution::Missing;
4032        }
4033        // A visible forward declaration and the physical class definition share
4034        // one FQN, but only the definition owns hierarchy facts. When lookup is
4035        // physically inside that definition, do not let an earlier header
4036        // forward declaration erase its base edges (#2240).
4037        let physical_owner_candidates = owner_candidates
4038            .iter()
4039            .copied()
4040            .filter(|candidate| candidate.source() == file)
4041            .collect::<Vec<_>>();
4042        let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
4043            owner_candidates
4044        } else {
4045            physical_owner_candidates
4046        };
4047        let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
4048            return LexicalTypeResolution::Ambiguous;
4049        };
4050
4051        let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
4052        let mut visited_owners = HashSet::default();
4053        while !frontier.is_empty() {
4054            let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
4055            let mut next_frontier = Vec::new();
4056            for owner in frontier {
4057                if !visited_owners.insert(owner.fq_name()) {
4058                    continue;
4059                }
4060                let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
4061                let candidates = self
4062                    .type_candidates(file, &qualified_name)
4063                    .into_iter()
4064                    .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
4065                    .collect::<Vec<_>>();
4066                if candidates.is_empty() {
4067                    for ancestor in hierarchy.get_direct_ancestors(&owner) {
4068                        if !next_frontier
4069                            .iter()
4070                            .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
4071                        {
4072                            next_frontier.push(ancestor);
4073                        }
4074                    }
4075                    continue;
4076                }
4077                let unit =
4078                    match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
4079                        Ok(unit) => unit,
4080                        Err(failure) => return failure.lexical_resolution(),
4081                    };
4082                level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
4083            }
4084            if let Some((unit, candidates)) = level_matches.first().cloned() {
4085                let Some(first_declaration) = candidates.first() else {
4086                    return LexicalTypeResolution::Ambiguous;
4087                };
4088                if !level_matches.iter().all(|(_, declarations)| {
4089                    declarations
4090                        .iter()
4091                        .all(|declaration| same_logical_symbol(first_declaration, declaration))
4092                }) {
4093                    return LexicalTypeResolution::Ambiguous;
4094                }
4095                let mut components = lexical_scope.to_vec();
4096                components.push(name.to_string());
4097                return LexicalTypeResolution::Resolved {
4098                    unit,
4099                    components,
4100                    candidates,
4101                };
4102            }
4103            frontier = next_frontier;
4104        }
4105        LexicalTypeResolution::Missing
4106    }
4107
4108    /// Resolve a base class through its injected class name at the nearest
4109    /// inheritance tier. Distinct same-named bases at that tier are ambiguous.
4110    pub fn inherited_injected_class_owner(
4111        &self,
4112        analyzer: &CppGraphSource<'_>,
4113        file: &ProjectFile,
4114        enclosing_owner: &CodeUnit,
4115        injected_name: &str,
4116    ) -> Option<CodeUnit> {
4117        let hierarchy = analyzer.type_hierarchy_provider()?;
4118        let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
4119        let mut visited = HashSet::default();
4120        while !frontier.is_empty() {
4121            let mut level_matches = Vec::new();
4122            let mut next_frontier = Vec::new();
4123            for raw_owner in frontier {
4124                let owner = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)?;
4125                if !visited.insert(owner.clone()) {
4126                    continue;
4127                }
4128                if owner.identifier() == injected_name
4129                    && !level_matches
4130                        .iter()
4131                        .any(|existing| same_logical_symbol(existing, &owner))
4132                {
4133                    level_matches.push(owner.clone());
4134                }
4135                next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
4136            }
4137            if let Some(first) = level_matches.first() {
4138                return level_matches
4139                    .iter()
4140                    .all(|candidate| same_logical_symbol(candidate, first))
4141                    .then(|| first.clone());
4142            }
4143            frontier = next_frontier;
4144        }
4145        None
4146    }
4147
4148    /// The one type the candidates name under `resolution`, or why they do not
4149    /// name one. The two preserving modes only ever reject candidates that
4150    /// disagree with each other, which is ambiguity; canonicalization can also
4151    /// fail because the alias chain leaves the index (#1828).
4152    fn resolve_type_candidates(
4153        &self,
4154        analyzer: &CppGraphSource<'_>,
4155        file: &ProjectFile,
4156        candidates: &[&CodeUnit],
4157        resolution: TypeCandidateResolution<'_>,
4158    ) -> Result<CodeUnit, TypeCandidateFailure> {
4159        match resolution {
4160            TypeCandidateResolution::Canonical => {
4161                self.canonical_type_candidate_resolution(analyzer, file, candidates)
4162            }
4163            TypeCandidateResolution::PreserveAlias => {
4164                // A generated index can retain identical alias spellings from
4165                // mutually exclusive headers. When the reference file
4166                // physically reaches exactly one of those source declarations,
4167                // include closure is the structured evidence that selects it;
4168                // treating the two source spellings as an overload set makes a
4169                // reachable alias appear ambiguous (#1844).
4170                let same_fqn_alias_family = candidates.len() > 1
4171                    && candidates.iter().all(|candidate| {
4172                        declared_type_alias(analyzer, candidate)
4173                            && same_logical_symbol(candidates[0], candidate)
4174                    })
4175                    && candidates
4176                        .iter()
4177                        .any(|candidate| candidate.source() != candidates[0].source());
4178                if same_fqn_alias_family {
4179                    let physically_visible = candidates
4180                        .iter()
4181                        .copied()
4182                        .filter(|candidate| self.is_physically_visible(file, candidate))
4183                        .collect::<Vec<_>>();
4184                    if !physically_visible.is_empty() {
4185                        // The family is one logical declaration, so multiple
4186                        // reachable spellings still produce one answer.
4187                        return Ok(physically_visible[0].clone());
4188                    }
4189                }
4190                unique_type_candidate_preserving_alias(analyzer, candidates)
4191                    .ok_or(TypeCandidateFailure::Ambiguous)
4192            }
4193            TypeCandidateResolution::PreserveTarget(target) => self
4194                .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
4195                .ok_or(TypeCandidateFailure::Ambiguous),
4196        }
4197    }
4198
4199    pub fn resolve_callable_value_components_lexically(
4200        &self,
4201        analyzer: &CppGraphSource<'_>,
4202        file: &ProjectFile,
4203        owner_components: &[String],
4204        member_name: &str,
4205        global: bool,
4206        lexical_scope: &[String],
4207    ) -> LexicalCallableValueResolution {
4208        if owner_components.is_empty() || member_name.is_empty() {
4209            return LexicalCallableValueResolution::Missing;
4210        }
4211        for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
4212            let owner_name = qualified_owner.join("::");
4213            let type_candidates = self
4214                .type_candidates(file, &owner_name)
4215                .into_iter()
4216                .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
4217                .collect::<Vec<_>>();
4218            let resolved_type = if type_candidates.is_empty() {
4219                None
4220            } else {
4221                let Some(unit) =
4222                    self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
4223                else {
4224                    return LexicalCallableValueResolution::Ambiguous;
4225                };
4226                Some(unit)
4227            };
4228
4229            let mut qualified_callable = qualified_owner;
4230            qualified_callable.push(member_name.to_string());
4231            let callable_name = qualified_callable.join("::");
4232            let free_function = self
4233                .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
4234                .into_iter()
4235                .find(|candidate| {
4236                    canonical_cpp_name_matches(candidate, &callable_name)
4237                        && type_owner_of(analyzer, candidate).is_none()
4238                })
4239                .cloned();
4240
4241            match (resolved_type, free_function) {
4242                (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
4243                (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
4244                (None, Some(function)) => {
4245                    return LexicalCallableValueResolution::FreeFunction(function);
4246                }
4247                (None, None) => {}
4248            }
4249        }
4250        LexicalCallableValueResolution::Missing
4251    }
4252
4253    fn resolve_type_for_declaration(
4254        &self,
4255        visible_from: &ProjectFile,
4256        declaration: &CodeUnit,
4257        raw_name: &str,
4258    ) -> Option<CodeUnit> {
4259        let normalized = normalize_reference_name(raw_name)?;
4260        if !normalized.contains("::")
4261            && let Some(namespace) = cpp_namespace_for(declaration)
4262        {
4263            for prefix in namespace_prefixes(&namespace) {
4264                let qualified = format!("{prefix}::{normalized}");
4265                if let Some(unit) = self
4266                    .type_candidates(visible_from, &qualified)
4267                    .into_iter()
4268                    .next()
4269                {
4270                    return Some(unit.clone());
4271                }
4272            }
4273        }
4274        self.resolve_type(visible_from, raw_name)
4275    }
4276
4277    fn resolve_unique_canonical_type_for_declaration(
4278        &self,
4279        analyzer: &CppGraphSource<'_>,
4280        visible_from: &ProjectFile,
4281        declaration: &CodeUnit,
4282        raw_name: &str,
4283    ) -> Option<CodeUnit> {
4284        let mut current =
4285            self.resolve_unique_type_for_declaration(visible_from, declaration, raw_name)?;
4286        let mut seen_aliases = HashSet::default();
4287        loop {
4288            let Some(target) = self.structured_alias_target(analyzer, &current) else {
4289                return current.is_class().then_some(current);
4290            };
4291            if matches!(target, StructuredAliasTarget::Builtin) {
4292                return current.is_class().then_some(current);
4293            }
4294            if !seen_aliases.insert(current.clone()) {
4295                return None;
4296            }
4297            current = self.resolve_structured_alias_target(visible_from, &current, &target)?;
4298        }
4299    }
4300
4301    pub fn canonical_type_unit(
4302        &self,
4303        analyzer: &CppGraphSource<'_>,
4304        visible_from: &ProjectFile,
4305        unit: &CodeUnit,
4306    ) -> Option<CodeUnit> {
4307        self.canonical_type_resolution(analyzer, visible_from, unit)
4308            .ok()
4309    }
4310
4311    /// Follow `unit`'s alias chain to the class it names, or report why the
4312    /// chain does not end at one indexed class.
4313    ///
4314    /// A chain that leaves the index - an alias to a template parameter, to a
4315    /// standard-library type, or to any other declaration the workspace does
4316    /// not hold - is `Unresolvable`, not `Ambiguous` (#1828). So is a cycle:
4317    /// there is still nothing to choose between.
4318    fn canonical_type_resolution(
4319        &self,
4320        analyzer: &CppGraphSource<'_>,
4321        visible_from: &ProjectFile,
4322        unit: &CodeUnit,
4323    ) -> Result<CodeUnit, TypeCandidateFailure> {
4324        let mut current = unit.clone();
4325        let mut seen_aliases = HashSet::default();
4326        loop {
4327            let Some(target) = self.structured_alias_target(analyzer, &current) else {
4328                return current
4329                    .is_class()
4330                    .then_some(current)
4331                    .ok_or(TypeCandidateFailure::Unresolvable);
4332            };
4333            if matches!(target, StructuredAliasTarget::Builtin) {
4334                return current
4335                    .is_class()
4336                    .then_some(current)
4337                    .ok_or(TypeCandidateFailure::Unresolvable);
4338            }
4339            if !seen_aliases.insert(current.clone()) {
4340                return Err(TypeCandidateFailure::Unresolvable);
4341            }
4342            current = self.structured_alias_target_resolution(visible_from, &current, &target)?;
4343        }
4344    }
4345
4346    pub fn canonical_visible_full_type_unit(
4347        &self,
4348        analyzer: &CppGraphSource<'_>,
4349        visible_from: &ProjectFile,
4350        unit: &CodeUnit,
4351    ) -> Option<CodeUnit> {
4352        let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
4353        if cpp_class_declaration_strength(analyzer, &canonical)
4354            != CppClassDeclarationStrength::Forward
4355        {
4356            return Some(canonical);
4357        }
4358        let mut full = Vec::new();
4359        for candidate in self
4360            .visible_identifier_candidates(visible_from, canonical.identifier())
4361            .filter(|candidate| {
4362                candidate.is_class()
4363                    && candidate.fq_name() == canonical.fq_name()
4364                    && cpp_class_declaration_strength(analyzer, candidate)
4365                        == CppClassDeclarationStrength::Full
4366            })
4367        {
4368            if !full.iter().any(|existing| same_symbol(existing, candidate)) {
4369                full.push(candidate.clone());
4370            }
4371        }
4372        match full.len() {
4373            0 => Some(canonical),
4374            1 => full.pop(),
4375            _ => None,
4376        }
4377    }
4378
4379    fn resolve_structured_alias_target(
4380        &self,
4381        visible_from: &ProjectFile,
4382        declaration: &CodeUnit,
4383        target: &StructuredAliasTarget,
4384    ) -> Option<CodeUnit> {
4385        self.structured_alias_target_resolution(visible_from, declaration, target)
4386            .ok()
4387    }
4388
4389    fn structured_alias_target_resolution(
4390        &self,
4391        visible_from: &ProjectFile,
4392        declaration: &CodeUnit,
4393        target: &StructuredAliasTarget,
4394    ) -> Result<CodeUnit, TypeCandidateFailure> {
4395        let primary =
4396            self.structured_alias_primary_resolution(visible_from, declaration, target)?;
4397        let StructuredAliasTarget::Named { arguments, .. } = target else {
4398            return Err(TypeCandidateFailure::Unresolvable);
4399        };
4400        match arguments {
4401            Some(arguments) => self
4402                .resolve_template_arguments(visible_from, primary, arguments)
4403                .map_err(|error| match error {
4404                    CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
4405                        TypeCandidateFailure::Ambiguous
4406                    }
4407                    _ => TypeCandidateFailure::Unresolvable,
4408                }),
4409            None => Ok(primary),
4410        }
4411    }
4412
4413    fn resolve_structured_alias_primary(
4414        &self,
4415        visible_from: &ProjectFile,
4416        declaration: &CodeUnit,
4417        target: &StructuredAliasTarget,
4418    ) -> Option<CodeUnit> {
4419        self.structured_alias_primary_resolution(visible_from, declaration, target)
4420            .ok()
4421    }
4422
4423    fn structured_alias_primary_resolution(
4424        &self,
4425        visible_from: &ProjectFile,
4426        declaration: &CodeUnit,
4427        target: &StructuredAliasTarget,
4428    ) -> Result<CodeUnit, TypeCandidateFailure> {
4429        let StructuredAliasTarget::Named {
4430            components, global, ..
4431        } = target
4432        else {
4433            return Err(TypeCandidateFailure::Unresolvable);
4434        };
4435        let qualified = components.join("::");
4436        let candidates = if *global {
4437            self.type_candidates(visible_from, &qualified)
4438        } else {
4439            self.type_candidates_for_declaration(visible_from, declaration, &qualified)
4440        };
4441        logical_type_candidate(candidates)
4442    }
4443
4444    pub fn structured_alias_primary_preserves_target(
4445        &self,
4446        analyzer: &CppGraphSource<'_>,
4447        visible_from: &ProjectFile,
4448        candidate: &CodeUnit,
4449        target: &CodeUnit,
4450    ) -> bool {
4451        let mut current = candidate.clone();
4452        let mut seen = HashSet::default();
4453        let mut matched_target = false;
4454        loop {
4455            if same_visible_symbol(&current, target)
4456                || self.compatible_primary_template_redeclarations(&current, target)
4457            {
4458                matched_target = true;
4459            }
4460            if !seen.insert(current.clone()) {
4461                return false;
4462            }
4463            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
4464                return matched_target;
4465            };
4466            if matches!(alias_target, StructuredAliasTarget::Builtin) {
4467                return matched_target;
4468            };
4469            let Some(primary) =
4470                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
4471            else {
4472                // A dependent member target such as `Detector<T>::type`
4473                // cannot be reduced to an indexed primary, but a preceding
4474                // structured alias hop may already have proven the requested
4475                // alias identity. Cycles still resolve a primary and are
4476                // rejected by `seen` above.
4477                return matched_target;
4478            };
4479            current = primary;
4480        }
4481    }
4482
4483    pub fn structured_class_alias_resolves_to_target(
4484        &self,
4485        analyzer: &CppGraphSource<'_>,
4486        visible_from: &ProjectFile,
4487        alias: &CodeUnit,
4488        target: &CodeUnit,
4489    ) -> bool {
4490        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
4491            return false;
4492        };
4493        let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
4494            return false;
4495        };
4496        let StructuredAliasTarget::Named {
4497            components, global, ..
4498        } = &alias_target
4499        else {
4500            return false;
4501        };
4502        let lexical_scope = canonical_cpp_scope_components(&owner);
4503        match self.resolve_type_components_lexically_for_target(
4504            analyzer,
4505            visible_from,
4506            components,
4507            *global,
4508            &lexical_scope,
4509            target,
4510        ) {
4511            LexicalTypeResolution::Resolved {
4512                unit, candidates, ..
4513            } => {
4514                same_visible_symbol(&unit, target)
4515                    || self.same_template_member_identity(analyzer, &unit, target)
4516                    || candidates.iter().any(|candidate| {
4517                        same_visible_symbol(candidate, target)
4518                            || self.same_template_member_identity(analyzer, candidate, target)
4519                    })
4520            }
4521            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
4522                self.structured_alias_primary_preserves_target(
4523                    analyzer,
4524                    visible_from,
4525                    alias,
4526                    target,
4527                ) || self.flattened_macro_namespace_alias_target_matches(
4528                    analyzer,
4529                    visible_from,
4530                    alias,
4531                    &alias_target,
4532                    target,
4533                )
4534            }
4535        }
4536    }
4537
4538    /// Return true when a class-owned alias names the requested type as one
4539    /// structured qualifier in its target path.
4540    ///
4541    /// A dependent target such as `Primary<T>::Type` cannot resolve to one
4542    /// indexed class. Forward lookup can still retain `Primary` as its bounded
4543    /// canonical identity. Inverse lookup needs the same evidence when later
4544    /// references use only the alias spelling.
4545    pub fn structured_class_alias_path_preserves_target(
4546        &self,
4547        analyzer: &CppGraphSource<'_>,
4548        visible_from: &ProjectFile,
4549        alias: &CodeUnit,
4550        target: &CodeUnit,
4551    ) -> bool {
4552        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
4553            return false;
4554        };
4555        let Some(StructuredAliasTarget::Named {
4556            components, global, ..
4557        }) = self.structured_alias_target(analyzer, alias)
4558        else {
4559            return false;
4560        };
4561        let lexical_scope = canonical_cpp_scope_components(&owner);
4562        (1..components.len()).rev().any(|component_count| {
4563            matches!(
4564                self.resolve_type_components_lexically_for_target(
4565                    analyzer,
4566                    visible_from,
4567                    &components[..component_count],
4568                    global,
4569                    &lexical_scope,
4570                    target,
4571                ),
4572                LexicalTypeResolution::Resolved {
4573                    ref unit,
4574                    ref candidates,
4575                    ..
4576                } if same_visible_symbol(unit, target)
4577                    || self.same_template_member_identity(analyzer, unit, target)
4578                    || candidates.iter().any(|candidate| {
4579                        same_visible_symbol(candidate, target)
4580                            || self.same_template_member_identity(analyzer, candidate, target)
4581                    })
4582            )
4583        })
4584    }
4585
4586    fn flattened_macro_namespace_alias_target_matches(
4587        &self,
4588        analyzer: &CppGraphSource<'_>,
4589        visible_from: &ProjectFile,
4590        alias: &CodeUnit,
4591        alias_target: &StructuredAliasTarget,
4592        target: &CodeUnit,
4593    ) -> bool {
4594        let StructuredAliasTarget::Named {
4595            components,
4596            global: false,
4597            arguments: None,
4598        } = alias_target
4599        else {
4600            return false;
4601        };
4602        let Some((target_name, namespace_components)) = components.split_last() else {
4603            return false;
4604        };
4605        if namespace_components.is_empty()
4606            || target_name != target.identifier()
4607            || alias.source() != target.source()
4608            || alias.source() != visible_from
4609            || !target.is_class()
4610            || declared_type_alias(analyzer, target)
4611        {
4612            return false;
4613        }
4614        if self
4615            .resolve_structured_alias_target(visible_from, alias, alias_target)
4616            .is_some()
4617        {
4618            return false;
4619        }
4620
4621        let alias_ranges = analyzer.ranges(alias);
4622        let target_ranges = analyzer.ranges(target);
4623        if alias_ranges.is_empty() || target_ranges.is_empty() {
4624            return false;
4625        }
4626        let alias_start = alias_ranges
4627            .iter()
4628            .map(|range| range.start_byte)
4629            .min()
4630            .expect("non-empty alias ranges have a minimum");
4631        let Some(prepared) = self.cpp.prepared_syntax(target.source()) else {
4632            return false;
4633        };
4634        let root = prepared.tree().root_node();
4635        let has_matching_declaration = target_ranges
4636            .iter()
4637            .filter(|range| range.end_byte <= alias_start)
4638            .filter_map(|range| node_for_exact_range(root, range))
4639            .any(|node| {
4640                flattened_macro_namespace_components(node, prepared.source())
4641                    .is_some_and(|recovered| recovered == namespace_components)
4642            });
4643        if !has_matching_declaration {
4644            return false;
4645        }
4646
4647        let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
4648        let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
4649        guard_requirement_sets_match(&alias_guards, &target_guards)
4650    }
4651
4652    pub fn template_alias_arguments_preserve_target(
4653        &self,
4654        analyzer: &CppGraphSource<'_>,
4655        visible_from: &ProjectFile,
4656        alias: &CodeUnit,
4657        arguments: &[CppTemplateExpression],
4658        target: &CodeUnit,
4659    ) -> bool {
4660        let Some(metadata) = self.cpp_template_metadata.get(alias) else {
4661            return false;
4662        };
4663        if metadata.alias_target.is_none()
4664            || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
4665        {
4666            return false;
4667        }
4668        self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
4669    }
4670
4671    pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
4672        self.cpp_template_metadata
4673            .get(unit)
4674            .is_some_and(CppTemplateMetadata::is_primary)
4675    }
4676
4677    pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
4678        self.cpp_template_metadata
4679            .get(unit)
4680            .is_some_and(CppTemplateMetadata::is_specialization)
4681    }
4682
4683    pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
4684        same_visible_symbol(left, right)
4685            || self.compatible_primary_template_redeclarations(left, right)
4686    }
4687
4688    pub fn same_template_member_identity(
4689        &self,
4690        analyzer: &CppGraphSource<'_>,
4691        left: &CodeUnit,
4692        right: &CodeUnit,
4693    ) -> bool {
4694        if same_visible_symbol(left, right) {
4695            return true;
4696        }
4697        if left.kind() != right.kind()
4698            || left.identifier() != right.identifier()
4699            || left.signature() != right.signature()
4700        {
4701            return false;
4702        }
4703        let (Some(left_owner), Some(right_owner)) =
4704            (analyzer.parent_of(left), analyzer.parent_of(right))
4705        else {
4706            return false;
4707        };
4708        left_owner.is_class()
4709            && right_owner.is_class()
4710            && self.same_template_owner_identity(&left_owner, &right_owner)
4711    }
4712
4713    fn unique_canonical_type_candidate(
4714        &self,
4715        analyzer: &CppGraphSource<'_>,
4716        visible_from: &ProjectFile,
4717        candidates: &[&CodeUnit],
4718    ) -> Option<CodeUnit> {
4719        self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
4720            .ok()
4721    }
4722
4723    fn canonical_type_candidate_resolution(
4724        &self,
4725        analyzer: &CppGraphSource<'_>,
4726        visible_from: &ProjectFile,
4727        candidates: &[&CodeUnit],
4728    ) -> Result<CodeUnit, TypeCandidateFailure> {
4729        let mut canonical = Vec::new();
4730        for candidate in candidates {
4731            let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
4732            if canonical
4733                .iter()
4734                .any(|existing| same_visible_symbol(existing, &resolved))
4735            {
4736                continue;
4737            }
4738            if let Some(existing) = canonical.iter_mut().find(|existing| {
4739                self.compatible_primary_template_redeclarations(existing, &resolved)
4740            }) {
4741                // A forward declaration and its full primary-template
4742                // definition are one C++ type even when they live in
4743                // different headers and alpha-rename their parameters. The
4744                // target-preserving path already reconciles this family; do
4745                // the same for ordinary canonical lookup so an out-of-line
4746                // member's lexical owner is not made ambiguous by its own
4747                // forward declaration. Retain the strongest physical
4748                // declaration for later owner/range queries.
4749                if matches!(
4750                    (
4751                        cpp_class_declaration_strength(analyzer, existing),
4752                        cpp_class_declaration_strength(analyzer, &resolved),
4753                    ),
4754                    (
4755                        CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
4756                        CppClassDeclarationStrength::Full,
4757                    ) | (
4758                        CppClassDeclarationStrength::Unknown,
4759                        CppClassDeclarationStrength::Forward,
4760                    )
4761                ) {
4762                    *existing = resolved;
4763                }
4764                continue;
4765            }
4766            canonical.push(resolved);
4767            if canonical.len() > 1 {
4768                return Err(TypeCandidateFailure::Ambiguous);
4769            }
4770        }
4771        canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
4772    }
4773
4774    pub fn unique_type_candidate_preserving_target(
4775        &self,
4776        analyzer: &CppGraphSource<'_>,
4777        visible_from: &ProjectFile,
4778        candidates: &[&CodeUnit],
4779        target: &CodeUnit,
4780    ) -> Option<CodeUnit> {
4781        // C++ headers often expose one logical type through mutually exclusive
4782        // physical declarations, for example a class in the fallback branch
4783        // and a `using` alias to the standard-library type in the configured
4784        // branch. The index intentionally retains both declarations so forward
4785        // lookup can report each target. Preserve the requested target when
4786        // that is the only ambiguity: every candidate has the same type kind,
4787        // exact canonical FQN, and source file, and the requested declaration
4788        // itself is one of the physical candidates. Do not merge same-named
4789        // declarations from different files or namespaces; those remain
4790        // ambiguous and fail closed below.
4791        if self.alternate_same_fqn_type_declarations(analyzer, candidates, target) {
4792            return Some(target.clone());
4793        }
4794        let mut resolved_candidates = Vec::new();
4795        for candidate in candidates {
4796            let resolved =
4797                self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)?;
4798            if resolved_candidates
4799                .iter()
4800                .any(|existing| same_visible_symbol(existing, &resolved))
4801            {
4802                continue;
4803            }
4804            resolved_candidates.push(resolved);
4805        }
4806        match resolved_candidates.as_slice() {
4807            [] => None,
4808            [single] => Some(single.clone()),
4809            // The branches disagree about what the name aliases. When they are
4810            // spellings of one entity (#1845) that disagreement is a build
4811            // configuration, not a choice between types, so it must not deny
4812            // the requested target its reference.
4813            _ => self
4814                .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
4815                .map(|_| target.clone()),
4816        }
4817    }
4818
4819    /// The declaration a same-file same-FQN family stands for when a reference
4820    /// names `target`, or `None` when the candidates are not one family or the
4821    /// family does not name `target`.
4822    ///
4823    /// A translation unit cannot hold two different types under one qualified
4824    /// name, so several same-kind declarations of one FQN in one file are
4825    /// alternate spellings of one entity - the configuration branches of an
4826    /// `#if` family, for example log4cxx's `logchar`, which aliases `char` in
4827    /// the UTF-8 branch and `UniChar` in the unichar branch. Their alias
4828    /// targets differ; canonicalizing each branch on its own and then demanding
4829    /// agreement reports an ambiguity that denies every declaration in the
4830    /// family its usages (#1845). The family names `target` when it declares
4831    /// it, or when one branch's alias chain reaches it.
4832    ///
4833    /// Declarations in different files or namespaces are distinct entities and
4834    /// are deliberately excluded: their disagreement is a real ambiguity.
4835    pub fn same_fqn_type_spelling_for_target<'b>(
4836        &self,
4837        analyzer: &CppGraphSource<'_>,
4838        visible_from: &ProjectFile,
4839        candidates: &[&'b CodeUnit],
4840        target: &CodeUnit,
4841    ) -> Option<&'b CodeUnit> {
4842        let [first, rest @ ..] = candidates else {
4843            return None;
4844        };
4845        if rest.is_empty()
4846            || !rest.iter().all(|candidate| {
4847                candidate.kind() == first.kind()
4848                    && candidate.fq_name() == first.fq_name()
4849                    && candidate.source() == first.source()
4850            })
4851        {
4852            return None;
4853        }
4854        candidates
4855            .iter()
4856            .copied()
4857            .find(|candidate| same_symbol(candidate, target))
4858            .or_else(|| {
4859                candidates.iter().copied().find(|candidate| {
4860                    self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
4861                        .is_some_and(|resolved| same_visible_symbol(&resolved, target))
4862                })
4863            })
4864    }
4865
4866    pub fn alternate_same_fqn_type_declarations(
4867        &self,
4868        analyzer: &CppGraphSource<'_>,
4869        candidates: &[&CodeUnit],
4870        target: &CodeUnit,
4871    ) -> bool {
4872        let Some(first) = candidates.first() else {
4873            return false;
4874        };
4875        let same_api = first.kind() == target.kind()
4876            && first.fq_name() == target.fq_name()
4877            && first.source() == target.source()
4878            && candidates.iter().all(|candidate| {
4879                candidate.kind() == target.kind()
4880                    && candidate.fq_name() == target.fq_name()
4881                    && candidate.source() == target.source()
4882            })
4883            && candidates
4884                .iter()
4885                .any(|candidate| same_symbol(candidate, target))
4886            && candidates
4887                .iter()
4888                .any(|candidate| !same_logical_symbol(candidate, target));
4889        if !same_api {
4890            return false;
4891        }
4892
4893        let requirements = candidates
4894            .iter()
4895            .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
4896            .collect::<Vec<_>>();
4897        requirements.len() > 1
4898            && requirements
4899                .iter()
4900                .all(|requirement| !requirement.is_empty())
4901            && requirements.iter().enumerate().all(|(index, left)| {
4902                requirements[index + 1..].iter().all(|right| {
4903                    left.iter().all(|(_, left_guards)| {
4904                        right.iter().all(|(_, right_guards)| {
4905                            merge_preprocessor_guards(left_guards, right_guards).is_none()
4906                        })
4907                    })
4908                })
4909            })
4910    }
4911
4912    fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
4913        let mut pending = vec![terms.to_vec()];
4914        while let Some(branch_terms) = pending.pop() {
4915            let mut normalized = Vec::new();
4916            let mut covers_branch = false;
4917            for term in branch_terms {
4918                if term.iter().any(|guard| term.contains(&guard.negated())) {
4919                    continue;
4920                }
4921                if term.is_empty() {
4922                    covers_branch = true;
4923                    break;
4924                }
4925                if !normalized.iter().any(|existing| existing == &term) {
4926                    normalized.push(term);
4927                }
4928            }
4929            if covers_branch {
4930                continue;
4931            }
4932            let Some(split_guard) = normalized
4933                .iter()
4934                .flat_map(|term| term.iter())
4935                .next()
4936                .cloned()
4937            else {
4938                return false;
4939            };
4940            let negated_guard = split_guard.negated();
4941            let mut when_defined = Vec::new();
4942            let mut when_undefined = Vec::new();
4943            for term in normalized {
4944                if term.contains(&negated_guard) {
4945                    // This term cannot hold when `split_guard` is true.
4946                } else if term.contains(&split_guard) {
4947                    let mut reduced = term.clone();
4948                    reduced.remove(&split_guard);
4949                    when_defined.push(reduced);
4950                } else {
4951                    when_defined.push(term.clone());
4952                }
4953                if term.contains(&split_guard) {
4954                    // This term cannot hold when `split_guard` is false.
4955                } else if term.contains(&negated_guard) {
4956                    let mut reduced = term;
4957                    reduced.remove(&negated_guard);
4958                    when_undefined.push(reduced);
4959                } else {
4960                    when_undefined.push(term);
4961                }
4962            }
4963            pending.push(when_defined);
4964            pending.push(when_undefined);
4965        }
4966        true
4967    }
4968
4969    /// The byte range of the one `#if` family with a terminal `#else` that holds
4970    /// every physical declaration of every candidate, or `None` when they do not
4971    /// share one such family.
4972    ///
4973    /// Guard terms alone cannot distinguish one `#if` family from separate blocks
4974    /// whose macros changed between declarations. Require every physical range to
4975    /// belong to one syntax-tree family with a terminal `#else` before the terms
4976    /// can prove branch coverage.
4977    fn declarations_share_exhaustive_conditional_family(
4978        &self,
4979        analyzer: &CppGraphSource<'_>,
4980        candidates: &[&CodeUnit],
4981    ) -> Option<(usize, usize)> {
4982        let mut family_range = None;
4983        for candidate in candidates {
4984            let prepared = self.cpp.prepared_syntax(candidate.source())?;
4985            let root = prepared.tree().root_node();
4986            let mut candidate_family = None;
4987            for range in analyzer.ranges(candidate) {
4988                let node = root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
4989                let family = preprocessor_conditional_family_for_declaration(node)?;
4990                let key = (family.start_byte(), family.end_byte());
4991                if candidate_family.is_some_and(|existing| existing != key) {
4992                    return None;
4993                }
4994                candidate_family = Some(key);
4995            }
4996            let candidate_family = candidate_family?;
4997            if family_range.is_some_and(|existing| existing != candidate_family) {
4998                return None;
4999            }
5000            family_range = Some(candidate_family);
5001        }
5002        family_range
5003    }
5004
5005    pub fn complementary_same_fqn_type_declarations(
5006        &self,
5007        analyzer: &CppGraphSource<'_>,
5008        candidates: &[&CodeUnit],
5009        target: &CodeUnit,
5010    ) -> bool {
5011        if candidates.len() < 2
5012            || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
5013            || self
5014                .declarations_share_exhaustive_conditional_family(analyzer, candidates)
5015                .is_none()
5016        {
5017            return false;
5018        }
5019        Self::preprocessor_guard_terms_cover_all_paths(
5020            &self.declaration_family_guard_terms(analyzer, candidates),
5021        )
5022    }
5023
5024    fn declaration_family_guard_terms(
5025        &self,
5026        analyzer: &CppGraphSource<'_>,
5027        candidates: &[&CodeUnit],
5028    ) -> Vec<HashSet<PreprocessorGuard>> {
5029        candidates
5030            .iter()
5031            .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
5032            .map(|(_, guards)| guards)
5033            .collect()
5034    }
5035
5036    /// A callable name declared on every branch of one completed `#if`/`#else`
5037    /// family is declared on every configuration path, so a reference below the
5038    /// whole family sees one of the branches whatever the preprocessor decides.
5039    /// Answer the family's end byte: only past `#endif` is every branch's
5040    /// declaration behind the reference.
5041    ///
5042    /// This is the callable analogue of `complementary_same_fqn_type_declarations`
5043    /// and shares both of its primitives. It does not require two distinct
5044    /// `CodeUnit`s: branches that declare the same signature can collapse into
5045    /// one unit carrying one physical range per branch.
5046    ///
5047    /// The branches are alternate spellings of one declaration, never competing
5048    /// declarations, so only the first branch stands for the family. Reporting
5049    /// every branch as visible would turn a name the source declares exactly
5050    /// once into an ambiguity between build configurations.
5051    fn exhaustive_guard_family_activation(
5052        &self,
5053        analyzer: &CppGraphSource<'_>,
5054        prepared: &PreparedSyntaxTree,
5055        candidate: &CodeUnit,
5056        reference: &CallableReferenceContext<'_>,
5057    ) -> Option<usize> {
5058        // Branch coverage says nothing about scope: a block-local declaration
5059        // stays invisible however many branches declare it.
5060        if nameable_callable_declaration_nodes(analyzer, prepared, candidate).is_empty() {
5061            return None;
5062        }
5063        let family = self
5064            .visible_identifier_candidates(candidate.source(), candidate.identifier())
5065            .filter(|peer| {
5066                peer.kind() == candidate.kind()
5067                    && peer.fq_name() == candidate.fq_name()
5068                    && peer.source() == candidate.source()
5069            })
5070            .collect::<Vec<_>>();
5071        let (_, family_end) =
5072            self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
5073        if !Self::preprocessor_guard_terms_cover_all_paths(
5074            &self.declaration_family_guard_terms(analyzer, &family),
5075        ) {
5076            return None;
5077        }
5078        // A reference whose own guards pick one branch already reaches that
5079        // branch through the ordinary same-guard path; the family must not
5080        // resurrect the branch the reference contradicts.
5081        if !declaration_guard_requirements(analyzer, self.cpp, candidate)
5082            .iter()
5083            .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
5084        {
5085            return None;
5086        }
5087        (first_declaration_byte(analyzer, candidate)?
5088            == family
5089                .iter()
5090                .filter_map(|peer| first_declaration_byte(analyzer, peer))
5091                .min()?)
5092        .then_some(family_end)
5093    }
5094
5095    fn type_candidate_preserving_target(
5096        &self,
5097        analyzer: &CppGraphSource<'_>,
5098        visible_from: &ProjectFile,
5099        candidate: &CodeUnit,
5100        target: &CodeUnit,
5101    ) -> Option<CodeUnit> {
5102        let mut current = candidate.clone();
5103        let mut matched_target = same_visible_symbol(&current, target)
5104            || self.compatible_primary_template_redeclarations(&current, target);
5105        let mut seen = HashSet::default();
5106        loop {
5107            if !seen.insert(current.clone()) {
5108                return None;
5109            }
5110            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
5111                return matched_target
5112                    .then(|| target.clone())
5113                    .or_else(|| current.is_class().then_some(current));
5114            };
5115            if self.flattened_macro_namespace_alias_target_matches(
5116                analyzer,
5117                visible_from,
5118                &current,
5119                &alias_target,
5120                target,
5121            ) {
5122                return Some(target.clone());
5123            }
5124            if matches!(alias_target, StructuredAliasTarget::Builtin) {
5125                return matched_target
5126                    .then(|| target.clone())
5127                    .or_else(|| current.is_class().then_some(current));
5128            }
5129            // A non-template alias can name a template alias with explicit
5130            // arguments (for example, `using Result = Expected<int>`).  When
5131            // the requested target is that alias's primary declaration, keep
5132            // the primary identity before expanding the RHS arguments.  The
5133            // expansion would otherwise canonicalize through the underlying
5134            // implementation type and lose the target spelling used by the
5135            // forward resolver.
5136            if !self.cpp_template_metadata.contains_key(&current)
5137                && let Some(primary) =
5138                    self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
5139                && (same_visible_symbol(&primary, target)
5140                    || self.compatible_primary_template_redeclarations(&primary, target))
5141            {
5142                return Some(target.clone());
5143            }
5144            if same_visible_symbol(&current, target) {
5145                return Some(target.clone());
5146            }
5147            if self.cpp_template_metadata.contains_key(&current) {
5148                return None;
5149            }
5150            let Some(next) =
5151                self.resolve_structured_alias_target(visible_from, &current, &alias_target)
5152            else {
5153                return matched_target.then(|| target.clone());
5154            };
5155            current = next;
5156            matched_target |= same_visible_symbol(&current, target)
5157                || self.compatible_primary_template_redeclarations(&current, target);
5158        }
5159    }
5160
5161    fn compatible_primary_template_redeclarations(
5162        &self,
5163        left: &CodeUnit,
5164        right: &CodeUnit,
5165    ) -> bool {
5166        let (Some(left_metadata), Some(right_metadata)) = (
5167            self.cpp_template_metadata.get(left),
5168            self.cpp_template_metadata.get(right),
5169        ) else {
5170            return false;
5171        };
5172        left_metadata.primary_fq_name == right_metadata.primary_fq_name
5173            && left_metadata.is_primary()
5174            && right_metadata.is_primary()
5175            && cpp_reconcile_primary_template_parameters(
5176                &[(left, left_metadata), (right, right_metadata)],
5177                right,
5178            )
5179            .is_some()
5180    }
5181
5182    fn alias_candidate_may_preserve_target(
5183        &self,
5184        analyzer: &CppGraphSource<'_>,
5185        visible_from: &ProjectFile,
5186        candidate: &CodeUnit,
5187        target: &CodeUnit,
5188    ) -> bool {
5189        let mut current = candidate.clone();
5190        let mut seen = HashSet::default();
5191        loop {
5192            if same_visible_symbol(&current, target)
5193                || self.compatible_primary_template_redeclarations(&current, target)
5194            {
5195                return true;
5196            }
5197            if self.cpp_template_metadata.contains_key(&current) {
5198                return true;
5199            }
5200            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
5201                return false;
5202            };
5203            let StructuredAliasTarget::Named {
5204                components,
5205                global,
5206                arguments,
5207            } = alias_target
5208            else {
5209                return false;
5210            };
5211            if arguments.is_some() || !seen.insert(current.clone()) {
5212                return true;
5213            }
5214            let qualified = components.join("::");
5215            let next = if global {
5216                unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
5217            } else {
5218                self.resolve_unique_type_for_declaration(visible_from, &current, &qualified)
5219            };
5220            let Some(next) = next else {
5221                return true;
5222            };
5223            current = next;
5224        }
5225    }
5226
5227    /// Every indexed type declaration `raw_name` names when it is written in
5228    /// `declaration`'s namespace: the innermost enclosing namespace that holds
5229    /// the name wins, otherwise the name is looked up unqualified.
5230    fn type_candidates_for_declaration<'b>(
5231        &'b self,
5232        visible_from: &ProjectFile,
5233        declaration: &CodeUnit,
5234        raw_name: &str,
5235    ) -> Vec<&'b CodeUnit> {
5236        let Some(normalized) = normalize_reference_name(raw_name) else {
5237            return Vec::new();
5238        };
5239        if let Some(namespace) = cpp_namespace_for(declaration) {
5240            for prefix in namespace_prefixes(&namespace) {
5241                let qualified = format!("{prefix}::{normalized}");
5242                let candidates = self.type_candidates(visible_from, &qualified);
5243                if !candidates.is_empty() {
5244                    return candidates;
5245                }
5246            }
5247        }
5248        self.type_candidates(visible_from, &normalized)
5249    }
5250
5251    fn resolve_unique_type_for_declaration(
5252        &self,
5253        visible_from: &ProjectFile,
5254        declaration: &CodeUnit,
5255        raw_name: &str,
5256    ) -> Option<CodeUnit> {
5257        unique_logical_type_candidate(self.type_candidates_for_declaration(
5258            visible_from,
5259            declaration,
5260            raw_name,
5261        ))
5262    }
5263
5264    pub fn resolves_to_type(
5265        &self,
5266        analyzer: &CppGraphSource<'_>,
5267        file: &ProjectFile,
5268        raw_name: &str,
5269        target: &CodeUnit,
5270    ) -> bool {
5271        let Some(normalized) = normalize_reference_name(raw_name) else {
5272            return false;
5273        };
5274        let candidates = self.type_candidates(file, &normalized);
5275        if candidates.is_empty() {
5276            return self.parser_alias_resolves_to_type(analyzer, file, raw_name, target);
5277        }
5278        let Some(resolved) =
5279            self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
5280        else {
5281            return false;
5282        };
5283        same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
5284    }
5285
5286    pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
5287        let raw_target = cpp_alias_declaration_target_text(alias.signature()?)?;
5288        let resolved = self.resolve_type_for_declaration(alias.source(), alias, &raw_target)?;
5289        match resolved.kind() {
5290            CodeUnitType::Class => Some(resolved),
5291            _ if is_type_alias(&resolved) => self.alias_target(&resolved),
5292            _ => None,
5293        }
5294    }
5295
5296    pub fn canonical_type_for_reference(
5297        &self,
5298        file: &ProjectFile,
5299        raw_name: &str,
5300    ) -> Option<CodeUnit> {
5301        let resolved = self.resolve_type(file, raw_name)?;
5302        self.alias_target(&resolved).or(Some(resolved))
5303    }
5304
5305    pub fn parser_alias_resolves_to_type(
5306        &self,
5307        analyzer: &CppGraphSource<'_>,
5308        file: &ProjectFile,
5309        raw_name: &str,
5310        target: &CodeUnit,
5311    ) -> bool {
5312        let Some(alias_name) = normalize_reference_name(raw_name) else {
5313            return false;
5314        };
5315        let Some(cpp) = analyzer.cpp else {
5316            return false;
5317        };
5318        let matches_file = |source_file: &ProjectFile| {
5319            self.file_alias_matches(cpp, source_file, &alias_name, target)
5320        };
5321        self.visible_source_files_by_root.get(file).map_or_else(
5322            || matches_file(file),
5323            |files| files.iter().any(matches_file),
5324        )
5325    }
5326
5327    fn file_alias_matches(
5328        &self,
5329        cpp: &dyn CppSource,
5330        file: &ProjectFile,
5331        alias_name: &str,
5332        target: &CodeUnit,
5333    ) -> bool {
5334        let cell = {
5335            let mut cells = self.alias_cells.lock().expect("alias cell map lock");
5336            Arc::clone(
5337                cells
5338                    .entry(file.clone())
5339                    .or_insert_with(|| Arc::new(OnceLock::new())),
5340            )
5341        };
5342        cell.get_or_init(|| {
5343            #[cfg(any(test, feature = "test-support"))]
5344            {
5345                *self
5346                    .alias_source_parse_counts
5347                    .lock()
5348                    .expect("alias source parse count lock")
5349                    .entry(file.clone())
5350                    .or_default() += 1;
5351            }
5352            aliases_from_prepared_source(cpp, file).into_boxed_slice()
5353        })
5354        .iter()
5355        .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
5356    }
5357
5358    #[cfg(any(test, feature = "test-support"))]
5359    pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
5360        self.visible_source_files_by_root
5361            .get(file)
5362            .cloned()
5363            .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
5364    }
5365
5366    #[cfg(any(test, feature = "test-support"))]
5367    pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
5368        self.alias_source_parse_counts
5369            .lock()
5370            .expect("alias source parse count lock")
5371            .get(file)
5372            .copied()
5373            .unwrap_or(0)
5374    }
5375
5376    pub fn resolve_named(
5377        &self,
5378        file: &ProjectFile,
5379        raw_name: &str,
5380        kind: TargetKind,
5381    ) -> Option<CodeUnit> {
5382        let normalized = normalize_reference_name(raw_name)?;
5383        self.named_candidates_for_normalized(file, &normalized, kind)
5384            .into_iter()
5385            .next()
5386            .cloned()
5387    }
5388
5389    pub fn contains_named_symbol(
5390        &self,
5391        file: &ProjectFile,
5392        raw_name: &str,
5393        kind: TargetKind,
5394        target: &CodeUnit,
5395    ) -> bool {
5396        let Some(normalized) = normalize_reference_name(raw_name) else {
5397            return false;
5398        };
5399        self.named_candidates_for_normalized(file, &normalized, kind)
5400            .into_iter()
5401            .any(|unit| {
5402                matches_kind_for_lookup(unit, kind)
5403                    && reference_matches_unit(&normalized, unit)
5404                    && same_visible_symbol(unit, target)
5405            })
5406    }
5407
5408    pub fn named_candidates(
5409        &self,
5410        file: &ProjectFile,
5411        raw_name: &str,
5412        kind: TargetKind,
5413    ) -> Vec<CodeUnit> {
5414        let Some(normalized) = normalize_reference_name(raw_name) else {
5415            return Vec::new();
5416        };
5417        self.named_candidates_for_normalized(file, &normalized, kind)
5418            .into_iter()
5419            .cloned()
5420            .collect()
5421    }
5422
5423    pub fn resolve_known_non_target(
5424        &self,
5425        file: &ProjectFile,
5426        raw_name: &str,
5427        kind: TargetKind,
5428        target: &CodeUnit,
5429    ) -> bool {
5430        let Some(normalized) = normalize_reference_name(raw_name) else {
5431            return false;
5432        };
5433        normalized.contains("::")
5434            && self
5435                .named_candidates_for_normalized(file, &normalized, kind)
5436                .into_iter()
5437                .any(|unit| {
5438                    matches_kind_for_lookup(unit, kind)
5439                        && reference_matches_unit(&normalized, unit)
5440                        && !same_visible_symbol(unit, target)
5441                })
5442    }
5443
5444    pub fn resolve_call_return_binding(
5445        &self,
5446        analyzer: &CppGraphSource<'_>,
5447        file: &ProjectFile,
5448        raw_name: &str,
5449        arity: usize,
5450        lexical_namespace: Option<&str>,
5451        direct_type: Option<&CodeUnit>,
5452    ) -> Option<CppScanBinding> {
5453        let normalized = normalize_reference_name(raw_name)?;
5454        let mut candidates = Vec::new();
5455        for function in
5456            self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
5457        {
5458            if cpp_callable_arity(analyzer, function).accepts(arity)
5459                && !direct_type.is_some_and(|direct_type| {
5460                    self.callable_is_constructor_declaration(analyzer, function)
5461                        && type_owner_of(analyzer, function)
5462                            .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
5463                })
5464            {
5465                candidates.push(function.clone());
5466            }
5467        }
5468        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
5469        unanimous_return_binding(analyzer, self, file, &candidates)
5470    }
5471
5472    pub fn resolve_call_return_binding_without_arity(
5473        &self,
5474        analyzer: &CppGraphSource<'_>,
5475        file: &ProjectFile,
5476        raw_name: &str,
5477        lexical_namespace: Option<&str>,
5478        direct_type: Option<&CodeUnit>,
5479    ) -> (bool, Option<CppScanBinding>) {
5480        let Some(normalized) = normalize_reference_name(raw_name) else {
5481            return (false, None);
5482        };
5483        let mut candidates = self
5484            .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
5485            .into_iter()
5486            .filter(|function| {
5487                function.is_function()
5488                    && !direct_type.is_some_and(|direct_type| {
5489                        self.callable_is_constructor_declaration(analyzer, function)
5490                            && type_owner_of(analyzer, function)
5491                                .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
5492                    })
5493            })
5494            .cloned()
5495            .collect::<Vec<_>>();
5496        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
5497        let has_candidates = !candidates.is_empty();
5498        (
5499            has_candidates,
5500            unanimous_return_binding(analyzer, self, file, &candidates),
5501        )
5502    }
5503
5504    pub fn visible_identifier_candidates<'b>(
5505        &'b self,
5506        file: &ProjectFile,
5507        identifier: &str,
5508    ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
5509        self.visible_by_identifier
5510            .get(file)
5511            .and_then(|by_name| by_name.get(identifier))
5512            .into_iter()
5513            .flatten()
5514    }
5515
5516    /// Return terminal reference names that can denote `target` from `file`.
5517    ///
5518    /// The indexed candidate table covers ordinary declarations and aliases;
5519    /// parser-only aliases are read through their per-file cells so this path
5520    /// never reparses a source that has already been inspected by the visibility
5521    /// index.
5522    pub fn visible_type_reference_component_names_for_target(
5523        &self,
5524        analyzer: &CppGraphSource<'_>,
5525        file: &ProjectFile,
5526        target: &CodeUnit,
5527    ) -> HashSet<String> {
5528        let mut names = HashSet::from_iter([target.identifier().to_string()]);
5529        if let Some(metadata) = self.cpp_template_metadata.get(target) {
5530            names.insert(metadata.primary_name.clone());
5531        }
5532
5533        if let Some(by_identifier) = self.visible_by_identifier.get(file) {
5534            for (identifier, candidates) in by_identifier {
5535                if candidates.iter().any(|candidate| {
5536                    (candidate.is_class()
5537                        && (same_visible_symbol(candidate, target)
5538                            || self.compatible_primary_template_redeclarations(candidate, target)))
5539                        || (declared_type_alias(analyzer, candidate)
5540                            && self.alias_candidate_may_preserve_target(
5541                                analyzer, file, candidate, target,
5542                            ))
5543                }) {
5544                    names.insert(identifier.clone());
5545                }
5546            }
5547        }
5548
5549        names.extend(self.visible_parser_alias_names_for_target(file, target));
5550
5551        names
5552    }
5553
5554    pub fn indexed_structural_class_scope(
5555        &self,
5556        file: &ProjectFile,
5557        class: Node<'_>,
5558        source: &str,
5559    ) -> Option<Vec<String>> {
5560        let key = (file.clone(), class.start_byte(), class.end_byte());
5561        if let Some(cached) = self
5562            .indexed_structural_class_scopes
5563            .lock()
5564            .expect("C++ indexed structural-class scope cache poisoned")
5565            .get(&key)
5566            .cloned()
5567        {
5568            return cached;
5569        }
5570        let resolved = (|| {
5571            let name = class.child_by_field_name("name")?;
5572            let identifier = if name.kind() == "template_type" {
5573                node_text(name.child_by_field_name("name")?, source).to_string()
5574            } else {
5575                let mut components = Vec::new();
5576                append_cpp_name_components(name, source, &mut components)?;
5577                components.last()?.clone()
5578            };
5579            let visible = self
5580                .visible_identifier_candidates(file, &identifier)
5581                .cloned()
5582                .collect::<Vec<_>>();
5583            let mut visible = visible;
5584            for candidate in
5585                self.visible_by_file
5586                    .get(file)
5587                    .into_iter()
5588                    .flatten()
5589                    .filter(|candidate| {
5590                        self.cpp_template_metadata
5591                            .get(candidate)
5592                            .is_some_and(|metadata| metadata.primary_name == identifier)
5593                    })
5594            {
5595                if !visible
5596                    .iter()
5597                    .any(|existing| same_logical_symbol(existing, candidate))
5598                {
5599                    visible.push(candidate.clone());
5600                }
5601            }
5602            // Built once per call rather than per candidate; `cpp_source` rebuilds
5603            // the five-field source from the same `self.cpp` on every call.
5604            let cpp_source = self.cpp_source();
5605            let candidates = visible
5606                .iter()
5607                .filter(|candidate| {
5608                    candidate.source() == file
5609                        && candidate.is_class()
5610                        && !declared_type_alias(&cpp_source, candidate)
5611                        && self.cpp.ranges(candidate).iter().any(|range| {
5612                            range.start_byte <= class.start_byte()
5613                                && class.end_byte() <= range.end_byte
5614                        })
5615                })
5616                .collect::<Vec<_>>();
5617            let owner = if name.kind() == "template_type" {
5618                let expected = normalize_cpp_whitespace(node_text(name, source));
5619                let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
5620                let exact = candidates
5621                    .iter()
5622                    .copied()
5623                    .filter(|candidate| {
5624                        candidate
5625                            .fq()
5626                            .segments()
5627                            .iter()
5628                            .rev()
5629                            .find_map(|&segment| {
5630                                let (text, kind) = interner.resolve(segment);
5631                                matches!(
5632                                    kind,
5633                                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
5634                                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
5635                                )
5636                                .then_some(text)
5637                            })
5638                            .is_some_and(|text| text == expected)
5639                    })
5640                    .collect::<Vec<_>>();
5641                unique_logical_type_candidate(exact)
5642                    .or_else(|| unique_logical_type_candidate(candidates.clone()))?
5643            } else {
5644                unique_logical_type_candidate(candidates)?
5645            };
5646            Some(canonical_cpp_scope_components(&owner))
5647        })();
5648        self.indexed_structural_class_scopes
5649            .lock()
5650            .expect("C++ indexed structural-class scope cache poisoned")
5651            .insert(key, resolved.clone());
5652        resolved
5653    }
5654
5655    pub fn indexed_enclosing_owner_scope(
5656        &self,
5657        analyzer: &CppGraphSource<'_>,
5658        file: &ProjectFile,
5659        node: Node<'_>,
5660    ) -> Option<Vec<String>> {
5661        let anchor = std::iter::successors(Some(node), |current| current.parent())
5662            .find(|current| {
5663                matches!(
5664                    current.kind(),
5665                    "function_definition"
5666                        | "class_specifier"
5667                        | "struct_specifier"
5668                        | "union_specifier"
5669                )
5670            })
5671            .unwrap_or(node);
5672        let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
5673        if let Some(cached) = self
5674            .indexed_enclosing_owner_scopes
5675            .lock()
5676            .expect("C++ indexed enclosing-owner scope cache poisoned")
5677            .get(&key)
5678            .cloned()
5679        {
5680            return cached;
5681        }
5682        let resolved = (|| {
5683            let range = Range {
5684                start_byte: node.start_byte(),
5685                end_byte: node.end_byte(),
5686                start_line: node.start_position().row,
5687                end_line: node.end_position().row,
5688            };
5689            let start = analyzer.enclosing_code_unit(file, &range)?;
5690            let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
5691                start,
5692                |unit| self.cached_precise_parent_of(analyzer, unit),
5693            )
5694            .find(|unit| {
5695                unit.is_class()
5696                    && !analyzer
5697                        .type_alias_provider()
5698                        .is_some_and(|provider| provider.is_type_alias(unit))
5699            })?;
5700            Some(canonical_cpp_scope_components(&owner))
5701        })();
5702        self.indexed_enclosing_owner_scopes
5703            .lock()
5704            .expect("C++ indexed enclosing-owner scope cache poisoned")
5705            .insert(key, resolved.clone());
5706        resolved
5707    }
5708
5709    fn cached_precise_parent_of(
5710        &self,
5711        analyzer: &CppGraphSource<'_>,
5712        code_unit: &CodeUnit,
5713    ) -> Option<CodeUnit> {
5714        if let Some(cached) = self
5715            .precise_parent_cache
5716            .lock()
5717            .expect("C++ precise-parent cache poisoned")
5718            .get(code_unit)
5719            .cloned()
5720        {
5721            return cached;
5722        }
5723        let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
5724        self.precise_parent_cache
5725            .lock()
5726            .expect("C++ precise-parent cache poisoned")
5727            .insert(code_unit.clone(), resolved.clone());
5728        resolved
5729    }
5730
5731    pub fn callable_is_constructor_declaration(
5732        &self,
5733        analyzer: &CppGraphSource<'_>,
5734        candidate: &CodeUnit,
5735    ) -> bool {
5736        if !candidate.is_function() {
5737            return false;
5738        }
5739        let Some(prepared) = self.cpp.prepared_syntax(candidate.source()) else {
5740            return false;
5741        };
5742        let root = prepared.tree().root_node();
5743        let candidate_ranges = analyzer.ranges(candidate);
5744        let enclosed_by_matching_type = candidate_ranges.iter().any(|range| {
5745            let mut current = root
5746                .descendant_for_byte_range(range.start_byte, range.end_byte)
5747                .and_then(|node| node.parent());
5748            while let Some(node) = current {
5749                if matches!(
5750                    node.kind(),
5751                    "class_specifier" | "struct_specifier" | "union_specifier"
5752                ) {
5753                    return node
5754                        .child_by_field_name("name")
5755                        .map(|name| terminal_name(node_text(name, prepared.source())))
5756                        .is_some_and(|name| name == candidate.identifier());
5757                }
5758                current = node.parent();
5759            }
5760            false
5761        });
5762        if enclosed_by_matching_type {
5763            return true;
5764        }
5765        let indexed_containment = analyzer
5766            .declarations(candidate.source())
5767            .into_iter()
5768            .filter(|unit| unit.is_class() && unit.identifier() == candidate.identifier())
5769            .any(|owner| {
5770                analyzer.ranges(&owner).iter().any(|owner_range| {
5771                    candidate_ranges.iter().any(|candidate_range| {
5772                        owner_range.start_byte <= candidate_range.start_byte
5773                            && candidate_range.end_byte <= owner_range.end_byte
5774                    })
5775                })
5776            });
5777        if indexed_containment {
5778            return true;
5779        }
5780        let metadata = analyzer.signature_metadata(candidate);
5781        !metadata.is_empty()
5782            && metadata
5783                .iter()
5784                .all(|signature| signature.return_type_text().is_none())
5785    }
5786
5787    /// Whether a callable declaration is a class-template deduction guide.
5788    ///
5789    /// Tree-sitter represents `Box(T) -> Box<T>;` as a declaration with no
5790    /// type field whose function declarator owns a trailing return type. This
5791    /// structured shape distinguishes a guide from both a constructor (no
5792    /// trailing return) and an ordinary trailing-return function (an `auto`
5793    /// type field).
5794    pub fn callable_is_deduction_guide_declaration(
5795        &self,
5796        analyzer: &CppGraphSource<'_>,
5797        candidate: &CodeUnit,
5798    ) -> bool {
5799        if !candidate.is_function() {
5800            return false;
5801        }
5802        let Some(prepared) = self.cpp.prepared_syntax(candidate.source()) else {
5803            return false;
5804        };
5805        nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
5806            .into_iter()
5807            .any(|declaration| {
5808                if declaration.kind() != "declaration"
5809                    || declaration.child_by_field_name("type").is_some()
5810                {
5811                    return false;
5812                }
5813                let Some(declarator) = declaration.child_by_field_name("declarator") else {
5814                    return false;
5815                };
5816                if declarator.kind() != "function_declarator" {
5817                    return false;
5818                }
5819                let mut cursor = declarator.walk();
5820                let has_trailing_return = declarator
5821                    .named_children(&mut cursor)
5822                    .any(|child| child.kind() == "trailing_return_type");
5823                has_trailing_return
5824                    && declarator_name_node(declarator).is_some_and(|name| {
5825                        node_text(name, prepared.source()) == candidate.identifier()
5826                    })
5827            })
5828    }
5829
5830    /// Whether a callable occurrence is directly wrapped by a C++ template
5831    /// declaration. This deliberately inspects declaration syntax instead of
5832    /// inferring template status from the rendered signature.
5833    pub fn callable_is_template_declaration(
5834        &self,
5835        analyzer: &CppGraphSource<'_>,
5836        candidate: &CodeUnit,
5837    ) -> bool {
5838        if !candidate.is_function() {
5839            return false;
5840        }
5841        let Some(prepared) = self.cpp.prepared_syntax(candidate.source()) else {
5842            return false;
5843        };
5844        let root = prepared.tree().root_node();
5845        analyzer.ranges(candidate).iter().any(|range| {
5846            let Some(node) = node_for_exact_range(root, range)
5847                .or_else(|| root.descendant_for_byte_range(range.start_byte, range.end_byte))
5848            else {
5849                return false;
5850            };
5851            node.parent().is_some_and(|parent| {
5852                parent.kind() == "template_declaration"
5853                    && parent
5854                        .named_child(parent.named_child_count().saturating_sub(1))
5855                        .is_some_and(|declaration| same_node(declaration, node))
5856            })
5857        })
5858    }
5859
5860    pub fn type_name_candidates<'b>(
5861        &'b self,
5862        file: &ProjectFile,
5863        normalized: &str,
5864    ) -> Vec<&'b CodeUnit> {
5865        self.candidate_units(file, normalized, TargetKind::Type)
5866    }
5867
5868    pub fn visible_members_for_owner_name<'b>(
5869        &'b self,
5870        file: &ProjectFile,
5871        owner: &CodeUnit,
5872        name: &str,
5873    ) -> Vec<&'b CodeUnit> {
5874        self.visible_identifier_candidates(file, name)
5875            .filter(|unit| {
5876                // Structured owner pop on the unit's own `fq()` (shared with
5877                // `CodeUnitIndex::parent_of`), not a re-split of its rendered fqn
5878                // string.
5879                brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
5880                    .is_some_and(|parent| parent == owner.fq_name())
5881            })
5882            .collect()
5883    }
5884
5885    pub fn visible_member_for_owner_name(
5886        &self,
5887        file: &ProjectFile,
5888        owner: &CodeUnit,
5889        name: &str,
5890    ) -> VisibleMemberResolution {
5891        let candidates = self.visible_members_for_owner_name(file, owner, name);
5892        let mut callables = Vec::new();
5893        let mut non_callable = None;
5894        for candidate in candidates {
5895            if candidate.is_function() {
5896                callables.push(candidate.clone());
5897            } else if non_callable.is_none() {
5898                non_callable = Some(candidate.clone());
5899            }
5900        }
5901        match (callables.is_empty(), non_callable) {
5902            (false, None) => VisibleMemberResolution::Callable(callables),
5903            (true, Some(_)) => VisibleMemberResolution::NonCallable,
5904            (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
5905            (true, None) => VisibleMemberResolution::Missing,
5906        }
5907    }
5908
5909    fn field_declared_type_fact(
5910        &self,
5911        analyzer: &CppGraphSource<'_>,
5912        field: &CodeUnit,
5913    ) -> Option<DeclaredFieldTypeFact> {
5914        if let Some(cached) = self
5915            .field_type_facts
5916            .lock()
5917            .expect("C++ field type fact cache poisoned")
5918            .get(field)
5919            .cloned()
5920        {
5921            return cached;
5922        }
5923        let decoded = decode_field_declared_type_fact(analyzer, field);
5924        self.field_type_facts
5925            .lock()
5926            .expect("C++ field type fact cache poisoned")
5927            .insert(field.clone(), decoded.clone());
5928        decoded
5929    }
5930
5931    fn structured_alias_target(
5932        &self,
5933        analyzer: &CppGraphSource<'_>,
5934        unit: &CodeUnit,
5935    ) -> Option<StructuredAliasTarget> {
5936        if let Some(cached) = self
5937            .structured_alias_targets
5938            .lock()
5939            .expect("C++ structured alias target cache poisoned")
5940            .get(unit)
5941            .cloned()
5942        {
5943            return cached;
5944        }
5945        let decoded = decode_structured_alias_target(analyzer, unit);
5946        self.structured_alias_targets
5947            .lock()
5948            .expect("C++ structured alias target cache poisoned")
5949            .insert(unit.clone(), decoded.clone());
5950        decoded
5951    }
5952
5953    pub fn type_candidates<'b>(
5954        &'b self,
5955        file: &ProjectFile,
5956        normalized: &str,
5957    ) -> Vec<&'b CodeUnit> {
5958        let mut candidates = self
5959            .candidate_units(file, normalized, TargetKind::Type)
5960            .into_iter()
5961            .filter(|unit| unit.kind() == CodeUnitType::Class || is_type_alias(unit))
5962            .collect::<Vec<_>>();
5963        dedup_unit_refs(&mut candidates);
5964        candidates
5965    }
5966
5967    pub fn named_candidates_for_normalized<'b>(
5968        &'b self,
5969        file: &ProjectFile,
5970        normalized: &str,
5971        kind: TargetKind,
5972    ) -> Vec<&'b CodeUnit> {
5973        let mut candidates = self
5974            .candidate_units(file, normalized, kind)
5975            .into_iter()
5976            .filter(|unit| {
5977                matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
5978            })
5979            .collect::<Vec<_>>();
5980        dedup_unit_refs(&mut candidates);
5981        candidates
5982    }
5983
5984    pub fn candidate_units<'b>(
5985        &'b self,
5986        file: &ProjectFile,
5987        normalized: &str,
5988        kind: TargetKind,
5989    ) -> Vec<&'b CodeUnit> {
5990        if normalized.contains("::") {
5991            // `normalized` comes from `normalize_cpp_reference_text`, which
5992            // truncates at the first `(`/`{`/`<`, leaving a plain `::`-joined
5993            // qualified-id with no embedded `.`/`/`/`\` and operator tokens
5994            // kept intact by the shared splitter's operator merge — the same
5995            // domain `cpp_reference_fqn_candidates` below already parses with
5996            // the shared splitter. Re-tokenizing and taking the last segment
5997            // reproduces `rsplit("::").find(non-empty)`'s terminal-component
5998            // scan exactly.
5999            let Some(identifier) = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6000                brokk_bifrost_core::analyzer::Language::Cpp,
6001                normalized,
6002            )
6003            .pop() else {
6004                return Vec::new();
6005            };
6006            let fqns = cpp_reference_fqn_candidates(normalized, kind);
6007            return self
6008                .visible_identifier_candidates(file, &identifier)
6009                .filter(|unit| {
6010                    #[cfg(any(test, feature = "test-support"))]
6011                    self.qualified_candidate_inspections
6012                        .fetch_add(1, Ordering::Relaxed);
6013                    fqns.iter().any(|fqn| unit.fq_name() == *fqn)
6014                        || canonical_cpp_name_matches(unit, normalized)
6015                })
6016                .collect();
6017        }
6018        self.visible_identifier_candidates(file, normalized)
6019            .collect()
6020    }
6021
6022    #[cfg(any(test, feature = "test-support"))]
6023    pub fn reset_qualified_candidate_inspections(&self) {
6024        self.qualified_candidate_inspections
6025            .store(0, Ordering::Relaxed);
6026    }
6027
6028    #[cfg(any(test, feature = "test-support"))]
6029    pub fn qualified_candidate_inspections(&self) -> usize {
6030        self.qualified_candidate_inspections.load(Ordering::Relaxed)
6031    }
6032
6033    #[cfg(any(test, feature = "test-support"))]
6034    pub fn reset_target_preserving_type_resolution_count(&self) {
6035        self.target_preserving_type_resolution_count
6036            .store(0, Ordering::Relaxed);
6037    }
6038
6039    #[cfg(any(test, feature = "test-support"))]
6040    pub fn target_preserving_type_resolution_count(&self) -> usize {
6041        self.target_preserving_type_resolution_count
6042            .load(Ordering::Relaxed)
6043    }
6044
6045    #[cfg(any(test, feature = "test-support"))]
6046    pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
6047        self.visible_parser_alias_name_set_build_count
6048            .load(Ordering::Relaxed)
6049    }
6050
6051    #[cfg(any(test, feature = "test-support"))]
6052    pub fn visible_parser_alias_target_names_build_count(&self) -> usize {
6053        self.visible_parser_alias_target_names_build_count
6054            .load(Ordering::Relaxed)
6055    }
6056}
6057
6058#[derive(Default)]
6059struct IncludeGraph {
6060    targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
6061}
6062
6063impl IncludeGraph {
6064    fn extend_with<F>(
6065        &mut self,
6066        root: &ProjectFile,
6067        cancellation: Option<&CancellationToken>,
6068        targets_for: &mut F,
6069    ) where
6070        F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
6071    {
6072        let mut stack = vec![root.clone()];
6073        while let Some(file) = stack.pop() {
6074            if cancellation.is_some_and(CancellationToken::is_cancelled) {
6075                break;
6076            }
6077            if self.targets_by_file.contains_key(&file) {
6078                continue;
6079            }
6080            let targets = targets_for(&file);
6081            stack.extend(targets.iter().cloned());
6082            self.targets_by_file.insert(file, targets);
6083        }
6084    }
6085
6086    fn files(&self) -> impl Iterator<Item = &ProjectFile> {
6087        self.targets_by_file.keys()
6088    }
6089
6090    fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
6091        self.targets_by_file
6092            .get(file)
6093            .map(Vec::as_slice)
6094            .unwrap_or_default()
6095    }
6096}
6097
6098pub struct VisibilityData {
6099    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
6100    pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
6101}
6102
6103pub fn build_visibility_data<F, D>(
6104    roots: &HashSet<ProjectFile>,
6105    cancellation: Option<&CancellationToken>,
6106    mut targets_for: F,
6107    mut declarations_for: D,
6108) -> VisibilityData
6109where
6110    F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
6111    D: FnMut(&ProjectFile) -> BTreeSet<CodeUnit>,
6112{
6113    let mut include_graph = IncludeGraph::default();
6114    for file in roots {
6115        if cancellation.is_some_and(CancellationToken::is_cancelled) {
6116            break;
6117        }
6118        include_graph.extend_with(file, cancellation, &mut targets_for);
6119    }
6120    let declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
6121        .files()
6122        .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
6123        .map(|file| (file.clone(), declarations_for(file)))
6124        .collect();
6125    let mut visible_by_file = HashMap::default();
6126    let mut visible_source_files_by_root = HashMap::default();
6127    for file in roots {
6128        if cancellation.is_some_and(CancellationToken::is_cancelled) {
6129            break;
6130        }
6131        let mut visited = HashSet::default();
6132        let mut visible = HashSet::default();
6133        collect_visible_declarations(
6134            &include_graph,
6135            &declarations_by_file,
6136            file,
6137            &mut visited,
6138            &mut visible,
6139            cancellation,
6140        );
6141        visible_by_file.insert(file.clone(), visible);
6142        visible_source_files_by_root.insert(file.clone(), visited);
6143    }
6144    VisibilityData {
6145        visible_by_file,
6146        visible_source_files_by_root,
6147    }
6148}
6149
6150/// Admit the class that an out-of-line definition proves is in scope.
6151///
6152/// `Owner::member(...) { ... }` in a file is structured proof that `Owner`
6153/// names a class-like entity in that file's scope: a member declaration can
6154/// live in a file other than its class's only when it is written out of line.
6155/// A file a build concatenates rather than compiles carries no `#include` edge
6156/// to the header declaring `Owner` -- google/wuffs
6157/// `internal/cgen/auxiliary/image.cc` defines
6158/// `DecodeImageResult::DecodeImageResult` and never includes `image.hh` -- so
6159/// every unqualified member and constructor reference in it had no candidate at
6160/// all (#1832).
6161///
6162/// The evidence is the indexed declaration's own owner name, taken from its
6163/// `FqName`, so this stays a structured answer rather than a text fallback.
6164/// Only an owner the file cannot already see is admitted: that is what keeps a
6165/// header declaring its own class from additionally seeing every same-named
6166/// class in the workspace, and it makes the pass free for the ordinary file
6167/// whose owners are all visible.
6168fn extend_with_out_of_line_owner_bindings(
6169    cpp: &dyn CppSource,
6170    visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
6171) {
6172    for (file, visible) in visible_by_file.iter_mut() {
6173        // The include-closure walk seeds every root with its own declarations,
6174        // so the file's members are already here; re-reading them from the
6175        // analyzer would pay for the same declaration set twice.
6176        let mut unseen_owners: HashSet<String> = visible
6177            .iter()
6178            .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
6179            .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
6180            .collect();
6181        if unseen_owners.is_empty() {
6182            continue;
6183        }
6184        for unit in visible.iter().filter(|unit| unit.is_class()) {
6185            unseen_owners.remove(&unit.fq_name());
6186        }
6187        let admitted = unseen_owners
6188            .iter()
6189            .flat_map(|owner| cpp.definitions(owner))
6190            .filter(CodeUnit::is_class)
6191            .collect::<Vec<_>>();
6192        visible.extend(admitted);
6193    }
6194}
6195
6196pub enum VisibleMemberResolution {
6197    Callable(Vec<CodeUnit>),
6198    NonCallable,
6199    AmbiguousKind,
6200    Missing,
6201}
6202
6203#[derive(Clone)]
6204pub enum EnclosingMemberOwnerResolution {
6205    Owner(CodeUnit),
6206    Ambiguous,
6207    Missing,
6208}
6209
6210pub fn resolve_declaring_member_owner(
6211    analyzer: &CppGraphSource<'_>,
6212    visibility: &VisibilityIndex<'_>,
6213    file: &ProjectFile,
6214    receiver_owner: &CodeUnit,
6215    member_name: &str,
6216) -> EnclosingMemberOwnerResolution {
6217    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
6218        return EnclosingMemberOwnerResolution::Missing;
6219    };
6220    let Some(receiver_owner) =
6221        visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
6222    else {
6223        return EnclosingMemberOwnerResolution::Ambiguous;
6224    };
6225    let resolve_level = |frontier: &[CodeUnit]| {
6226        let mut member_owners = Vec::new();
6227        for raw_owner in frontier {
6228            let Some(owner) =
6229                visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
6230            else {
6231                return EnclosingMemberOwnerResolution::Ambiguous;
6232            };
6233            for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
6234                let Some(member_owner) = type_owner_of(analyzer, member) else {
6235                    return EnclosingMemberOwnerResolution::Ambiguous;
6236                };
6237                if !member_owners
6238                    .iter()
6239                    .any(|existing| same_visible_symbol(existing, &member_owner))
6240                {
6241                    member_owners.push(member_owner);
6242                }
6243            }
6244        }
6245        match member_owners.len() {
6246            0 => EnclosingMemberOwnerResolution::Missing,
6247            1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
6248            _ => EnclosingMemberOwnerResolution::Ambiguous,
6249        }
6250    };
6251    // The first declaration on each structured base path hides deeper names,
6252    // regardless of whether its callable overload is applicable at a particular
6253    // call site. Applicability is checked only after this owner is established.
6254    let direct = resolve_level(std::slice::from_ref(&receiver_owner));
6255    if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
6256        return direct;
6257    }
6258    let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
6259    let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
6260    let mut path_matches = Vec::new();
6261    while let Some(raw_owner) = stack.pop() {
6262        let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
6263        else {
6264            return EnclosingMemberOwnerResolution::Ambiguous;
6265        };
6266        // Persisted hierarchy edges do not encode virtual-base or base-subobject paths.
6267        // Propagate at most two occurrences of each owner: that preserves the distinction
6268        // between one and multiple resolving base paths without exponential diamond walks.
6269        let propagated = propagated_counts.entry(owner.clone()).or_default();
6270        if *propagated == 2 {
6271            continue;
6272        }
6273        *propagated += 1;
6274        match resolve_level(std::slice::from_ref(&owner)) {
6275            EnclosingMemberOwnerResolution::Owner(owner) => {
6276                path_matches.push(owner);
6277                if path_matches.len() == 2 {
6278                    return EnclosingMemberOwnerResolution::Ambiguous;
6279                }
6280            }
6281            EnclosingMemberOwnerResolution::Ambiguous => {
6282                return EnclosingMemberOwnerResolution::Ambiguous;
6283            }
6284            EnclosingMemberOwnerResolution::Missing => {
6285                stack.extend(hierarchy.get_direct_ancestors(&owner));
6286            }
6287        }
6288    }
6289    match path_matches.len() {
6290        0 => EnclosingMemberOwnerResolution::Missing,
6291        1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
6292        _ => unreachable!("base-path matches are capped at one before returning"),
6293    }
6294}
6295
6296/// Resolve the declaring owner of a callable after applying a member
6297/// `using <Base>::<member>;` declaration to one exact call arity.
6298///
6299/// Ordinary member lookup is intentionally name-based: the first class that
6300/// declares a name hides the same name on deeper bases. A member
6301/// using-declaration is the one exception. When none of the declarations on
6302/// that first owner accepts the call arity, it can reintroduce an applicable
6303/// overload from the named base. If a declaration on the first owner does
6304/// accept the arity, argument types would be needed to choose between it and
6305/// a same-arity introduced overload, so this resolver conservatively keeps the
6306/// ordinary owner (#1835/#1843).
6307///
6308/// The caller supplies ordinary name-based owner resolution so a file scan can
6309/// reuse its existing owner cache before applying this callable-only exception.
6310pub fn resolve_declaring_callable_owner(
6311    analyzer: &CppGraphSource<'_>,
6312    visibility: &VisibilityIndex<'_>,
6313    file: &ProjectFile,
6314    ordinary: EnclosingMemberOwnerResolution,
6315    member_name: &str,
6316    call_arity: usize,
6317) -> EnclosingMemberOwnerResolution {
6318    let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
6319        return ordinary;
6320    };
6321    if visibility
6322        .visible_members_for_owner_name(file, ordinary_owner, member_name)
6323        .into_iter()
6324        .any(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity))
6325    {
6326        return ordinary;
6327    }
6328
6329    let mut pending = match member_using_declaration_bases(
6330        analyzer,
6331        visibility,
6332        file,
6333        ordinary_owner,
6334        member_name,
6335    ) {
6336        Ok(bases) => bases,
6337        Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
6338    };
6339    let mut visited = HashSet::default();
6340    let mut introduced_owners = Vec::new();
6341    while let Some(owner) = pending.pop() {
6342        if !visited.insert(owner.clone()) {
6343            continue;
6344        }
6345        let accepts_arity = visibility
6346            .visible_members_for_owner_name(file, &owner, member_name)
6347            .into_iter()
6348            .any(|unit| {
6349                unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity)
6350            });
6351        if accepts_arity {
6352            if !introduced_owners
6353                .iter()
6354                .any(|existing| same_visible_symbol(existing, &owner))
6355            {
6356                introduced_owners.push(owner);
6357            }
6358            continue;
6359        }
6360        match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
6361            Ok(bases) => pending.extend(bases),
6362            Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
6363        }
6364    }
6365    match introduced_owners.as_slice() {
6366        [] => ordinary,
6367        [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
6368        _ => EnclosingMemberOwnerResolution::Ambiguous,
6369    }
6370}
6371
6372fn member_using_declaration_bases(
6373    analyzer: &CppGraphSource<'_>,
6374    visibility: &VisibilityIndex<'_>,
6375    file: &ProjectFile,
6376    owner: &CodeUnit,
6377    member_name: &str,
6378) -> Result<Vec<CodeUnit>, ()> {
6379    let Some(source) = analyzer.get_source(owner, false) else {
6380        return Ok(Vec::new());
6381    };
6382    let scopes = cpp_member_using_declaration_scopes(&source, member_name);
6383    if scopes.is_empty() {
6384        return Ok(Vec::new());
6385    }
6386    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
6387        return Ok(Vec::new());
6388    };
6389    let mut bases = Vec::new();
6390    for raw_ancestor in hierarchy.get_ancestors(owner) {
6391        let Some(ancestor) =
6392            visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
6393        else {
6394            return Err(());
6395        };
6396        let qualified = cpp_name_for(&ancestor);
6397        if scopes
6398            .iter()
6399            .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
6400            && !bases
6401                .iter()
6402                .any(|existing| same_visible_symbol(existing, &ancestor))
6403        {
6404            bases.push(ancestor);
6405        }
6406    }
6407    Ok(bases)
6408}
6409
6410pub fn lexical_component_tiers<'a>(
6411    components: &'a [String],
6412    global: bool,
6413    lexical_scope: &'a [String],
6414) -> impl Iterator<Item = Vec<String>> + 'a {
6415    let first_prefix_len = if global { 0 } else { lexical_scope.len() };
6416    (0..=first_prefix_len).rev().map(move |prefix_len| {
6417        let mut qualified = Vec::with_capacity(prefix_len + components.len());
6418        qualified.extend_from_slice(&lexical_scope[..prefix_len]);
6419        qualified.extend_from_slice(components);
6420        qualified
6421    })
6422}
6423
6424pub fn build_visible_identifier_index(
6425    analyzer: &CppGraphSource<'_>,
6426    visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
6427    visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
6428    global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
6429) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
6430    let mut out = HashMap::default();
6431    for (file, visible) in visible_by_file {
6432        let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
6433        for unit in visible {
6434            if unit.is_field()
6435                && !visible_source_files_by_root
6436                    .get(file)
6437                    .is_some_and(|sources| sources.contains(unit.source()))
6438                && cpp_global_field_has_internal_linkage_cached(
6439                    analyzer,
6440                    global_field_internal_linkage,
6441                    unit,
6442                )
6443            {
6444                continue;
6445            }
6446            by_identifier
6447                .entry(unit.identifier().to_string())
6448                .or_default()
6449                .push(unit.clone());
6450        }
6451        for units in by_identifier.values_mut() {
6452            sort_lookup_units(units);
6453            units.dedup();
6454        }
6455        out.insert(file.clone(), by_identifier);
6456    }
6457    out
6458}
6459
6460fn sort_lookup_units(units: &mut [CodeUnit]) {
6461    units.sort_by(|left, right| {
6462        left.fq_name()
6463            .cmp(&right.fq_name())
6464            .then_with(|| left.signature().cmp(&right.signature()))
6465            .then_with(|| left.source().cmp(right.source()))
6466            .then_with(|| left.kind().cmp(&right.kind()))
6467            .then_with(|| {
6468                left.package_segment_count()
6469                    .cmp(&right.package_segment_count())
6470            })
6471            .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
6472            .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
6473    });
6474}
6475
6476fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
6477    let interner = segment_interner();
6478    for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
6479        let (left_text, left_kind) = interner.resolve(left_id);
6480        let (right_text, right_kind) = interner.resolve(right_id);
6481        let order = left_text
6482            .cmp(right_text)
6483            .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
6484        if order != CmpOrdering::Equal {
6485            return order;
6486        }
6487    }
6488    left.len().cmp(&right.len())
6489}
6490
6491const fn segment_kind_order(kind: SegmentKind) -> u8 {
6492    match kind {
6493        SegmentKind::Path => 0,
6494        SegmentKind::Package => 1,
6495        SegmentKind::Type => 2,
6496        SegmentKind::Companion => 3,
6497        SegmentKind::Nested => 4,
6498        SegmentKind::Member => 5,
6499        SegmentKind::Unknown => 6,
6500    }
6501}
6502
6503fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
6504    let mut deduped = Vec::with_capacity(units.len());
6505    for unit in units.drain(..) {
6506        if !deduped.contains(&unit) {
6507            deduped.push(unit);
6508        }
6509    }
6510    *units = deduped;
6511}
6512
6513pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
6514    // Same domain as `candidate_units` above: `reference` is a plain
6515    // `::`-joined qualified-id with operator tokens kept intact by the shared
6516    // splitter's operator merge.
6517    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6518        brokk_bifrost_core::analyzer::Language::Cpp,
6519        reference,
6520    );
6521    if parts.is_empty() {
6522        return Vec::new();
6523    }
6524
6525    let mut candidates = Vec::new();
6526    for package_len in 0..parts.len() {
6527        let package = parts[..package_len].join("::");
6528        let rest = &parts[package_len..];
6529        if rest.is_empty() {
6530            continue;
6531        }
6532        match kind {
6533            TargetKind::Type | TargetKind::Constructor => {
6534                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
6535                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
6536            }
6537            TargetKind::FreeFunction
6538            | TargetKind::Method
6539            | TargetKind::GlobalField
6540            | TargetKind::MemberField
6541            | TargetKind::Macro => {
6542                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
6543                if rest.len() > 1 {
6544                    let owner = rest[..rest.len() - 1].join("$");
6545                    let short = format!("{}.{}", owner, rest[rest.len() - 1]);
6546                    push_cpp_fqn_candidate(&mut candidates, &package, &short);
6547                }
6548            }
6549        }
6550    }
6551    candidates
6552}
6553
6554fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
6555    let fqn = if package.is_empty() {
6556        short.to_string()
6557    } else {
6558        format!("{package}.{short}")
6559    };
6560    if !out.contains(&fqn) {
6561        out.push(fqn);
6562    }
6563}
6564
6565pub fn infer_cpp_initializer_type(
6566    analyzer: &CppGraphSource<'_>,
6567    visibility: &VisibilityIndex<'_>,
6568    file: &ProjectFile,
6569    source: &str,
6570    node: Node<'_>,
6571) -> Option<CodeUnit> {
6572    infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
6573        .and_then(|binding| binding.unit)
6574}
6575
6576pub fn infer_cpp_initializer_binding(
6577    analyzer: &CppGraphSource<'_>,
6578    visibility: &VisibilityIndex<'_>,
6579    file: &ProjectFile,
6580    source: &str,
6581    node: Node<'_>,
6582    receiver_resolver: Option<&ReceiverResolver<'_>>,
6583) -> Option<CppScanBinding> {
6584    match node.kind() {
6585        "new_expression" => {
6586            let text = normalize_cpp_whitespace(node_text(node, source));
6587            let rest = text.strip_prefix("new ").unwrap_or(text.as_str());
6588            let type_text = rest.split(['(', '{']).next().unwrap_or(rest);
6589            let name = normalize_cpp_type_name(type_text);
6590            Some(CppScanBinding::from_type_name(
6591                name.clone(),
6592                visibility.resolve_type(file, &name),
6593                1,
6594            ))
6595        }
6596        "call_expression" => node.child_by_field_name("function").and_then(|function| {
6597            let function_text = node_text(function, source);
6598            let direct_type_binding = visibility
6599                .resolve_type(file, function_text)
6600                .map(|unit| CppScanBinding::from_unit(unit, 0));
6601            if function.kind() == "template_function" && direct_type_binding.is_some() {
6602                let lexical_namespace = enclosing_namespace_context(node, source);
6603                let arity = visibility.call_arity_evidence(file, node, source).exact();
6604                if let Some(arity) = arity
6605                    && let Some(binding) = visibility.resolve_call_return_binding(
6606                        analyzer,
6607                        file,
6608                        function_text,
6609                        arity,
6610                        lexical_namespace.as_deref(),
6611                        direct_type_binding
6612                            .as_ref()
6613                            .and_then(|binding| binding.unit.as_ref()),
6614                    )
6615                {
6616                    return Some(binding);
6617                }
6618                let (has_callable, callable_binding) = visibility
6619                    .resolve_call_return_binding_without_arity(
6620                        analyzer,
6621                        file,
6622                        function_text,
6623                        lexical_namespace.as_deref(),
6624                        direct_type_binding
6625                            .as_ref()
6626                            .and_then(|binding| binding.unit.as_ref()),
6627                    );
6628                if let Some(binding) = callable_binding {
6629                    return Some(binding);
6630                }
6631                if has_callable {
6632                    return None;
6633                }
6634                return direct_type_binding;
6635            }
6636            let arity = visibility.call_arity_evidence(file, node, source).exact()?;
6637            let direct_type_binding_for_call = direct_type_binding.clone();
6638            resolve_static_method_call_return_binding(
6639                analyzer, visibility, file, source, function, arity,
6640            )
6641            .or_else(|| {
6642                // An applicable free function supplies the receiver value
6643                // before an unrelated visible type with the same terminal
6644                // name. The direct type still excludes its own constructor
6645                // declaration below and remains the construction fallback.
6646                visibility.resolve_call_return_binding(
6647                    analyzer,
6648                    file,
6649                    function_text,
6650                    arity,
6651                    enclosing_namespace_context(node, source).as_deref(),
6652                    direct_type_binding_for_call
6653                        .as_ref()
6654                        .and_then(|binding| binding.unit.as_ref()),
6655                )
6656            })
6657            .or(direct_type_binding)
6658            .or_else(|| {
6659                resolve_field_method_call_return_binding(
6660                    analyzer,
6661                    visibility,
6662                    file,
6663                    source,
6664                    function,
6665                    arity,
6666                    receiver_resolver,
6667                )
6668            })
6669        }),
6670        _ => None,
6671    }
6672}
6673
6674fn resolve_static_method_call_return_binding(
6675    analyzer: &CppGraphSource<'_>,
6676    visibility: &VisibilityIndex<'_>,
6677    file: &ProjectFile,
6678    source: &str,
6679    function: Node<'_>,
6680    arity: usize,
6681) -> Option<CppScanBinding> {
6682    if function.kind() != "qualified_identifier" {
6683        return None;
6684    }
6685    let qualified = normalize_cpp_reference_text(node_text(function, source));
6686    // A C++ qualified-id is `::`-joined with no embedded delimiters in any
6687    // single component (the shared splitter's operator-token merge keeps
6688    // `operator+`-style names intact), so re-tokenizing with the shared
6689    // structured splitter and peeling the terminal segment reproduces
6690    // `rsplit_once("::")`'s (owner, member) split exactly — same shape as
6691    // `cpp_out_of_line_function_owner`'s `qualified` split above.
6692    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6693        brokk_bifrost_core::analyzer::Language::Cpp,
6694        &qualified,
6695    );
6696    let (owner_text, member_name) = match parts.split_last() {
6697        Some((member, owner_parts)) if !owner_parts.is_empty() => {
6698            (owner_parts.join("::"), member.clone())
6699        }
6700        _ => {
6701            let scope = function.child_by_field_name("scope")?;
6702            let name = function.child_by_field_name("name")?;
6703            (
6704                node_text(scope, source).to_string(),
6705                node_text(name, source).to_string(),
6706            )
6707        }
6708    };
6709    let owner = visibility.resolve_type(file, &owner_text)?;
6710    let candidates = visibility
6711        .visible_members_for_owner_name(file, &owner, &member_name)
6712        .into_iter()
6713        .filter(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity))
6714        .cloned()
6715        .collect::<Vec<_>>();
6716    unanimous_return_binding(analyzer, visibility, file, &candidates)
6717}
6718
6719fn resolve_field_method_call_return_binding(
6720    analyzer: &CppGraphSource<'_>,
6721    visibility: &VisibilityIndex<'_>,
6722    file: &ProjectFile,
6723    source: &str,
6724    function: Node<'_>,
6725    arity: usize,
6726    receiver_resolver: Option<&ReceiverResolver<'_>>,
6727) -> Option<CppScanBinding> {
6728    if function.kind() != "field_expression" {
6729        return None;
6730    }
6731    let receiver_resolver = receiver_resolver?;
6732    let field = function.child_by_field_name("field")?;
6733    let member_name = node_text(function_terminal_node(field), source);
6734    let receiver = function
6735        .child_by_field_name("argument")
6736        .or_else(|| function.named_child(0))?;
6737    let owners = receiver_resolver(receiver, source);
6738    let mut candidates = Vec::new();
6739    for owner in owners {
6740        let declaring_owner =
6741            match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
6742                EnclosingMemberOwnerResolution::Owner(owner) => owner,
6743                EnclosingMemberOwnerResolution::Missing => continue,
6744                EnclosingMemberOwnerResolution::Ambiguous => return None,
6745            };
6746        candidates.extend(
6747            visibility
6748                .visible_members_for_owner_name(file, &declaring_owner, member_name)
6749                .into_iter()
6750                .filter(|unit| {
6751                    unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity)
6752                })
6753                .cloned(),
6754        );
6755    }
6756    unanimous_return_binding(analyzer, visibility, file, &candidates)
6757}
6758
6759fn unanimous_return_binding(
6760    analyzer: &CppGraphSource<'_>,
6761    visibility: &VisibilityIndex<'_>,
6762    file: &ProjectFile,
6763    candidates: &[CodeUnit],
6764) -> Option<CppScanBinding> {
6765    let mut resolved_return: Option<CppScanBinding> = None;
6766    for function in candidates {
6767        let metadata = analyzer.signature_metadata(function);
6768        let return_types = if metadata.is_empty() {
6769            vec![cpp_function_return_type_text(analyzer, function)?]
6770        } else {
6771            metadata
6772                .iter()
6773                .map(|metadata| metadata.return_type_text().map(str::to_string))
6774                .collect::<Option<Vec<_>>>()?
6775        };
6776        for return_text in return_types {
6777            let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
6778            let name = normalize_cpp_type_name(&return_text);
6779            let binding = CppScanBinding::from_type_name(
6780                name.clone(),
6781                visibility
6782                    .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
6783                indirection,
6784            );
6785            if let Some(existing) = resolved_return.as_ref()
6786                && (existing.indirection != binding.indirection
6787                    || match (&existing.unit, &binding.unit) {
6788                        (Some(left), Some(right)) => !same_visible_symbol(left, right),
6789                        (None, None) => existing.type_name != binding.type_name,
6790                        (Some(_), None) | (None, Some(_)) => true,
6791                    })
6792            {
6793                return None;
6794            }
6795            resolved_return = Some(binding);
6796        }
6797    }
6798    resolved_return
6799}
6800
6801fn aliases_from_prepared_source(cpp: &dyn CppSource, file: &ProjectFile) -> Vec<CppAlias> {
6802    let Some(prepared) = cpp.prepared_syntax(file) else {
6803        return Vec::new();
6804    };
6805    let mut aliases = Vec::new();
6806    collect_cpp_aliases(prepared.tree().root_node(), prepared.source(), &mut aliases);
6807    aliases
6808}
6809
6810fn collect_cpp_aliases(root: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
6811    let mut stack = vec![root];
6812    while let Some(node) = stack.pop() {
6813        match node.kind() {
6814            "alias_declaration" if alias_has_visible_file_scope(node) => {
6815                if let Some(alias) = cpp_alias_from_alias_declaration(node, source) {
6816                    out.push(alias);
6817                }
6818            }
6819            "type_definition" if alias_has_visible_file_scope(node) => {
6820                collect_typedef_aliases(node, source, out)
6821            }
6822            _ => {}
6823        }
6824
6825        for index in (0..node.named_child_count()).rev() {
6826            if let Some(child) = node.named_child(index) {
6827                stack.push(child);
6828            }
6829        }
6830    }
6831}
6832
6833fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
6834    let mut current = node.parent();
6835    while let Some(parent) = current {
6836        match parent.kind() {
6837            "translation_unit"
6838            | "namespace_definition"
6839            | "declaration_list"
6840            | "linkage_specification" => current = parent.parent(),
6841            "template_declaration" => current = parent.parent(),
6842            _ => return false,
6843        }
6844    }
6845    true
6846}
6847
6848fn cpp_alias_from_alias_declaration(node: Node<'_>, source: &str) -> Option<CppAlias> {
6849    let name = node
6850        .child_by_field_name("name")
6851        .and_then(|node| normalize_reference_name(node_text(node, source)))?;
6852    let target = node
6853        .child_by_field_name("type")
6854        .and_then(|node| normalize_reference_name(node_text(node, source)))?;
6855    Some(CppAlias {
6856        name,
6857        target,
6858        namespace: enclosing_namespace_context(node, source),
6859    })
6860}
6861
6862fn collect_typedef_aliases(node: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
6863    let Some(type_node) = node.child_by_field_name("type") else {
6864        return;
6865    };
6866    let Some(target) = normalize_reference_name(node_text(type_node, source)) else {
6867        return;
6868    };
6869
6870    let mut cursor = node.walk();
6871    for child in node.named_children(&mut cursor) {
6872        if same_node(child, type_node) {
6873            continue;
6874        }
6875        if let Some(name) = extract_typedef_declarator_name(child, source) {
6876            out.push(CppAlias {
6877                name,
6878                target: target.clone(),
6879                namespace: enclosing_namespace_context(node, source),
6880            });
6881        }
6882    }
6883}
6884
6885fn extract_typedef_declarator_name(node: Node<'_>, source: &str) -> Option<String> {
6886    match node.kind() {
6887        "identifier" | "field_identifier" | "type_identifier" | "qualified_identifier" => {
6888            normalize_reference_name(node_text(node, source))
6889        }
6890        _ => node
6891            .child_by_field_name("declarator")
6892            .or_else(|| node.child_by_field_name("name"))
6893            .or_else(|| last_named_child(node))
6894            .and_then(|child| extract_typedef_declarator_name(child, source)),
6895    }
6896}
6897
6898fn last_named_child(node: Node<'_>) -> Option<Node<'_>> {
6899    let count = node.named_child_count();
6900    if count == 0 {
6901        None
6902    } else {
6903        node.named_child(count - 1)
6904    }
6905}
6906
6907pub fn collect_include_closure(
6908    analyzer: &CppGraphSource<'_>,
6909    include_targets: &IncludeTargetIndex,
6910    file: &ProjectFile,
6911    out: &mut HashSet<ProjectFile>,
6912    cancellation: Option<&CancellationToken>,
6913) {
6914    let mut stack = vec![file.clone()];
6915    while let Some(file) = stack.pop() {
6916        if cancellation.is_some_and(CancellationToken::is_cancelled) {
6917            break;
6918        }
6919        if !out.insert(file.clone()) {
6920            continue;
6921        }
6922        let imports = analyzer.import_statements(&file);
6923        for include in cpp_include_paths(&imports) {
6924            for target in resolve_include_targets_with_index(&file, &include, include_targets) {
6925                stack.push(target);
6926            }
6927        }
6928    }
6929}
6930
6931fn collect_visible_declarations(
6932    include_graph: &IncludeGraph,
6933    declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
6934    file: &ProjectFile,
6935    visited: &mut HashSet<ProjectFile>,
6936    out: &mut HashSet<CodeUnit>,
6937    cancellation: Option<&CancellationToken>,
6938) {
6939    let mut stack = vec![file.clone()];
6940    while let Some(file) = stack.pop() {
6941        if cancellation.is_some_and(CancellationToken::is_cancelled) {
6942            break;
6943        }
6944        if !visited.insert(file.clone()) {
6945            continue;
6946        }
6947        if let Some(declarations) = declarations_by_file.get(&file) {
6948            out.extend(declarations.iter().cloned());
6949        }
6950        stack.extend(include_graph.targets(&file).iter().cloned());
6951    }
6952}
6953
6954pub fn signature_arity(signature: Option<&str>) -> usize {
6955    let Some(signature) = signature else {
6956        return 0;
6957    };
6958    let inner = signature
6959        .find('(')
6960        .and_then(|open| {
6961            signature[open + 1..]
6962                .find(')')
6963                .map(|close| &signature[open + 1..open + 1 + close])
6964        })
6965        .unwrap_or(signature)
6966        .trim();
6967    if inner.is_empty() || inner == "void" {
6968        return 0;
6969    }
6970    cpp_split_top_level_commas(inner).count()
6971}
6972
6973fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
6974    let source = format!("void __bifrost_macro_parameters({replacement});");
6975    let mut parser = Parser::new();
6976    parser
6977        .set_language(&tree_sitter_cpp::LANGUAGE.into())
6978        .ok()?;
6979    let tree = parser.parse(&source, None)?;
6980    let root = tree.root_node();
6981    if root.has_error() {
6982        return None;
6983    }
6984    let declaration = root.named_child(0)?;
6985    let declarator = declaration.child_by_field_name("declarator")?;
6986    let parameters = declarator.child_by_field_name("parameters")?;
6987    let mut required = 0;
6988    let mut total = 0;
6989    let mut repeated = false;
6990    let mut cursor = parameters.walk();
6991    for parameter in parameters.children(&mut cursor) {
6992        match parameter.kind() {
6993            "parameter_declaration" => {
6994                if parameter.child_by_field_name("declarator").is_none()
6995                    && parameter
6996                        .child_by_field_name("type")
6997                        .is_some_and(|type_node| node_text(type_node, &source).trim() == "void")
6998                {
6999                    continue;
7000                }
7001                required += 1;
7002                total += 1;
7003            }
7004            "optional_parameter_declaration" => total += 1,
7005            "variadic_parameter" | "variadic_parameter_declaration" | "..." => {
7006                repeated = true;
7007            }
7008            _ => {}
7009        }
7010    }
7011    Some(CallableArity::new(required, total, repeated))
7012}
7013
7014pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> CallableArity {
7015    analyzer
7016        .signature_metadata(unit)
7017        .into_iter()
7018        .find_map(|metadata| metadata.callable_arity())
7019        .unwrap_or_else(|| CallableArity::exact(signature_arity(unit.signature())))
7020}
7021
7022pub fn cpp_callable_parameter_types(
7023    analyzer: &CppGraphSource<'_>,
7024    unit: &CodeUnit,
7025) -> Option<Vec<String>> {
7026    analyzer
7027        .signature_metadata(unit)
7028        .into_iter()
7029        .find_map(|metadata| metadata.callable_parameter_types().map(<[String]>::to_vec))
7030        .or_else(|| unit.signature().and_then(cpp_signature_param_types))
7031}
7032
7033fn merge_compatible_callable_arities(
7034    left: CallableArity,
7035    right: CallableArity,
7036) -> Option<CallableArity> {
7037    let total = left.total();
7038    let left_repeated = left.accepts(total.saturating_add(1));
7039    let right_repeated = right.accepts(right.total().saturating_add(1));
7040    if total != right.total() || left_repeated != right_repeated {
7041        return None;
7042    }
7043    let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
7044    Some(CallableArity::new(required, total, left_repeated))
7045}
7046
7047fn find_include_activation(
7048    cpp: &dyn CppSource,
7049    file: &ProjectFile,
7050    prepared: &PreparedSyntaxTree,
7051    donor_source: &ProjectFile,
7052) -> Option<usize> {
7053    let include_targets = cpp.include_target_index();
7054    let mut direct_includes = Vec::new();
7055    let mut nodes = vec![prepared.tree().root_node()];
7056    // An include activates for the whole file, so only an unconditional
7057    // directive counts here.
7058    let reference = CallableReferenceContext {
7059        file,
7060        position: None,
7061    };
7062    while let Some(node) = nodes.pop() {
7063        if node.kind() == "preproc_include" {
7064            if callable_preprocessor_context_is_visible_for_reference(
7065                node,
7066                prepared.source(),
7067                &reference,
7068            ) {
7069                let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
7070                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
7071                    if let Some(target) = unique_include_target(resolve_include_targets_with_index(
7072                        file,
7073                        &include,
7074                        include_targets,
7075                    )) {
7076                        direct_includes.push((node.end_byte(), target));
7077                    }
7078                }
7079            }
7080            continue;
7081        }
7082        for index in (0..node.named_child_count()).rev() {
7083            if let Some(child) = node.named_child(index) {
7084                nodes.push(child);
7085            }
7086        }
7087    }
7088    direct_includes.sort_by_key(|(activation, _)| *activation);
7089    let mut known_missing = HashSet::default();
7090    direct_includes
7091        .into_iter()
7092        .find(|(_, direct)| {
7093            unconditional_include_reaches(
7094                cpp,
7095                include_targets,
7096                direct,
7097                donor_source,
7098                file,
7099                &mut known_missing,
7100            )
7101        })
7102        .map(|(activation, _)| activation)
7103}
7104
7105fn find_conditional_include_projection_index(
7106    cpp: &dyn CppSource,
7107    file: &ProjectFile,
7108    prepared: &PreparedSyntaxTree,
7109    on_state: &dyn Fn(),
7110) -> ConditionalIncludeProjectionIndex {
7111    let include_targets = cpp.include_target_index();
7112    let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
7113        HashMap::default();
7114    let mut pending = Vec::new();
7115    let mut nodes = vec![prepared.tree().root_node()];
7116    while let Some(node) = nodes.pop() {
7117        if node.kind() == "preproc_include" {
7118            let Some(required_guards) = preprocessor_guard_environment(node, prepared.source())
7119            else {
7120                continue;
7121            };
7122            let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
7123            for include in cpp_include_paths(std::slice::from_ref(&raw)) {
7124                let Some(target) = unique_include_target(resolve_include_targets_with_index(
7125                    file,
7126                    &include,
7127                    include_targets,
7128                )) else {
7129                    continue;
7130                };
7131                pending.push((target, node.end_byte(), required_guards.clone()));
7132            }
7133            continue;
7134        }
7135        for index in (0..node.named_child_count()).rev() {
7136            if let Some(child) = node.named_child(index) {
7137                nodes.push(child);
7138            }
7139        }
7140    }
7141
7142    // One reached file can have several distinct compatible guard paths. A
7143    // state is expanded once for each exact guard set and top-level activation
7144    // byte; this preserves those paths while terminating include cycles.
7145    let mut expanded: HashMap<(ProjectFile, usize), Vec<HashSet<PreprocessorGuard>>> =
7146        HashMap::default();
7147    while let Some((current_file, activation_byte, required_guards)) = pending.pop() {
7148        let guard_sets = expanded
7149            .entry((current_file.clone(), activation_byte))
7150            .or_default();
7151        if guard_sets.contains(&required_guards) {
7152            continue;
7153        }
7154        guard_sets.push(required_guards.clone());
7155        on_state();
7156
7157        let projections = projections_by_source
7158            .entry(current_file.clone())
7159            .or_default();
7160        if !projections.iter().any(|projection| {
7161            projection.activation_byte == activation_byte
7162                && projection.required_guards == required_guards
7163        }) {
7164            projections.push(ConditionalIncludeProjection {
7165                activation_byte,
7166                required_guards: required_guards.clone(),
7167            });
7168        }
7169
7170        let Some(current_prepared) = cpp.prepared_syntax(&current_file) else {
7171            continue;
7172        };
7173        let mut nodes = vec![current_prepared.tree().root_node()];
7174        while let Some(node) = nodes.pop() {
7175            if node.kind() == "preproc_include" {
7176                let Some(include_guards) =
7177                    preprocessor_guard_environment(node, current_prepared.source())
7178                else {
7179                    continue;
7180                };
7181                let Some(path_guards) =
7182                    merge_preprocessor_guards(&required_guards, &include_guards)
7183                else {
7184                    continue;
7185                };
7186                let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
7187                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
7188                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
7189                        &current_file,
7190                        &include,
7191                        include_targets,
7192                    )) else {
7193                        continue;
7194                    };
7195                    pending.push((target, activation_byte, path_guards.clone()));
7196                }
7197                continue;
7198            }
7199            for index in (0..node.named_child_count()).rev() {
7200                if let Some(child) = node.named_child(index) {
7201                    nodes.push(child);
7202                }
7203            }
7204        }
7205    }
7206
7207    projections_by_source
7208        .into_iter()
7209        .map(|(source, mut projections)| {
7210            projections.sort_by_key(|projection| projection.activation_byte);
7211            (source, Arc::from(projections))
7212        })
7213        .collect()
7214}
7215
7216fn unconditional_include_reaches(
7217    cpp: &dyn CppSource,
7218    include_targets: &IncludeTargetIndex,
7219    first: &ProjectFile,
7220    donor_source: &ProjectFile,
7221    reference_file: &ProjectFile,
7222    known_missing: &mut HashSet<ProjectFile>,
7223) -> bool {
7224    if first == donor_source {
7225        return true;
7226    }
7227    if known_missing.contains(first) {
7228        return false;
7229    }
7230    let reference_is_c = reference_file
7231        .rel_path()
7232        .extension()
7233        .and_then(|extension| extension.to_str())
7234        == Some("c");
7235    if let Some(reaches) =
7236        cpp.cached_unconditional_include_reachability(first, donor_source, reference_is_c)
7237    {
7238        return reaches;
7239    }
7240    let mut visited = HashSet::default();
7241    let mut files = vec![first.clone()];
7242    // Only an unconditional directive extends the include reach, so the walk
7243    // asks the question without a reference position.
7244    let reference = CallableReferenceContext {
7245        file: reference_file,
7246        position: None,
7247    };
7248    while let Some(file) = files.pop() {
7249        if file == *donor_source {
7250            cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, true);
7251            return true;
7252        }
7253        if known_missing.contains(&file) || !visited.insert(file.clone()) {
7254            continue;
7255        }
7256        let Some(prepared) = cpp.prepared_syntax(&file) else {
7257            continue;
7258        };
7259        let mut nodes = vec![prepared.tree().root_node()];
7260        while let Some(node) = nodes.pop() {
7261            if node.kind() == "preproc_include" {
7262                if callable_preprocessor_context_is_visible_for_reference(
7263                    node,
7264                    prepared.source(),
7265                    &reference,
7266                ) {
7267                    let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
7268                    for include in cpp_include_paths(std::slice::from_ref(&raw)) {
7269                        if let Some(target) = unique_include_target(
7270                            resolve_include_targets_with_index(&file, &include, include_targets),
7271                        ) {
7272                            files.push(target);
7273                        }
7274                    }
7275                }
7276                continue;
7277            }
7278            for index in (0..node.named_child_count()).rev() {
7279                if let Some(child) = node.named_child(index) {
7280                    nodes.push(child);
7281                }
7282            }
7283        }
7284    }
7285    known_missing.extend(visited);
7286    cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, false);
7287    false
7288}
7289
7290fn declaration_guard_requirements(
7291    analyzer: &CppGraphSource<'_>,
7292    cpp: &dyn CppSource,
7293    candidate: &CodeUnit,
7294) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
7295    let Some(prepared) = cpp.prepared_syntax(candidate.source()) else {
7296        return Vec::new();
7297    };
7298    let root = prepared.tree().root_node();
7299    analyzer
7300        .ranges(candidate)
7301        .into_iter()
7302        .filter_map(|range| {
7303            root.descendant_for_byte_range(range.start_byte, range.end_byte)
7304                .and_then(|node| preprocessor_guard_environment(node, prepared.source()))
7305                // A class name is injected into its own body at the declaration's
7306                // introduction point, not after the complete class range. Using
7307                // the start also preserves normal before/after ordering for aliases.
7308                .map(|required| (range.start_byte, required))
7309        })
7310        .collect()
7311}
7312
7313fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
7314    analyzer
7315        .ranges(candidate)
7316        .into_iter()
7317        .map(|range| range.start_byte)
7318        .min()
7319}
7320
7321fn guard_requirements_hold_at_reference(
7322    required: &HashSet<PreprocessorGuard>,
7323    reference: Option<&HashSet<PreprocessorGuard>>,
7324) -> bool {
7325    reference.is_some_and(|active| {
7326        required
7327            .iter()
7328            .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
7329    })
7330}
7331
7332fn preprocessor_guard_holds_at_reference(
7333    required: &PreprocessorGuard,
7334    active: &HashSet<PreprocessorGuard>,
7335) -> bool {
7336    if active.contains(required) {
7337        return true;
7338    }
7339    let active_expression = BooleanGuardExpression::all(
7340        active
7341            .iter()
7342            .filter_map(PreprocessorGuard::as_boolean_expression),
7343    );
7344    required
7345        .as_boolean_expression()
7346        .is_some_and(|required| active_expression.implies(&required))
7347}
7348
7349/// Cross-file guard rule: two guard sets are compatible when neither one
7350/// contradicts the other. Use this instead of the subset test whenever the
7351/// guards come from a foreign file, which resolves its own conditionals
7352/// independently of the reference.
7353fn guards_compatible_at_reference(
7354    declaration: &HashSet<PreprocessorGuard>,
7355    reference: Option<&HashSet<PreprocessorGuard>>,
7356) -> bool {
7357    reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
7358}
7359
7360/// The byte range of the `#if`/`#elif`/`#else` chain that encloses the smallest
7361/// node covering `[start_byte, end_byte)`, or `None` when nothing there is
7362/// conditional.
7363///
7364/// Two declarations of one name that report the same chain stand in different
7365/// branches of it, so at most one of them is compiled in any configuration.
7366/// They are alternate spellings of a single declaration, not competing
7367/// declarations, and navigation must not present them as an ambiguity.
7368pub fn preprocessor_conditional_family_range(
7369    root: Node<'_>,
7370    start_byte: usize,
7371    end_byte: usize,
7372) -> Option<(usize, usize)> {
7373    let node = root.descendant_for_byte_range(start_byte, end_byte)?;
7374    let mut ancestor = Some(node);
7375    while let Some(current) = ancestor {
7376        if is_preprocessor_conditional(current)
7377            && preprocessor_conditional_contains_descendant(current, node)
7378        {
7379            let family = preprocessor_conditional_family_root(current);
7380            return Some((family.start_byte(), family.end_byte()));
7381        }
7382        ancestor = current.parent();
7383    }
7384    None
7385}
7386
7387fn preprocessor_conditional_family_for_declaration(node: Node<'_>) -> Option<Node<'_>> {
7388    let mut ancestor = node.parent();
7389    while let Some(current) = ancestor {
7390        if is_preprocessor_conditional(current)
7391            && preprocessor_conditional_contains_descendant(current, node)
7392        {
7393            let family = preprocessor_conditional_family_root(current);
7394            if preprocessor_conditional_family_has_terminal_else(family) {
7395                return Some(family);
7396            }
7397        }
7398        ancestor = current.parent();
7399    }
7400    None
7401}
7402
7403fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
7404    while let Some(parent) = conditional.parent() {
7405        let is_alternative = parent
7406            .child_by_field_name("alternative")
7407            .is_some_and(|alternative| {
7408                alternative.start_byte() == conditional.start_byte()
7409                    && alternative.end_byte() == conditional.end_byte()
7410            });
7411        if !is_alternative {
7412            break;
7413        }
7414        conditional = parent;
7415    }
7416    conditional
7417}
7418
7419fn preprocessor_conditional_family_has_terminal_else(mut conditional: Node<'_>) -> bool {
7420    loop {
7421        let Some(alternative) = conditional.child_by_field_name("alternative") else {
7422            return false;
7423        };
7424        match alternative.kind() {
7425            "preproc_else" => return true,
7426            "preproc_elif" => conditional = alternative,
7427            _ => return false,
7428        }
7429    }
7430}
7431
7432pub fn preprocessor_guard_environment(
7433    node: Node<'_>,
7434    source: &str,
7435) -> Option<HashSet<PreprocessorGuard>> {
7436    let mut guards = HashSet::default();
7437    let mut ancestor = node.parent();
7438    while let Some(conditional) = ancestor {
7439        if matches!(
7440            conditional.kind(),
7441            "preproc_if" | "preproc_ifdef" | "preproc_elif"
7442        ) && !is_file_covering_include_guard(conditional, source)
7443            && preprocessor_conditional_contains_descendant(conditional, node)
7444        {
7445            let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
7446            match guard {
7447                PreprocessorGuard::Constant(true) => {
7448                    ancestor = conditional.parent();
7449                    continue;
7450                }
7451                PreprocessorGuard::Constant(false) => return None,
7452                _ => {}
7453            }
7454            if guards.contains(&guard.negated()) {
7455                return None;
7456            }
7457            guards.insert(guard);
7458        }
7459        ancestor = conditional.parent();
7460    }
7461    if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
7462        match guard {
7463            PreprocessorGuard::Constant(true) => {}
7464            PreprocessorGuard::Constant(false) => return None,
7465            _ => {
7466                if guards.contains(&guard.negated()) {
7467                    return None;
7468                }
7469                guards.insert(guard);
7470            }
7471        }
7472    }
7473    Some(guards)
7474}
7475
7476fn fragmented_statement_preprocessor_guard(
7477    descendant: Node<'_>,
7478    source: &str,
7479) -> Option<PreprocessorGuard> {
7480    // A conditional that starts before `} else if (...) {` crosses the
7481    // enclosing statement's grammar boundary. tree-sitter leaves its opener
7482    // as a `preproc_if` with a missing terminator in the consequence and
7483    // reparses the real `#endif` as a `preproc_call` in the alternative. Pair
7484    // those structured nodes before restoring the guard to intervening uses.
7485    let mut ancestor = descendant.parent();
7486    while let Some(statement) = ancestor {
7487        if statement.kind() == "if_statement"
7488            && let (Some(consequence), Some(alternative)) = (
7489                statement.child_by_field_name("consequence"),
7490                statement.child_by_field_name("alternative"),
7491            )
7492            && alternative.start_byte() <= descendant.start_byte()
7493            && descendant.end_byte() <= alternative.end_byte()
7494        {
7495            let mut cursor = consequence.walk();
7496            let openers = consequence
7497                .named_children(&mut cursor)
7498                .filter(|child| {
7499                    matches!(child.kind(), "preproc_if" | "preproc_ifdef")
7500                        && child
7501                            .child(child.child_count().saturating_sub(1))
7502                            .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
7503                })
7504                .collect::<Vec<_>>();
7505            if openers.len() != 1 {
7506                ancestor = statement.parent();
7507                continue;
7508            }
7509
7510            let mut terminators = Vec::new();
7511            let mut stack = vec![alternative];
7512            while let Some(node) = stack.pop() {
7513                if node.kind() == "preproc_call"
7514                    && node.start_byte() >= descendant.end_byte()
7515                    && node
7516                        .child_by_field_name("directive")
7517                        .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
7518                {
7519                    terminators.push(node);
7520                    continue;
7521                }
7522                for index in (0..node.named_child_count()).rev() {
7523                    if let Some(child) = node.named_child(index) {
7524                        stack.push(child);
7525                    }
7526                }
7527            }
7528            if terminators.len() == 1 {
7529                return simple_preprocessor_guard(openers[0], source);
7530            }
7531        }
7532        ancestor = statement.parent();
7533    }
7534    None
7535}
7536
7537fn preprocessor_guard_for_descendant(
7538    conditional: Node<'_>,
7539    descendant: Node<'_>,
7540    source: &str,
7541) -> Option<PreprocessorGuard> {
7542    let mut guard = simple_preprocessor_guard(conditional, source)?;
7543    if conditional
7544        .child_by_field_name("alternative")
7545        .is_some_and(|alternative| {
7546            alternative.start_byte() <= descendant.start_byte()
7547                && descendant.end_byte() <= alternative.end_byte()
7548        })
7549    {
7550        let alternative = conditional.child_by_field_name("alternative")?;
7551        // Tree-sitter nests an `#elif` chain in each `alternative` field. A
7552        // descendant in any later branch must first exclude the parent branch,
7553        // then collect the nested `preproc_elif` guard from its own ancestor.
7554        if !matches!(alternative.kind(), "preproc_else" | "preproc_elif") {
7555            return None;
7556        }
7557        guard = guard.negated();
7558    }
7559    Some(guard)
7560}
7561
7562fn preprocessor_conditional_contains_descendant(
7563    conditional: Node<'_>,
7564    descendant: Node<'_>,
7565) -> bool {
7566    cpp_displaced_preprocessor_boundary(conditional)
7567        .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
7568}
7569
7570pub fn merge_preprocessor_guards(
7571    left: &HashSet<PreprocessorGuard>,
7572    right: &HashSet<PreprocessorGuard>,
7573) -> Option<HashSet<PreprocessorGuard>> {
7574    let mut merged = left.clone();
7575    for guard in right {
7576        if merged.contains(&guard.negated()) {
7577            return None;
7578        }
7579        merged.insert(guard.clone());
7580    }
7581    Some(merged)
7582}
7583
7584fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
7585    if conditional.kind() == "preproc_ifdef" {
7586        let name = conditional.child_by_field_name("name")?;
7587        let name = node_text(name, source).to_string();
7588        return match conditional.child(0)?.kind() {
7589            "#ifdef" => Some(PreprocessorGuard::Defined(name)),
7590            "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
7591            _ => None,
7592        };
7593    }
7594    let condition = conditional.child_by_field_name("condition")?;
7595    simple_preprocessor_expression_guard(condition, source).or_else(|| {
7596        Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
7597            node_text(condition, source),
7598        )))
7599    })
7600}
7601
7602fn simple_preprocessor_expression_guard(
7603    expression: Node<'_>,
7604    source: &str,
7605) -> Option<PreprocessorGuard> {
7606    match expression.kind() {
7607        "number_literal" => match node_text(expression, source).trim() {
7608            "0" => Some(PreprocessorGuard::Constant(false)),
7609            "1" => Some(PreprocessorGuard::Constant(true)),
7610            _ => None,
7611        },
7612        "preproc_defined" => {
7613            let identifier = (0..expression.named_child_count())
7614                .filter_map(|index| expression.named_child(index))
7615                .find(|child| child.kind() == "identifier")?;
7616            Some(PreprocessorGuard::Defined(
7617                node_text(identifier, source).to_string(),
7618            ))
7619        }
7620        "unary_expression"
7621            if expression
7622                .child_by_field_name("operator")
7623                .is_some_and(|operator| operator.kind() == "!") =>
7624        {
7625            simple_preprocessor_expression_guard(
7626                expression.child_by_field_name("argument")?,
7627                source,
7628            )
7629            .map(|guard| guard.negated())
7630        }
7631        "parenthesized_expression" => (0..expression.named_child_count())
7632            .filter_map(|index| expression.named_child(index))
7633            .next()
7634            .and_then(|child| simple_preprocessor_expression_guard(child, source)),
7635        "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
7636            expression, source,
7637        ))),
7638        _ => None,
7639    }
7640}
7641
7642fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
7643    match expression.kind() {
7644        "number_literal" => match node_text(expression, source).trim() {
7645            "0" => BooleanGuardExpression::Constant(false),
7646            "1" => BooleanGuardExpression::Constant(true),
7647            _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
7648                expression, source,
7649            ))),
7650        },
7651        "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
7652        "preproc_defined" => {
7653            let identifier = (0..expression.named_child_count())
7654                .filter_map(|index| expression.named_child(index))
7655                .find(|child| child.kind() == "identifier");
7656            identifier.map_or_else(
7657                || {
7658                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
7659                        expression, source,
7660                    )))
7661                },
7662                |identifier| {
7663                    BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
7664                },
7665            )
7666        }
7667        "unary_expression"
7668            if expression
7669                .child_by_field_name("operator")
7670                .is_some_and(|operator| operator.kind() == "!") =>
7671        {
7672            expression.child_by_field_name("argument").map_or_else(
7673                || {
7674                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
7675                        expression, source,
7676                    )))
7677                },
7678                |argument| boolean_preprocessor_expression(argument, source).negated(),
7679            )
7680        }
7681        "parenthesized_expression" => (0..expression.named_child_count())
7682            .filter_map(|index| expression.named_child(index))
7683            .next()
7684            .map_or_else(
7685                || {
7686                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
7687                        expression, source,
7688                    )))
7689                },
7690                |child| boolean_preprocessor_expression(child, source),
7691            ),
7692        "binary_expression" => {
7693            let operands = || {
7694                Some((
7695                    boolean_preprocessor_expression(
7696                        expression.child_by_field_name("left")?,
7697                        source,
7698                    ),
7699                    boolean_preprocessor_expression(
7700                        expression.child_by_field_name("right")?,
7701                        source,
7702                    ),
7703                ))
7704            };
7705            match expression
7706                .child_by_field_name("operator")
7707                .map(|operator| operator.kind())
7708            {
7709                Some("&&") => operands().map_or_else(
7710                    || {
7711                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
7712                            expression, source,
7713                        )))
7714                    },
7715                    |(left, right)| BooleanGuardExpression::all([left, right]),
7716                ),
7717                Some("||") => operands().map_or_else(
7718                    || {
7719                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
7720                            expression, source,
7721                        )))
7722                    },
7723                    |(left, right)| BooleanGuardExpression::any([left, right]),
7724                ),
7725                _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
7726                    expression, source,
7727                ))),
7728            }
7729        }
7730        _ => {
7731            BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
7732        }
7733    }
7734}
7735
7736fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
7737    if targets.len() == 1 {
7738        targets.pop()
7739    } else {
7740        None
7741    }
7742}
7743
7744/// The declaration nodes of `candidate` in `prepared` that stand at a scope a
7745/// later reference can name.
7746///
7747/// A declaration inside a real function body, lambda, or nested block is block
7748/// local and is dropped. A declaration inside a parser-recovery wrapper that
7749/// merely looks callable -- an export macro between `class` and its name, or a
7750/// namespace-opening macro token before `namespace x {` -- keeps class or
7751/// namespace scope and is kept.
7752fn nameable_callable_declaration_nodes<'tree>(
7753    analyzer: &CppGraphSource<'_>,
7754    prepared: &'tree PreparedSyntaxTree,
7755    candidate: &CodeUnit,
7756) -> Vec<Node<'tree>> {
7757    let root = prepared.tree().root_node();
7758    analyzer
7759        .ranges(candidate)
7760        .into_iter()
7761        .filter_map(|range| {
7762            let mut declaration =
7763                root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
7764            while !matches!(
7765                declaration.kind(),
7766                "declaration" | "field_declaration" | "function_definition"
7767            ) {
7768                declaration = declaration.parent()?;
7769            }
7770            let mut ancestor = declaration.parent();
7771            while let Some(node) = ancestor {
7772                if node.kind() == "function_definition"
7773                    && is_recovered_declaration_scope_container(node, prepared.source())
7774                {
7775                    ancestor = node.parent();
7776                    continue;
7777                }
7778                if node.kind() == "compound_statement"
7779                    && node.parent().is_some_and(|parent| {
7780                        is_recovered_declaration_scope_container(parent, prepared.source())
7781                    })
7782                {
7783                    ancestor = node.parent().and_then(|parent| parent.parent());
7784                    continue;
7785                }
7786                if matches!(
7787                    node.kind(),
7788                    "compound_statement" | "function_definition" | "lambda_expression"
7789                ) {
7790                    return None;
7791                }
7792                ancestor = node.parent();
7793            }
7794            Some(declaration)
7795        })
7796        .collect()
7797}
7798
7799fn callable_declaration_activation_in_file(
7800    analyzer: &CppGraphSource<'_>,
7801    prepared: &PreparedSyntaxTree,
7802    candidate: &CodeUnit,
7803    reference: &CallableReferenceContext<'_>,
7804) -> Option<usize> {
7805    nameable_callable_declaration_nodes(analyzer, prepared, candidate)
7806        .into_iter()
7807        .filter(|declaration| {
7808            callable_preprocessor_context_is_visible_for_reference(
7809                *declaration,
7810                prepared.source(),
7811                reference,
7812            )
7813        })
7814        .map(callable_declaration_activation_byte)
7815        .min()
7816}
7817
7818/// C and C++ activate a declared name at the end of its declarator, not at the
7819/// end of the whole declaration. A function definition ends at the closing
7820/// brace of its body, so the declaration end byte would hide the function from
7821/// its own body and make self recursion unresolvable without a prototype.
7822fn callable_declaration_activation_byte(declaration: Node<'_>) -> usize {
7823    if declaration.kind() != "function_definition" {
7824        return declaration.end_byte();
7825    }
7826    declaration
7827        .child_by_field_name("declarator")
7828        .map_or(declaration.end_byte(), |declarator| declarator.end_byte())
7829}
7830
7831/// The reference side of a callable visibility question.
7832///
7833/// An include-graph walk and a whole-file arity activation ask the question
7834/// without one reference position, so they carry no `position` and therefore no
7835/// guard environment.
7836struct CallableReferenceContext<'a> {
7837    file: &'a ProjectFile,
7838    position: Option<CallableReferencePosition<'a>>,
7839}
7840
7841/// One reference position plus its preprocessor guard environment. The
7842/// environment is computed on demand because most declarations carry no
7843/// non-trivial guard.
7844struct CallableReferencePosition<'a> {
7845    prepared: &'a PreparedSyntaxTree,
7846    byte: usize,
7847    guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
7848}
7849
7850impl CallableReferenceContext<'_> {
7851    fn is_c(&self) -> bool {
7852        self.file
7853            .rel_path()
7854            .extension()
7855            .and_then(|extension| extension.to_str())
7856            == Some("c")
7857    }
7858
7859    fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
7860        let position = self.position.as_ref()?;
7861        position
7862            .guards
7863            .get_or_init(|| {
7864                position
7865                    .prepared
7866                    .tree()
7867                    .root_node()
7868                    .descendant_for_byte_range(position.byte, position.byte)
7869                    .and_then(|node| {
7870                        preprocessor_guard_environment(node, position.prepared.source())
7871                    })
7872            })
7873            .as_ref()
7874    }
7875}
7876
7877fn callable_preprocessor_context_is_visible_for_reference(
7878    node: Node<'_>,
7879    source: &str,
7880    reference: &CallableReferenceContext<'_>,
7881) -> bool {
7882    let reference_is_c = reference.is_c();
7883    let mut ancestor = node.parent();
7884    while let Some(conditional) = ancestor {
7885        if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
7886            && !is_file_covering_include_guard(conditional, source)
7887            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
7888            && preprocessor_conditional_contains_descendant(conditional, node)
7889        {
7890            let Some(guard) = preprocessor_guard_for_descendant(conditional, node, source) else {
7891                return false;
7892            };
7893            match guard {
7894                PreprocessorGuard::Constant(true) => {}
7895                PreprocessorGuard::Constant(false) => return false,
7896                PreprocessorGuard::Defined(name) if name == "__cplusplus" => {
7897                    if reference_is_c {
7898                        return false;
7899                    }
7900                }
7901                PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
7902                    if !reference_is_c {
7903                        return false;
7904                    }
7905                }
7906                // The declaration stands under a guard whose value this
7907                // analyzer cannot decide. It is still co-active with a
7908                // reference whose active guards imply it. Collecting one guard
7909                // per ancestor makes the whole walk a conjunction of the
7910                // declaration requirements.
7911                guard => {
7912                    if !reference
7913                        .guards()
7914                        .is_some_and(|active| preprocessor_guard_holds_at_reference(&guard, active))
7915                    {
7916                        return false;
7917                    }
7918                }
7919            }
7920        }
7921        ancestor = conditional.parent();
7922    }
7923    true
7924}
7925
7926fn flattened_macro_namespace_declaration_matches(
7927    analyzer: &CppGraphSource<'_>,
7928    cpp: &dyn CppSource,
7929    reference_file: &ProjectFile,
7930    visible_declaration: &CodeUnit,
7931    qualified_candidate: &CodeUnit,
7932    reference_byte: usize,
7933) -> bool {
7934    // Namespace-opening macros can leave tree-sitter unable to retain the
7935    // namespace owner after a later recovery point. In that shape the forward
7936    // declaration is indexed at translation-unit scope, while the definition
7937    // still has its qualified owner. Require all surviving structural evidence
7938    // before treating the declaration as activation for that definition.
7939    if visible_declaration.kind() != qualified_candidate.kind()
7940        || visible_declaration.identifier() != qualified_candidate.identifier()
7941        || visible_declaration.signature() != qualified_candidate.signature()
7942        || !visible_declaration.package_name().is_empty()
7943        || qualified_candidate.package_name().is_empty()
7944    {
7945        return false;
7946    }
7947
7948    let Some(prepared) = cpp.prepared_syntax(visible_declaration.source()) else {
7949        return false;
7950    };
7951    let root = prepared.tree().root_node();
7952    let closing_brace_limit = if visible_declaration.source() == reference_file {
7953        reference_byte
7954    } else {
7955        usize::MAX
7956    };
7957
7958    analyzer
7959        .ranges(visible_declaration)
7960        .into_iter()
7961        .any(|range| {
7962            let Some(mut declaration) =
7963                root.descendant_for_byte_range(range.start_byte, range.end_byte)
7964            else {
7965                return false;
7966            };
7967            while !matches!(
7968                declaration.kind(),
7969                "declaration" | "field_declaration" | "function_definition"
7970            ) {
7971                let Some(parent) = declaration.parent() else {
7972                    return false;
7973                };
7974                declaration = parent;
7975            }
7976            if declaration
7977                .parent()
7978                .is_none_or(|parent| parent.kind() != "translation_unit")
7979                || !macro_displaced_cpp_return_type(declaration, prepared.source())
7980            {
7981                return false;
7982            }
7983
7984            let mut cursor = root.walk();
7985            root.named_children(&mut cursor).any(|sibling| {
7986                sibling.start_byte() >= declaration.end_byte()
7987                    && sibling.start_byte() < closing_brace_limit
7988                    && direct_unmatched_closing_brace(sibling)
7989            })
7990        })
7991}
7992
7993fn flattened_macro_namespace_components(
7994    declaration: Node<'_>,
7995    source: &str,
7996) -> Option<Vec<String>> {
7997    flattened_macro_function_namespace_components(declaration, source)
7998        .or_else(|| flattened_macro_error_namespace_components(declaration, source))
7999}
8000
8001fn flattened_macro_function_namespace_components(
8002    declaration: Node<'_>,
8003    source: &str,
8004) -> Option<Vec<String>> {
8005    let body = declaration
8006        .parent()
8007        .filter(|parent| parent.kind() == "compound_statement")?;
8008    let function = body.parent()?;
8009    if function.child_by_field_name("body") != Some(body) {
8010        return None;
8011    }
8012    let namespace_name = recovered_macro_namespace_name(function, source)?;
8013    let mut components = enclosing_namespace_components(declaration, source)?;
8014    components.push(namespace_name);
8015    Some(components)
8016}
8017
8018/// The namespace name a namespace-opening macro token displaced into a
8019/// synthetic `function_definition`, or `None` when `function` is not that
8020/// recovery shape.
8021///
8022/// `ABSL_NAMESPACE_BEGIN` (or `FMT_BEGIN_NAMESPACE`, ...) immediately before
8023/// `namespace x {` leaves tree-sitter with a `function_definition` whose type is
8024/// the macro token, whose declarator is the namespace name behind an `ERROR`
8025/// holding the `namespace` keyword, and whose body spans the whole namespace
8026/// region. The matching `*_NAMESPACE_END` sibling is what separates the recovery
8027/// artifact from a real function definition.
8028fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
8029    if function.kind() != "function_definition" || !function.has_error() {
8030        return None;
8031    }
8032    let body = function
8033        .child_by_field_name("body")
8034        .filter(|body| body.kind() == "compound_statement")?;
8035    let mut cursor = function.walk();
8036    let prefix = function
8037        .named_children(&mut cursor)
8038        .take_while(|child| child.start_byte() < body.start_byte())
8039        .filter(|child| child.kind() != "comment")
8040        .collect::<Vec<_>>();
8041    let begin_index = prefix.iter().rposition(|child| {
8042        flattened_macro_sentinel_name(*child, source)
8043            .is_some_and(|name| is_namespace_begin_sentinel(&name))
8044    })?;
8045    let mut identifiers = Vec::new();
8046    let mut stack = prefix[begin_index + 1..]
8047        .iter()
8048        .rev()
8049        .copied()
8050        .collect::<Vec<_>>();
8051    while let Some(current) = stack.pop() {
8052        if let Some(identifier) = direct_cpp_identifier_name(current, source) {
8053            identifiers.push(identifier);
8054            continue;
8055        }
8056        let mut cursor = current.walk();
8057        let children = current.named_children(&mut cursor).collect::<Vec<_>>();
8058        stack.extend(children.into_iter().rev());
8059    }
8060    let [keyword, namespace_name] = identifiers.as_slice() else {
8061        return None;
8062    };
8063    if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
8064    {
8065        return None;
8066    }
8067    let mut next = function.next_named_sibling();
8068    let next = loop {
8069        let candidate = next?;
8070        next = candidate.next_named_sibling();
8071        if candidate.kind() != "comment" {
8072            break candidate;
8073        }
8074    };
8075    flattened_macro_sentinel_name(next, source)
8076        .is_some_and(|name| is_namespace_end_sentinel(&name))
8077        .then(|| namespace_name.clone())
8078}
8079
8080/// A `function_definition` that exists only because tree-sitter recovered a
8081/// macro-decorated class head or a namespace-opening macro token. A declaration
8082/// in such a body keeps class or namespace scope, so a scope walk must step over
8083/// the wrapper instead of treating the declaration as block local.
8084fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
8085    crate::declarations::is_recovered_exported_class_container(node, source)
8086        || recovered_macro_namespace_name(node, source).is_some()
8087}
8088
8089fn flattened_macro_error_namespace_components(
8090    declaration: Node<'_>,
8091    source: &str,
8092) -> Option<Vec<String>> {
8093    let parent = declaration
8094        .parent()
8095        .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
8096    let mut cursor = parent.walk();
8097    let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
8098    let declaration_index = siblings
8099        .iter()
8100        .position(|candidate| same_node(*candidate, declaration))?;
8101    let begin_index = (0..declaration_index).rev().find(|index| {
8102        flattened_macro_sentinel_name(siblings[*index], source)
8103            .is_some_and(|name| is_namespace_begin_sentinel(&name))
8104    })?;
8105
8106    let significant = siblings[begin_index + 1..declaration_index]
8107        .iter()
8108        .copied()
8109        .filter(|node| node.kind() != "comment")
8110        .collect::<Vec<_>>();
8111    let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
8112        return None;
8113    };
8114    if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
8115        return None;
8116    }
8117    let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
8118    if significant[2..].iter().any(|node| {
8119        flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
8120            is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
8121        })
8122    }) {
8123        return None;
8124    }
8125
8126    let mut saw_namespace_close = false;
8127    for sibling in siblings.iter().skip(declaration_index + 1).copied() {
8128        if sibling.kind() == "comment" {
8129            continue;
8130        }
8131        if !saw_namespace_close {
8132            if direct_unmatched_closing_brace(sibling) {
8133                saw_namespace_close = true;
8134                continue;
8135            }
8136            if flattened_macro_sentinel_name(sibling, source).is_some() {
8137                return None;
8138            }
8139            continue;
8140        }
8141        if !flattened_macro_sentinel_name(sibling, source)
8142            .is_some_and(|name| is_namespace_end_sentinel(&name))
8143        {
8144            return None;
8145        }
8146        let mut components = enclosing_namespace_components(declaration, source)?;
8147        components.push(namespace_name);
8148        return Some(components);
8149    }
8150    None
8151}
8152
8153fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
8154    // At translation-unit scope the trailing `X_NAMESPACE_END` token parses as
8155    // an `expression_statement` with a missing semicolon; inside a namespace
8156    // body the same token stays a bare `type_identifier`.
8157    let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
8158        node.named_child(0)?
8159    } else {
8160        node
8161    };
8162    let candidate = direct_cpp_identifier_name(node, source).or_else(|| {
8163        node.child_by_field_name("type")
8164            .and_then(|type_node| direct_cpp_identifier_name(type_node, source))
8165    })?;
8166    (cpp_export_macro_token(&candidate)
8167        && (is_namespace_begin_sentinel(&candidate) || is_namespace_end_sentinel(&candidate)))
8168    .then_some(candidate)
8169}
8170
8171/// Namespace-opening macros are spelled both ways in the wild:
8172/// `ABSL_NAMESPACE_BEGIN` (abseil, nlohmann) and `FMT_BEGIN_NAMESPACE` (fmt).
8173fn is_namespace_begin_sentinel(name: &str) -> bool {
8174    name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE")
8175}
8176
8177fn is_namespace_end_sentinel(name: &str) -> bool {
8178    name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE")
8179}
8180
8181fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
8182    if node.kind() != "ERROR" || node.named_child_count() != 1 {
8183        return None;
8184    }
8185    let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
8186    (!cpp_export_macro_token(&name)).then_some(name)
8187}
8188
8189fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
8190    if !matches!(
8191        node.kind(),
8192        "identifier" | "namespace_identifier" | "type_identifier"
8193    ) {
8194        return None;
8195    }
8196    let name = normalize_cpp_whitespace(node_text(node, source));
8197    (!name.is_empty()).then_some(name)
8198}
8199
8200fn guard_requirement_sets_match(
8201    left: &[(usize, HashSet<PreprocessorGuard>)],
8202    right: &[(usize, HashSet<PreprocessorGuard>)],
8203) -> bool {
8204    left.len() == right.len()
8205        && left.iter().all(|(_, left_guards)| {
8206            right
8207                .iter()
8208                .any(|(_, right_guards)| left_guards == right_guards)
8209        })
8210        && right.iter().all(|(_, right_guards)| {
8211            left.iter()
8212                .any(|(_, left_guards)| right_guards == left_guards)
8213        })
8214}
8215
8216fn macro_displaced_cpp_return_type(declaration: Node<'_>, source: &str) -> bool {
8217    let Some(type_node) = declaration.child_by_field_name("type") else {
8218        return false;
8219    };
8220    let type_name = normalize_cpp_whitespace(node_text(type_node, source));
8221    !type_name.is_empty()
8222        && type_name
8223            .chars()
8224            .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
8225        && (0..declaration.named_child_count()).any(|index| {
8226            declaration
8227                .named_child(index)
8228                .is_some_and(|child| child.kind() == "ERROR")
8229        })
8230}
8231
8232fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
8233    node.kind() == "ERROR"
8234        && (0..node.child_count())
8235            .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
8236}
8237
8238pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
8239    let mut ancestor = node.parent();
8240    while let Some(parent) = ancestor {
8241        if is_preprocessor_conditional(parent)
8242            && !is_file_covering_include_guard(parent, source)
8243            && !is_split_cpp_language_linkage_wrapper(parent, node, source)
8244        {
8245            return false;
8246        }
8247        ancestor = parent.parent();
8248    }
8249    true
8250}
8251
8252fn is_split_cpp_language_linkage_wrapper(
8253    conditional: Node<'_>,
8254    descendant: Node<'_>,
8255    source: &str,
8256) -> bool {
8257    if conditional.child_by_field_name("alternative").is_some()
8258        || !matches!(
8259            simple_preprocessor_guard(conditional, source),
8260            Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
8261        )
8262    {
8263        return false;
8264    }
8265    let mut current = descendant.parent();
8266    let linkage = loop {
8267        let Some(node) = current else {
8268            return false;
8269        };
8270        if node == conditional {
8271            return false;
8272        }
8273        if node.kind() == "linkage_specification" {
8274            break node;
8275        }
8276        current = node.parent();
8277    };
8278    if linkage
8279        .child_by_field_name("value")
8280        .is_none_or(|value| node_text(value, source) != "\"C\"")
8281    {
8282        return false;
8283    }
8284    let Some(body) = linkage.child_by_field_name("body") else {
8285        return false;
8286    };
8287    let closes_opening_branch = (0..body.named_child_count())
8288        .filter_map(|index| body.named_child(index))
8289        .take_while(|child| child.end_byte() <= descendant.start_byte())
8290        .any(|child| {
8291            child.kind() == "preproc_call"
8292                && child
8293                    .child_by_field_name("directive")
8294                    .is_some_and(|directive| node_text(directive, source) == "#endif")
8295        });
8296    let reopens_for_closing_brace = (0..body.named_child_count())
8297        .filter_map(|index| body.named_child(index))
8298        .skip_while(|child| child.start_byte() < descendant.end_byte())
8299        .any(|child| {
8300            matches!(
8301                simple_preprocessor_guard(child, source),
8302                Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
8303            ) && (0..child.child_count()).any(|index| {
8304                child
8305                    .child(index)
8306                    .is_some_and(|token| token.kind() == "#endif" && token.is_missing())
8307            })
8308        });
8309    closes_opening_branch && reopens_for_closing_brace
8310}
8311
8312pub fn call_arity(node: Node<'_>) -> usize {
8313    node.child_by_field_name("arguments")
8314        .or_else(|| node.child_by_field_name("parameters"))
8315        .or_else(|| node.child_by_field_name("value"))
8316        .or_else(|| first_named_child_of_kind(node, "argument_list"))
8317        .or_else(|| first_named_child_of_kind(node, "initializer_list"))
8318        .map(|args| argument_children(args).count())
8319        .unwrap_or(0)
8320}
8321
8322pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
8323    let recovered_block_arguments = recovered_block_literal_arguments(node);
8324    (0..node.child_count())
8325        .filter_map(move |index| node.child(index))
8326        .filter(|child| child.is_named() && !child.is_extra())
8327        .flat_map(move |child| {
8328            if let Some((raw, left, right)) = recovered_block_arguments
8329                && child == raw
8330            {
8331                [Some(left), Some(right)]
8332            } else {
8333                [Some(child), None]
8334            }
8335        })
8336        .flatten()
8337}
8338
8339fn recovered_c_keyword_argument_count(
8340    file: &ProjectFile,
8341    call: Node<'_>,
8342    arguments: Node<'_>,
8343    source: &str,
8344) -> usize {
8345    // A C identifier that is a C++ keyword can be displaced twice by the C++
8346    // grammar: first into a direct parameter-list `ERROR(keyword)`, then into
8347    // a direct argument-list `ERROR(',', keyword)`. Match those CST tokens in
8348    // the enclosing C function before restoring the otherwise dropped slot.
8349    if !is_c_source_file(file) || arguments.kind() != "argument_list" {
8350        return 0;
8351    }
8352    let mut ancestor = Some(call);
8353    let function = loop {
8354        let Some(current) = ancestor else {
8355            return 0;
8356        };
8357        if current.kind() == "function_definition" {
8358            break current;
8359        }
8360        ancestor = current.parent();
8361    };
8362    let Some(parameters) = function
8363        .child_by_field_name("declarator")
8364        .and_then(|declarator| declarator.child_by_field_name("parameters"))
8365    else {
8366        return 0;
8367    };
8368    let displaced_parameter_keywords = (0..parameters.child_count())
8369        .filter_map(|index| parameters.child(index))
8370        .filter(|error| error.kind() == "ERROR")
8371        .filter_map(|error| {
8372            let parameter = error.prev_named_sibling()?;
8373            if parameter.kind() != "parameter_declaration"
8374                || parameter.end_byte() != error.start_byte()
8375                || extract_variable_name(parameter, source).is_some()
8376            {
8377                return None;
8378            }
8379            let mut children = (0..error.child_count())
8380                .filter_map(|index| error.child(index))
8381                .filter(|child| !child.is_extra() && !child.is_missing());
8382            let keyword = children.next()?;
8383            (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
8384                .then_some(keyword)
8385        })
8386        .collect::<Vec<_>>();
8387    if displaced_parameter_keywords.is_empty() {
8388        return 0;
8389    }
8390
8391    (0..arguments.child_count())
8392        .filter_map(|index| arguments.child(index))
8393        .filter(|error| error.kind() == "ERROR" && error.is_extra())
8394        .filter(|error| {
8395            let mut children = (0..error.child_count())
8396                .filter_map(|index| error.child(index))
8397                .filter(|child| !child.is_extra() && !child.is_missing());
8398            let Some(comma) = children.next() else {
8399                return false;
8400            };
8401            let Some(keyword) = children.next() else {
8402                return false;
8403            };
8404            children.next().is_none()
8405                && comma.kind() == ","
8406                && !keyword.is_named()
8407                && keyword.child_count() == 0
8408                && displaced_parameter_keywords
8409                    .iter()
8410                    .any(|parameter| parameter.kind_id() == keyword.kind_id())
8411        })
8412        .count()
8413}
8414
8415fn recovered_block_literal_arguments<'tree>(
8416    arguments: Node<'tree>,
8417) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
8418    if arguments.kind() != "argument_list" {
8419        return None;
8420    }
8421    let mut raw_arguments = (0..arguments.child_count())
8422        .filter_map(|index| arguments.child(index))
8423        .filter(|child| child.is_named() && !child.is_extra());
8424    let raw = raw_arguments.next()?;
8425    if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
8426        return None;
8427    }
8428
8429    let left = raw.child_by_field_name("left")?;
8430    if left.is_missing() || left.start_byte() == left.end_byte() {
8431        return None;
8432    }
8433    let right = raw.child_by_field_name("right")?;
8434    if right.kind() != "compound_literal_expression"
8435        || right.is_missing()
8436        || right
8437            .child_by_field_name("type")
8438            .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
8439        || right
8440            .child_by_field_name("value")
8441            .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
8442    {
8443        return None;
8444    }
8445    let has_intervening_error = (0..raw.child_count())
8446        .filter_map(|index| raw.child(index))
8447        .any(|child| {
8448            child.kind() == "ERROR"
8449                && !child.is_missing()
8450                && child.start_byte() >= left.end_byte()
8451                && child.end_byte() <= right.start_byte()
8452        });
8453    has_intervening_error.then_some((raw, left, right))
8454}
8455
8456pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
8457    match node.kind() {
8458        "new_expression" => node
8459            .child_by_field_name("type")
8460            .or_else(|| node.named_child(0)),
8461        "compound_literal_expression" => node.child_by_field_name("type"),
8462        "call_expression" => node.child_by_field_name("function"),
8463        _ => None,
8464    }
8465}
8466
8467pub fn field_initializer_constructs_target(
8468    node: Node<'_>,
8469    ctx: &ScanCtx<'_>,
8470    owner: &CodeUnit,
8471) -> bool {
8472    // A qualified name in a constructor initializer denotes a base
8473    // subobject constructor (`namespace::Base(args)`), not a member field.  The
8474    // field-initializer grammar exposes the qualified name as one structured
8475    // `qualified_identifier`; resolve its owner through the same lexical type
8476    // machinery used for ordinary C++ type references before considering the
8477    // initializer a hit.  This keeps an unrelated `namespace::Other(...)`, a
8478    // qualified non-constructor member, and an unresolved owner out of the
8479    // target constructor's inverse usage set.
8480    if first_named_child_of_kind(node, "qualified_identifier").is_some() {
8481        return qualified_base_initializer_constructs_target(node, ctx, owner);
8482    }
8483    let Some(name) = node
8484        .child_by_field_name("name")
8485        .or_else(|| first_named_child_of_kind(node, "field_identifier"))
8486        .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
8487    else {
8488        return false;
8489    };
8490    let field_name = node_text(name, ctx.source);
8491    ctx.visibility
8492        .visible_identifier_candidates(ctx.file, field_name)
8493        .filter(|unit| unit.is_field() && unit.identifier() == field_name)
8494        .any(|unit| field_declares_type(unit, ctx, owner))
8495}
8496
8497fn qualified_base_initializer_constructs_target(
8498    node: Node<'_>,
8499    ctx: &ScanCtx<'_>,
8500    owner: &CodeUnit,
8501) -> bool {
8502    let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
8503        return false;
8504    };
8505    let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
8506        return false;
8507    };
8508    let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
8509        return false;
8510    };
8511    let resolves_target = |components: &[String]| {
8512        matches!(
8513            ctx.visibility.resolve_type_components_lexically_for_target(
8514                &ctx.analyzer,
8515                ctx.file,
8516                components,
8517                is_globally_qualified_cpp_name(qualified),
8518                &lexical_scope,
8519                owner,
8520            ),
8521            LexicalTypeResolution::Resolved { unit, .. }
8522                if same_visible_symbol(&unit, owner)
8523        )
8524    };
8525    if resolves_target(&components) {
8526        return true;
8527    }
8528
8529    // Some real-world code spells a base mem-initializer as
8530    // `Base::Base(args)`. In that structured path the final component repeats
8531    // the constructor name; resolve the preceding type path. The terminal
8532    // identity check prevents an arbitrary qualified member from taking this
8533    // route.
8534    components
8535        .last()
8536        .is_some_and(|terminal| terminal == owner.identifier())
8537        && resolves_target(&components[..components.len() - 1])
8538}
8539
8540fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
8541    unit.signature()
8542        .is_some_and(|declaration| field_declaration_type_matches(declaration, unit, ctx, owner))
8543        || ctx
8544            .analyzer
8545            .get_source(unit, false)
8546            .is_some_and(|declaration| {
8547                field_declaration_type_matches(&declaration, unit, ctx, owner)
8548            })
8549}
8550
8551pub fn field_declared_binding(
8552    analyzer: &CppGraphSource<'_>,
8553    visibility: &VisibilityIndex<'_>,
8554    visible_from: &ProjectFile,
8555    field: &CodeUnit,
8556) -> Option<CppScanBinding> {
8557    let fact = visibility.field_declared_type_fact(analyzer, field)?;
8558    let normalized = normalize_field_type_text(&fact.type_text);
8559    let resolved = visibility.resolve_unique_canonical_type_for_declaration(
8560        analyzer,
8561        visible_from,
8562        field,
8563        &normalized,
8564    );
8565    let resolved = match (resolved, fact.template_arguments.as_deref()) {
8566        (Some(primary), Some(arguments)) => visibility
8567            .resolve_template_arguments(visible_from, primary, arguments)
8568            .ok(),
8569        (resolved, None) => resolved,
8570        (None, Some(_)) => None,
8571    };
8572    Some(CppScanBinding::from_type_name(
8573        normalized,
8574        resolved,
8575        fact.indirection,
8576    ))
8577}
8578
8579/// The one logical type the candidates name, or why they do not name one.
8580fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
8581    let Some(first) = candidates.first() else {
8582        return Err(TypeCandidateFailure::Unresolvable);
8583    };
8584    if candidates
8585        .iter()
8586        .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
8587    {
8588        Ok((*first).clone())
8589    } else {
8590        Err(TypeCandidateFailure::Ambiguous)
8591    }
8592}
8593
8594fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
8595    logical_type_candidate(candidates).ok()
8596}
8597
8598fn unique_type_candidate_preserving_alias(
8599    analyzer: &CppGraphSource<'_>,
8600    candidates: &[&CodeUnit],
8601) -> Option<CodeUnit> {
8602    let first = *candidates.first()?;
8603    if declared_type_alias(analyzer, first) {
8604        return candidates
8605            .iter()
8606            .all(|candidate| {
8607                declared_type_alias(analyzer, candidate)
8608                    && candidate.kind() == first.kind()
8609                    && candidate.fq_name() == first.fq_name()
8610                    && candidate.source() == first.source()
8611            })
8612            .then(|| first.clone());
8613    }
8614    candidates
8615        .iter()
8616        .all(|candidate| {
8617            !declared_type_alias(analyzer, candidate)
8618                && candidate.kind() == first.kind()
8619                && candidate.fq_name() == first.fq_name()
8620        })
8621        .then(|| first.clone())
8622}
8623
8624fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
8625    is_type_alias(unit)
8626        || analyzer
8627            .type_alias_provider()
8628            .is_some_and(|provider| provider.is_type_alias(unit))
8629}
8630
8631pub fn field_declared_type_binding(
8632    analyzer: &CppGraphSource<'_>,
8633    visibility: &VisibilityIndex<'_>,
8634    visible_from: &ProjectFile,
8635    field: &CodeUnit,
8636) -> Option<(String, Option<CodeUnit>, i32)> {
8637    let fact = visibility.field_declared_type_fact(analyzer, field)?;
8638    let normalized = normalize_field_type_text(&fact.type_text);
8639    let primary = visibility.resolve_unique_canonical_type_for_declaration(
8640        analyzer,
8641        visible_from,
8642        field,
8643        &normalized,
8644    );
8645    let resolved = match (primary, fact.template_arguments.as_deref()) {
8646        (Some(primary), Some(arguments)) => visibility
8647            .resolve_template_arguments(visible_from, primary, arguments)
8648            .ok(),
8649        (resolved, None) => resolved,
8650        (None, Some(_)) => None,
8651    };
8652    Some((normalized, resolved, fact.indirection))
8653}
8654
8655fn decode_field_declared_type_fact(
8656    analyzer: &CppGraphSource<'_>,
8657    field: &CodeUnit,
8658) -> Option<DeclaredFieldTypeFact> {
8659    let declaration = analyzer.get_source(field, false)?;
8660    let mut parser = Parser::new();
8661    parser
8662        .set_language(&tree_sitter_cpp::LANGUAGE.into())
8663        .ok()?;
8664    let tree = parser.parse(&declaration, None)?;
8665    let mut stack = vec![tree.root_node()];
8666    while let Some(node) = stack.pop() {
8667        if matches!(node.kind(), "declaration" | "field_declaration")
8668            && let Some(type_node) = node
8669                .child_by_field_name("type")
8670                .or_else(|| first_type_child(node))
8671            && let Some(indirection) =
8672                declared_name_indirection(node, type_node, field.identifier(), &declaration)
8673        {
8674            let declared_type = if matches!(
8675                type_node.kind(),
8676                "class_specifier" | "struct_specifier" | "union_specifier"
8677            ) {
8678                type_node.child_by_field_name("name")?
8679            } else {
8680                type_node
8681            };
8682            return Some(DeclaredFieldTypeFact {
8683                type_text: node_text(declared_type, &declaration).to_string(),
8684                indirection,
8685                template_arguments: cpp_template_reference_arguments(declared_type, &declaration),
8686            });
8687        }
8688        let mut cursor = node.walk();
8689        stack.extend(node.named_children(&mut cursor));
8690    }
8691    None
8692}
8693
8694/// Text of the type that a C or C++ alias declaration names, read from the
8695/// `type_definition` or `alias_declaration` node's `type` field.
8696///
8697/// The declaration text is never scanned. A function-pointer typedef
8698/// interleaves its aliased type with its declarator (`typedef R (*F)(int)`),
8699/// so no prefix or suffix of the spelling isolates the target.
8700///
8701/// An alias whose declarator is a function declarator names a function type:
8702/// `typedef R F(int)`, `typedef R (*F)(int)`, `typedef R *F(int)`, and
8703/// `using F = R (*)(int)`. The analyzer's type model names declared types only,
8704/// so such an alias has no canonical target. Its `type` field holds the return
8705/// type `R`, which is a different type from the alias, so this returns `None`
8706/// rather than that return type.
8707pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
8708    let mut parser = Parser::new();
8709    parser
8710        .set_language(&tree_sitter_cpp::LANGUAGE.into())
8711        .ok()?;
8712    let tree = parser.parse(declaration, None)?;
8713    let mut stack = vec![tree.root_node()];
8714    while let Some(node) = stack.pop() {
8715        let type_node = match node.kind() {
8716            "type_definition" => {
8717                let mut cursor = node.walk();
8718                if node
8719                    .children_by_field_name("declarator", &mut cursor)
8720                    .any(declarator_names_function_type)
8721                {
8722                    return None;
8723                }
8724                node.child_by_field_name("type")?
8725            }
8726            "alias_declaration" => {
8727                let type_node = node.child_by_field_name("type")?;
8728                if type_node
8729                    .child_by_field_name("declarator")
8730                    .is_some_and(declarator_names_function_type)
8731                {
8732                    return None;
8733                }
8734                type_node
8735            }
8736            _ => {
8737                let mut cursor = node.walk();
8738                let children = node.named_children(&mut cursor).collect::<Vec<_>>();
8739                stack.extend(children.into_iter().rev());
8740                continue;
8741            }
8742        };
8743        return Some(node_text(type_node, declaration).to_string());
8744    }
8745    None
8746}
8747
8748/// True when an alias declarator names a function type.
8749///
8750/// The declarator chain is walked through the `declarator` field, so the
8751/// parameter list -- a sibling field -- is never entered and a parameter's own
8752/// function declarator cannot be mistaken for the alias's.
8753fn declarator_names_function_type(declarator: Node<'_>) -> bool {
8754    let mut current = Some(declarator);
8755    while let Some(node) = current {
8756        match node.kind() {
8757            "function_declarator" | "abstract_function_declarator" => return true,
8758            "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
8759                current = node.named_child(0);
8760            }
8761            _ => current = node.child_by_field_name("declarator"),
8762        }
8763    }
8764    false
8765}
8766
8767fn decode_structured_alias_target(
8768    analyzer: &CppGraphSource<'_>,
8769    unit: &CodeUnit,
8770) -> Option<StructuredAliasTarget> {
8771    analyzer
8772        .get_source(unit, false)
8773        .and_then(|declaration| decode_structured_alias_target_source(unit, &declaration, true))
8774        .or_else(|| {
8775            let signature = unit.signature()?;
8776            decode_structured_alias_target_source(unit, signature, false)
8777        })
8778}
8779
8780fn decode_structured_alias_target_source(
8781    unit: &CodeUnit,
8782    declaration: &str,
8783    require_top_level: bool,
8784) -> Option<StructuredAliasTarget> {
8785    let mut parser = Parser::new();
8786    parser
8787        .set_language(&tree_sitter_cpp::LANGUAGE.into())
8788        .ok()?;
8789    let tree = parser.parse(declaration, None)?;
8790    let mut stack = vec![tree.root_node()];
8791    while let Some(node) = stack.pop() {
8792        let type_node = match node.kind() {
8793            "type_definition" => {
8794                if require_top_level
8795                    && node
8796                        .parent()
8797                        .is_none_or(|parent| parent.kind() != "translation_unit")
8798                {
8799                    let mut cursor = node.walk();
8800                    stack.extend(node.named_children(&mut cursor));
8801                    continue;
8802                }
8803                let mut declarator_cursor = node.walk();
8804                let declarator = node
8805                    .children_by_field_name("declarator", &mut declarator_cursor)
8806                    .find(|declarator| {
8807                        extract_typedef_declarator_name(*declarator, declaration)
8808                            .is_some_and(|name| name == unit.identifier())
8809                    })?;
8810                if declarator_names_function_type(declarator) {
8811                    return None;
8812                }
8813                node.child_by_field_name("type")?
8814            }
8815            "alias_declaration" => {
8816                if require_top_level
8817                    && node
8818                        .parent()
8819                        .is_none_or(|parent| parent.kind() != "translation_unit")
8820                {
8821                    let mut cursor = node.walk();
8822                    stack.extend(node.named_children(&mut cursor));
8823                    continue;
8824                }
8825                let name = node.child_by_field_name("name")?;
8826                if node_text(name, declaration) != unit.identifier() {
8827                    return None;
8828                }
8829                let type_node = node.child_by_field_name("type")?;
8830                if type_node
8831                    .child_by_field_name("declarator")
8832                    .is_some_and(declarator_names_function_type)
8833                {
8834                    return None;
8835                }
8836                type_node
8837            }
8838            _ => {
8839                let mut cursor = node.walk();
8840                stack.extend(node.named_children(&mut cursor));
8841                continue;
8842            }
8843        };
8844        return structured_alias_type_target(type_node, declaration);
8845    }
8846    None
8847}
8848
8849fn structured_alias_type_target(
8850    mut type_node: Node<'_>,
8851    source: &str,
8852) -> Option<StructuredAliasTarget> {
8853    while type_node.kind() == "type_descriptor" {
8854        type_node = type_node.child_by_field_name("type")?;
8855    }
8856    if type_node.kind() == "primitive_type" {
8857        return Some(StructuredAliasTarget::Builtin);
8858    }
8859    if matches!(
8860        type_node.kind(),
8861        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
8862    ) {
8863        type_node = type_node.child_by_field_name("name")?;
8864    }
8865    let global = type_node.child_by_field_name("scope").is_none()
8866        && type_node.child(0).is_some_and(|child| child.kind() == "::");
8867    let mut components = Vec::new();
8868    append_structured_type_components(type_node, source, &mut components)?;
8869    let arguments = cpp_template_reference_arguments(type_node, source);
8870    (!components.is_empty()).then_some(StructuredAliasTarget::Named {
8871        components,
8872        global,
8873        arguments,
8874    })
8875}
8876
8877fn append_structured_type_components(
8878    node: Node<'_>,
8879    source: &str,
8880    out: &mut Vec<String>,
8881) -> Option<()> {
8882    match node.kind() {
8883        "identifier" | "namespace_identifier" | "type_identifier" => {
8884            out.push(node_text(node, source).to_string());
8885            Some(())
8886        }
8887        "template_type" => {
8888            append_structured_type_components(node.child_by_field_name("name")?, source, out)
8889        }
8890        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
8891            if let Some(scope) = node.child_by_field_name("scope") {
8892                append_structured_type_components(scope, source, out)?;
8893            }
8894            append_structured_type_components(node.child_by_field_name("name")?, source, out)
8895        }
8896        _ => None,
8897    }
8898}
8899
8900fn declared_name_indirection(
8901    declaration: Node<'_>,
8902    type_node: Node<'_>,
8903    field_name: &str,
8904    source: &str,
8905) -> Option<i32> {
8906    let mut stack = Vec::new();
8907    let mut cursor = declaration.walk();
8908    stack.extend(
8909        declaration
8910            .named_children(&mut cursor)
8911            .filter(|child| !same_node(*child, type_node)),
8912    );
8913    while let Some(node) = stack.pop() {
8914        if matches!(node.kind(), "identifier" | "field_identifier")
8915            && node_text(node, source) == field_name
8916        {
8917            let mut indirection = 0;
8918            let mut current = node.parent();
8919            while let Some(parent) = current {
8920                if same_node(parent, declaration) {
8921                    return Some(indirection);
8922                }
8923                if parent.kind() == "pointer_declarator" {
8924                    indirection += 1;
8925                }
8926                current = parent.parent();
8927            }
8928            return None;
8929        }
8930        let mut cursor = node.walk();
8931        stack.extend(node.named_children(&mut cursor));
8932    }
8933    None
8934}
8935
8936fn field_declaration_type_matches(
8937    declaration: &str,
8938    unit: &CodeUnit,
8939    ctx: &ScanCtx<'_>,
8940    owner: &CodeUnit,
8941) -> bool {
8942    ctx.visibility
8943        .resolves_to_type(&ctx.analyzer, ctx.file, declaration, owner)
8944        || field_type_prefix(declaration, unit.identifier()).is_some_and(|type_text| {
8945            let normalized = normalize_field_type_text(type_text);
8946            ctx.visibility
8947                .resolves_to_type(&ctx.analyzer, ctx.file, type_text, owner)
8948                || ctx.visibility.resolves_to_type(
8949                    &ctx.analyzer,
8950                    ctx.file,
8951                    normalized.as_str(),
8952                    owner,
8953                )
8954        })
8955}
8956
8957fn field_type_prefix<'a>(declaration: &'a str, field_name: &str) -> Option<&'a str> {
8958    let declaration = declaration
8959        .split(['=', ';'])
8960        .next()
8961        .unwrap_or(declaration)
8962        .trim();
8963    let index = declaration.rfind(field_name)?;
8964    let before = &declaration[..index];
8965    let after = &declaration[index + field_name.len()..];
8966    if before.chars().next_back().is_some_and(is_identifier_char)
8967        || after.chars().next().is_some_and(is_identifier_char)
8968    {
8969        return None;
8970    }
8971    Some(before.trim())
8972}
8973
8974fn normalize_field_type_text(type_text: &str) -> String {
8975    const FIELD_SPECIFIERS: [&str; 8] = [
8976        "extern ",
8977        "static ",
8978        "mutable ",
8979        "constexpr ",
8980        "constinit ",
8981        "inline ",
8982        "volatile ",
8983        "const ",
8984    ];
8985
8986    let mut normalized = normalize_type_text(type_text);
8987    loop {
8988        let Some(stripped) = FIELD_SPECIFIERS
8989            .iter()
8990            .find_map(|specifier| normalized.strip_prefix(specifier))
8991        else {
8992            return normalized;
8993        };
8994        normalized = normalize_type_text(stripped);
8995    }
8996}
8997
8998fn is_identifier_char(ch: char) -> bool {
8999    ch == '_' || ch.is_ascii_alphanumeric()
9000}
9001
9002pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
9003    let Some(type_node) = node.child_by_field_name("type") else {
9004        return false;
9005    };
9006    ctx.visibility.resolves_to_type(
9007        &ctx.analyzer,
9008        ctx.file,
9009        node_text(type_node, ctx.source),
9010        owner,
9011    )
9012}
9013
9014pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9015    !ctx.analyzer
9016        .declarations(ctx.file)
9017        .into_iter()
9018        .filter(|unit| unit.is_function())
9019        .any(|unit| {
9020            ctx.analyzer.ranges(&unit).iter().any(|range| {
9021                node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
9022            })
9023        })
9024}
9025
9026pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
9027    let mut cursor = node.walk();
9028    for child in node.named_children(&mut cursor) {
9029        if child.kind() == "init_declarator" {
9030            return child
9031                .child_by_field_name("value")
9032                .or_else(|| first_named_child_of_kind(child, "initializer_list"))
9033                .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
9034                .map(declaration_init_value_arity)
9035                .unwrap_or(0);
9036        }
9037        if is_declarator_node(child) {
9038            return declaration_declarator_arity(child);
9039        }
9040    }
9041    0
9042}
9043
9044fn declaration_init_value_arity(value: Node<'_>) -> usize {
9045    match value.kind() {
9046        "argument_list" | "initializer_list" => argument_children(value).count(),
9047        "compound_literal_expression" => call_arity(value),
9048        _ => 1,
9049    }
9050}
9051
9052fn declaration_declarator_arity(node: Node<'_>) -> usize {
9053    if let Some(parameters) = node.child_by_field_name("parameters") {
9054        return argument_children(parameters).count();
9055    }
9056    node.child_by_field_name("declarator")
9057        .map(declaration_declarator_arity)
9058        .unwrap_or(0)
9059}
9060
9061fn first_named_child_of_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
9062    let mut cursor = node.walk();
9063    node.named_children(&mut cursor)
9064        .find(|child| child.kind() == kind)
9065}
9066
9067fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
9068    let mut stack = vec![root];
9069    while let Some(node) = stack.pop() {
9070        if node.kind() == kind {
9071            return Some(node);
9072        }
9073        for index in (0..node.named_child_count()).rev() {
9074            if let Some(child) = node.named_child(index) {
9075                stack.push(child);
9076            }
9077        }
9078    }
9079    None
9080}
9081
9082fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
9083    if node.kind() == "identifier" {
9084        return true;
9085    }
9086    if node.kind() == "parenthesized_expression" {
9087        return false;
9088    }
9089    if node.kind() == "call_expression" {
9090        return node
9091            .child_by_field_name("function")
9092            .is_some_and(|function| function.kind() == "identifier");
9093    }
9094    let mut stack = vec![node];
9095    while let Some(descendant) = stack.pop() {
9096        if descendant != node && descendant.kind() == "parenthesized_expression" {
9097            continue;
9098        }
9099        if descendant.kind() == "identifier" {
9100            return true;
9101        }
9102        if descendant.kind() == "call_expression" {
9103            if descendant
9104                .child_by_field_name("function")
9105                .is_some_and(|function| function.kind() == "identifier")
9106            {
9107                return true;
9108            }
9109            continue;
9110        }
9111        for index in (0..descendant.named_child_count()).rev() {
9112            if let Some(child) = descendant.named_child(index) {
9113                stack.push(child);
9114            }
9115        }
9116    }
9117    false
9118}
9119
9120fn macro_expansion_shape_is_safe(
9121    node: Node<'_>,
9122    source: &str,
9123    parameters: &[String],
9124    environment: &MacroEnvironment,
9125) -> bool {
9126    if matches!(node.kind(), "identifier" | "parenthesized_expression") {
9127        return true;
9128    }
9129    if node.kind() == "call_expression" {
9130        let Some(function) = node.child_by_field_name("function") else {
9131            return true;
9132        };
9133        if function.kind() != "identifier" {
9134            return true;
9135        }
9136        let function_name = node_text(function, source);
9137        if parameters
9138            .iter()
9139            .any(|parameter| parameter == function_name)
9140        {
9141            return false;
9142        }
9143        if !environment.may_bind(function_name) {
9144            return true;
9145        }
9146        let Some(arguments) = node.child_by_field_name("arguments") else {
9147            return false;
9148        };
9149        return argument_children(arguments).all(|argument| {
9150            if argument.kind() == "identifier"
9151                && parameters
9152                    .iter()
9153                    .any(|parameter| parameter == node_text(argument, source))
9154            {
9155                return false;
9156            }
9157            macro_expansion_shape_is_safe(argument, source, parameters, environment)
9158        });
9159    }
9160    let mut stack = vec![node];
9161    while let Some(descendant) = stack.pop() {
9162        if descendant != node {
9163            if descendant.kind() == "parenthesized_expression" {
9164                continue;
9165            }
9166            if descendant.kind() == "call_expression" {
9167                let expands = descendant
9168                    .child_by_field_name("function")
9169                    .filter(|function| function.kind() == "identifier")
9170                    .is_some_and(|function| environment.may_bind(node_text(function, source)));
9171                if expands {
9172                    return false;
9173                }
9174                continue;
9175            }
9176        }
9177        if descendant.kind() == "identifier" {
9178            let identifier = node_text(descendant, source);
9179            if parameters.iter().any(|parameter| parameter == identifier)
9180                || environment.may_bind(identifier)
9181            {
9182                return false;
9183            }
9184        }
9185        for index in (0..descendant.named_child_count()).rev() {
9186            if let Some(child) = descendant.named_child(index) {
9187                stack.push(child);
9188            }
9189        }
9190    }
9191    true
9192}
9193
9194fn structured_include_path<'a>(path: Node<'_>, source: &'a str) -> Option<&'a str> {
9195    let text = node_text(path, source);
9196    match path.kind() {
9197        "string_literal" => text.strip_prefix('"')?.strip_suffix('"'),
9198        "system_lib_string" => text.strip_prefix('<')?.strip_suffix('>'),
9199        _ => None,
9200    }
9201}
9202
9203fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
9204    let descendant = node;
9205    while let Some(parent) = node.parent() {
9206        if is_preprocessor_conditional(parent)
9207            && !is_file_covering_include_guard(parent, source)
9208            && preprocessor_conditional_contains_descendant(parent, descendant)
9209        {
9210            return true;
9211        }
9212        node = parent;
9213    }
9214    false
9215}
9216
9217fn is_preprocessor_conditional(node: Node<'_>) -> bool {
9218    matches!(
9219        node.kind(),
9220        "preproc_if"
9221            | "preproc_ifdef"
9222            | "preproc_ifndef"
9223            | "preproc_elif"
9224            | "preproc_elifdef"
9225            | "preproc_else"
9226    )
9227}
9228
9229fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
9230    node.parent()
9231        .filter(|parent| parent.kind() == "translation_unit")
9232        .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
9233        && is_canonical_include_guard(node, source)
9234}
9235
9236fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
9237    if node.kind() != "preproc_ifdef"
9238        || node
9239            .child(0)
9240            .is_none_or(|directive| directive.kind() != "#ifndef")
9241        || node.child_by_field_name("alternative").is_some()
9242    {
9243        return false;
9244    }
9245    let Some(guard_name) = node.child_by_field_name("name") else {
9246        return false;
9247    };
9248    let mut cursor = node.walk();
9249    node.named_children(&mut cursor)
9250        .find(|child| *child != guard_name && child.kind() != "comment")
9251        .filter(|child| child.kind() == "preproc_def")
9252        .and_then(|definition| definition.child_by_field_name("name"))
9253        .is_some_and(|defined_name| {
9254            node_text(defined_name, source) == node_text(guard_name, source)
9255        })
9256}
9257
9258fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
9259    let mut guard = None;
9260    for index in 0..root.named_child_count() {
9261        let Some(child) = root.named_child(index) else {
9262            continue;
9263        };
9264        if child.kind() == "comment" || is_pragma_once(child, source) {
9265            continue;
9266        }
9267        if guard.is_none() && is_canonical_include_guard(child, source) {
9268            guard = Some(child);
9269        } else {
9270            return None;
9271        }
9272    }
9273    guard
9274        .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
9275        .map(|name| node_text(name, source).to_string())
9276}
9277
9278fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
9279    if (0..root.named_child_count())
9280        .filter_map(|index| root.named_child(index))
9281        .any(|child| is_pragma_once(child, source))
9282    {
9283        return MacroIncludeProtection::PragmaOnce;
9284    }
9285    top_level_canonical_include_guard_name(root, source)
9286        .map(MacroIncludeProtection::MacroGuard)
9287        .unwrap_or(MacroIncludeProtection::None)
9288}
9289
9290fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
9291    node.kind() == "preproc_call"
9292        && node
9293            .child_by_field_name("directive")
9294            .is_some_and(|directive| node_text(directive, source) == "#pragma")
9295        && node
9296            .child_by_field_name("argument")
9297            .is_some_and(|argument| node_text(argument, source).trim() == "once")
9298}
9299
9300fn parse_preproc_identifier(argument: &str) -> Option<String> {
9301    let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
9302    let mut parser = Parser::new();
9303    parser
9304        .set_language(&tree_sitter_cpp::LANGUAGE.into())
9305        .ok()?;
9306    let tree = parser.parse(&sentinel, None)?;
9307    if tree.root_node().has_error() {
9308        return None;
9309    }
9310    let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
9311    let identifier = statement.named_child(0)?;
9312    (identifier.kind() == "identifier" && statement.named_child_count() == 1)
9313        .then(|| node_text(identifier, &sentinel).to_string())
9314}
9315
9316pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
9317    match node.kind() {
9318        "identifier" | "field_identifier" => {
9319            let name = node_text(node, source).trim();
9320            (!name.is_empty()).then(|| name.to_string())
9321        }
9322        "abstract_array_declarator"
9323        | "abstract_function_declarator"
9324        | "abstract_parenthesized_declarator"
9325        | "abstract_pointer_declarator"
9326        | "abstract_reference_declarator" => None,
9327        "function_declarator" => node
9328            .child_by_field_name("declarator")
9329            .or_else(|| node.child_by_field_name("name"))
9330            .and_then(|child| extract_variable_name(child, source)),
9331        _ => node
9332            .child_by_field_name("declarator")
9333            .or_else(|| node.child_by_field_name("name"))
9334            .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
9335            .and_then(|child| extract_variable_name(child, source)),
9336    }
9337}
9338
9339/// Whether `file` is proven to use plain-C source semantics.
9340///
9341/// `Language::Cpp` intentionally serves both C and C++. Headers do not carry a
9342/// compilation dialect on their own, so only an exact `.c` source extension is
9343/// sufficient to reinterpret C++-grammar keyword nodes such as `this` as C
9344/// identifiers.
9345pub fn is_c_source_file(file: &ProjectFile) -> bool {
9346    file.rel_path()
9347        .extension()
9348        .and_then(|extension| extension.to_str())
9349        == Some("c")
9350}
9351
9352pub fn is_declarator_node(node: Node<'_>) -> bool {
9353    matches!(
9354        node.kind(),
9355        "identifier"
9356            | "field_identifier"
9357            | "pointer_declarator"
9358            | "reference_declarator"
9359            | "array_declarator"
9360            | "parenthesized_declarator"
9361            | "function_declarator"
9362    )
9363}
9364
9365#[derive(Clone, Default)]
9366pub struct OrphanedNamespaceTypeScopeIndex {
9367    scopes: Vec<OrphanedNamespaceTypeScope>,
9368}
9369
9370#[derive(Clone)]
9371struct OrphanedNamespaceTypeScope {
9372    body_end: usize,
9373    scope_end: usize,
9374    components: Vec<String>,
9375}
9376
9377impl OrphanedNamespaceTypeScopeIndex {
9378    /// Index the physical namespace interval that remains after tree-sitter
9379    /// prematurely closes an error-marked namespace at a recovered class body.
9380    /// The later unmatched `}` is the structured upper bound: declarations
9381    /// between the truncated body and that token remain in the namespace, while
9382    /// declarations after it do not.
9383    pub fn build(root: Node<'_>, source: &str) -> Self {
9384        let mut scopes = Vec::new();
9385        let mut stack = vec![root];
9386        while let Some(current) = stack.pop() {
9387            if current.kind() == "namespace_definition"
9388                && current.has_error()
9389                && let Some(body) = current.child_by_field_name("body")
9390                && current.end_byte() == body.end_byte()
9391                && let Some(name) = current.child_by_field_name("name")
9392            {
9393                let mut components =
9394                    enclosing_namespace_components(current, source).unwrap_or_default();
9395                if append_cpp_name_components(name, source, &mut components).is_some()
9396                    && !components.is_empty()
9397                {
9398                    let mut following = current.next_named_sibling();
9399                    while let Some(candidate) = following {
9400                        if direct_unmatched_closing_brace(candidate) {
9401                            scopes.push(OrphanedNamespaceTypeScope {
9402                                body_end: body.end_byte(),
9403                                scope_end: candidate.start_byte(),
9404                                components,
9405                            });
9406                            break;
9407                        }
9408                        following = candidate.next_named_sibling();
9409                    }
9410                }
9411            }
9412            if !current.has_error() {
9413                continue;
9414            }
9415            let mut cursor = current.walk();
9416            stack.extend(
9417                current
9418                    .named_children(&mut cursor)
9419                    .filter(|child| child.has_error()),
9420            );
9421        }
9422        Self { scopes }
9423    }
9424
9425    pub fn scope_at(&self, byte: usize) -> Option<(usize, &[String])> {
9426        self.scopes
9427            .iter()
9428            .filter(|scope| scope.body_end < byte && byte < scope.scope_end)
9429            .max_by_key(|scope| (scope.components.len(), scope.body_end))
9430            .map(|scope| (scope.body_end, scope.components.as_slice()))
9431    }
9432}
9433
9434#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9435pub enum RecoveredDeclaratorTypeContext {
9436    Declaration,
9437    FunctionDefinition,
9438    Parameter,
9439}
9440
9441/// Recognize a real type displaced into a qualified declarator by parser
9442/// recovery.
9443///
9444/// Tree-sitter parses `API Result *make(Arg);` as if `API` were the declared
9445/// type and `Result` were the scope of a qualified declarator with a missing
9446/// `::`. A template return such as `API Result<T> make()` uses a
9447/// `template_type` for the same recovered scope. The same recovery occurs for
9448/// macro-prefixed definitions, extern variables, and macro-decorated
9449/// parameters (`f(MACRO T* p)`, where the parameter's own `type` field takes
9450/// the macro). Keep this intentionally structural: the recovered scope must
9451/// have the grammar's missing separator, the qualified node must occupy the
9452/// declaration's declarator chain, a separate nonempty type must occupy the
9453/// normal type field, and the recovered name must unwrap to a real declarator
9454/// name.
9455pub fn recovered_macro_decorated_declarator_type(
9456    node: Node<'_>,
9457) -> Option<RecoveredDeclaratorTypeContext> {
9458    recovered_macro_decorated_type_node(node).map(|(_, context)| context)
9459}
9460
9461/// Return the declaration/function `type` displaced by a macro-shaped
9462/// qualified declarator, together with the enclosing declaration context.
9463/// Callers use the macro scope only as structural admission evidence; the
9464/// returned node is the real type reference to resolve and record.
9465pub fn recovered_macro_decorated_type_node(
9466    node: Node<'_>,
9467) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
9468    if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
9469        return None;
9470    }
9471    let qualified = node.parent()?;
9472    if qualified.kind() != "qualified_identifier"
9473        || qualified.child_by_field_name("scope") != Some(node)
9474        || !(0..qualified.child_count())
9475            .filter_map(|index| qualified.child(index))
9476            .any(|child| child.kind() == "::" && child.is_missing())
9477    {
9478        return None;
9479    }
9480    if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
9481        return None;
9482    }
9483
9484    let (declaration, context) = recovered_declarator_container(qualified)?;
9485    let type_node = declaration
9486        .child_by_field_name("type")
9487        .filter(|type_node| {
9488            *type_node != qualified
9489                && !type_node.is_missing()
9490                && type_node.start_byte() != type_node.end_byte()
9491        })?;
9492    Some((type_node, context))
9493}
9494
9495fn recovered_declarator_container(
9496    mut declarator: Node<'_>,
9497) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
9498    loop {
9499        let parent = declarator.parent()?;
9500        if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
9501            return Some((
9502                parent
9503                    .parent()
9504                    .filter(|declaration| declaration.kind() == "declaration")?,
9505                RecoveredDeclaratorTypeContext::Declaration,
9506            ));
9507        }
9508        if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
9509            return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
9510        }
9511        if parent.kind() == "function_definition"
9512            && has_field_child(parent, "declarator", declarator)
9513        {
9514            return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
9515        }
9516        // `f(MACRO T* p)` recovers exactly like `MACRO T *make(...)` does, one
9517        // level down: the parameter's `type` field takes the macro token and
9518        // the real type `T` becomes the recovered scope of the declarator.
9519        // Declining here left every xxhash `XXH_NOESCAPE` parameter with no
9520        // candidate at all (#1830).
9521        if matches!(
9522            parent.kind(),
9523            "parameter_declaration" | "optional_parameter_declaration"
9524        ) && has_field_child(parent, "declarator", declarator)
9525        {
9526            return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
9527        }
9528        if !matches!(
9529            parent.kind(),
9530            "array_declarator"
9531                | "function_declarator"
9532                | "parenthesized_declarator"
9533                | "pointer_declarator"
9534                | "pointer_type_declarator"
9535                | "reference_declarator"
9536        ) || !has_field_child(parent, "declarator", declarator)
9537        {
9538            return None;
9539        }
9540        declarator = parent;
9541    }
9542}
9543
9544fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
9545    let mut cursor = parent.walk();
9546    parent
9547        .children_by_field_name(field, &mut cursor)
9548        .any(|child| child == target)
9549}
9550
9551fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
9552    loop {
9553        if node.is_missing() || node.start_byte() == node.end_byte() {
9554            return false;
9555        }
9556        match node.kind() {
9557            "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
9558                return true;
9559            }
9560            "array_declarator"
9561            | "function_declarator"
9562            | "parenthesized_declarator"
9563            | "pointer_declarator"
9564            | "pointer_type_declarator"
9565            | "reference_declarator" => {
9566                let Some(declarator) = node.child_by_field_name("declarator") else {
9567                    return false;
9568                };
9569                node = declarator;
9570            }
9571            _ => return false,
9572        }
9573    }
9574}
9575
9576/// Aggregate-owner proof for a structurally recognized designated initializer.
9577pub enum DesignatedInitializerOwner {
9578    Resolved(CodeUnit),
9579    Unresolved,
9580}
9581
9582/// Recognize a designated-initializer field and, when possible, resolve its
9583/// aggregate owner.
9584///
9585/// Covers both the grammar's ordinary `field_designator` shape and the exact
9586/// recovery used for `.field = value` after a preprocessor-split array
9587/// initializer. Nested aggregate levels are deliberately left unresolved unless
9588/// the single outer level is the containing array initializer: resolving those
9589/// would require following the enclosing field's declared type. `None` means the
9590/// node is not a designator at all; an unresolved designator remains classified so
9591/// callers cannot fall through to unrelated global/member heuristics.
9592pub fn designated_initializer_owner(
9593    visibility: &VisibilityIndex<'_>,
9594    file: &ProjectFile,
9595    source: &str,
9596    node: Node<'_>,
9597) -> Option<DesignatedInitializerOwner> {
9598    if let Some(designator) = node
9599        .parent()
9600        .filter(|parent| parent.kind() == "field_designator")
9601    {
9602        let pair = designator.parent()?;
9603        if pair.kind() != "initializer_pair"
9604            || pair.child_by_field_name("designator") != Some(designator)
9605        {
9606            return None;
9607        }
9608        let initializer = pair.parent()?;
9609        if initializer.kind() != "initializer_list" {
9610            return None;
9611        }
9612        return Some(classified_designated_owner(initializer_list_owner(
9613            visibility,
9614            file,
9615            source,
9616            initializer,
9617        )));
9618    }
9619
9620    let init_declarator = node.parent()?;
9621    if init_declarator.child_by_field_name("declarator") != Some(node)
9622        || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
9623    {
9624        return None;
9625    }
9626    Some(classified_designated_owner(declaration_owner(
9627        visibility,
9628        file,
9629        source,
9630        init_declarator.parent()?,
9631    )))
9632}
9633
9634fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
9635    owner.map_or(
9636        DesignatedInitializerOwner::Unresolved,
9637        DesignatedInitializerOwner::Resolved,
9638    )
9639}
9640
9641fn initializer_list_owner(
9642    visibility: &VisibilityIndex<'_>,
9643    file: &ProjectFile,
9644    source: &str,
9645    initializer: Node<'_>,
9646) -> Option<CodeUnit> {
9647    let mut current = initializer;
9648    let mut outer_initializer_lists = 0usize;
9649    loop {
9650        let parent = current.parent()?;
9651        match parent.kind() {
9652            "initializer_pair" => return None,
9653            "initializer_list" => {
9654                outer_initializer_lists += 1;
9655                if outer_initializer_lists > 1 {
9656                    return None;
9657                }
9658                current = parent;
9659            }
9660            "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
9661                let declaration = parent.parent()?;
9662                if outer_initializer_lists == 1
9663                    && !parent
9664                        .child_by_field_name("declarator")
9665                        .is_some_and(contains_array_declarator)
9666                {
9667                    return None;
9668                }
9669                return declaration_owner(visibility, file, source, declaration);
9670            }
9671            "compound_literal_expression"
9672                if parent.child_by_field_name("value") == Some(current)
9673                    && outer_initializer_lists == 0 =>
9674            {
9675                let type_node = parent.child_by_field_name("type")?;
9676                return resolve_designated_owner_type(visibility, file, source, type_node);
9677            }
9678            "ERROR" => current = parent,
9679            _ => return None,
9680        }
9681    }
9682}
9683
9684fn declaration_owner(
9685    visibility: &VisibilityIndex<'_>,
9686    file: &ProjectFile,
9687    source: &str,
9688    declaration: Node<'_>,
9689) -> Option<CodeUnit> {
9690    if !matches!(declaration.kind(), "declaration" | "field_declaration") {
9691        return None;
9692    }
9693    let type_node = declaration
9694        .child_by_field_name("type")
9695        .or_else(|| first_type_child(declaration))?;
9696    resolve_designated_owner_type(visibility, file, source, type_node)
9697}
9698
9699fn resolve_designated_owner_type(
9700    visibility: &VisibilityIndex<'_>,
9701    file: &ProjectFile,
9702    source: &str,
9703    type_node: Node<'_>,
9704) -> Option<CodeUnit> {
9705    let type_name = normalize_type_text(node_text(type_node, source));
9706    visibility
9707        .resolve_type(file, &type_name)
9708        .filter(CodeUnit::is_class)
9709}
9710
9711fn contains_array_declarator(declarator: Node<'_>) -> bool {
9712    let mut stack = vec![declarator];
9713    while let Some(node) = stack.pop() {
9714        if node.kind() == "array_declarator" {
9715            return true;
9716        }
9717        if matches!(node.kind(), "initializer_list" | "compound_statement") {
9718            continue;
9719        }
9720        let mut cursor = node.walk();
9721        stack.extend(node.named_children(&mut cursor));
9722    }
9723    false
9724}
9725
9726pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
9727    let mut cursor = node.walk();
9728    node.named_children(&mut cursor).find(|child| {
9729        matches!(
9730            child.kind(),
9731            "type_identifier"
9732                | "primitive_type"
9733                | "qualified_identifier"
9734                | "scoped_type_identifier"
9735                | "struct_specifier"
9736                | "union_specifier"
9737                | "enum_specifier"
9738        )
9739    })
9740}
9741
9742pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
9743    visibility: &VisibilityIndex<'_>,
9744    file: &ProjectFile,
9745    source: &str,
9746    declarator: Node<'_>,
9747    type_text: Option<&str>,
9748    bindings: &LocalInferenceEngine<T>,
9749) -> bool {
9750    if !has_ancestor_kind(declarator, "compound_statement") {
9751        return false;
9752    }
9753    if declarator
9754        .child_by_field_name("declarator")
9755        .is_none_or(|declarator| declarator.kind() != "identifier")
9756    {
9757        return false;
9758    }
9759    if !type_text
9760        .and_then(|text| visibility.resolve_type(file, text))
9761        .is_some_and(|unit| unit.is_class())
9762    {
9763        return false;
9764    }
9765    declarator
9766        .child_by_field_name("parameters")
9767        .is_some_and(|parameters| {
9768            constructor_parameters_look_like_expressions(parameters, source, bindings)
9769        })
9770}
9771
9772fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
9773    parameters: Node<'_>,
9774    source: &str,
9775    bindings: &LocalInferenceEngine<T>,
9776) -> bool {
9777    let mut cursor = parameters.walk();
9778    parameters.named_children(&mut cursor).any(|parameter| {
9779        !matches!(
9780            parameter.kind(),
9781            "parameter_declaration" | "optional_parameter_declaration"
9782        ) || parameter_declaration_is_local_expression(parameter, source, bindings)
9783    })
9784}
9785
9786fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
9787    parameter: Node<'_>,
9788    source: &str,
9789    bindings: &LocalInferenceEngine<T>,
9790) -> bool {
9791    let text = node_text(parameter, source).trim();
9792    if text
9793        .chars()
9794        .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
9795        && bindings.is_shadowed(text)
9796    {
9797        return true;
9798    }
9799
9800    let Some(base) = parameter
9801        .child_by_field_name("type")
9802        .filter(|base| base.kind() == "type_identifier")
9803    else {
9804        return false;
9805    };
9806    let Some(subscript) = parameter
9807        .child_by_field_name("declarator")
9808        .filter(|declarator| declarator.kind() == "abstract_array_declarator")
9809    else {
9810        return false;
9811    };
9812    subscript.child_by_field_name("size").is_some()
9813        && bindings.is_shadowed(node_text(base, source).trim())
9814}
9815
9816pub fn is_declaration_name(node: Node<'_>) -> bool {
9817    let Some(parent) = node.parent() else {
9818        return false;
9819    };
9820    if parent
9821        .child_by_field_name("name")
9822        .is_some_and(|name| same_node(name, node))
9823    {
9824        if matches!(
9825            parent.kind(),
9826            "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
9827        ) {
9828            return cpp_tag_specifier_declares_name(parent);
9829        }
9830        if matches!(
9831            parent.kind(),
9832            "namespace_definition"
9833                | "namespace_alias_definition"
9834                | "alias_declaration"
9835                | "enumerator"
9836        ) {
9837            return true;
9838        }
9839    }
9840
9841    let mut current = Some(parent);
9842    while let Some(ancestor) = current {
9843        let type_definition = ancestor.kind() == "type_definition";
9844        let mut declarator_cursor = ancestor.walk();
9845        if ancestor
9846            .children_by_field_name("declarator", &mut declarator_cursor)
9847            .any(|declarator| declarator_name_path_contains(declarator, node, type_definition))
9848        {
9849            return true;
9850        }
9851        if matches!(
9852            ancestor.kind(),
9853            "declaration"
9854                | "field_declaration"
9855                | "parameter_declaration"
9856                | "optional_parameter_declaration"
9857                | "function_definition"
9858                | "type_definition"
9859                | "alias_declaration"
9860                | "class_specifier"
9861                | "struct_specifier"
9862                | "union_specifier"
9863                | "enum_specifier"
9864        ) {
9865            return false;
9866        }
9867        current = ancestor.parent();
9868    }
9869    false
9870}
9871
9872/// Whether tree-sitter recovered a qualified friend-class type as an ordinary
9873/// declaration's declarator inside a malformed class body.
9874///
9875/// An export macro between `class` and the class name can make the containing
9876/// body parse as a function body. A source declaration such as
9877/// `friend class internal::Friend;` then retains this exact structure:
9878/// `declaration(type: friend, ERROR(class), declarator: internal::Friend)`.
9879/// The declarator is a type reference despite its field role.
9880pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
9881    if !matches!(
9882        node.kind(),
9883        "qualified_identifier" | "scoped_type_identifier"
9884    ) {
9885        return false;
9886    }
9887    let Some(declaration) = node
9888        .parent()
9889        .filter(|parent| parent.kind() == "declaration")
9890    else {
9891        return false;
9892    };
9893    if declaration.child_by_field_name("declarator") != Some(node)
9894        || !declaration
9895            .child_by_field_name("type")
9896            .is_some_and(|friend| {
9897                friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
9898            })
9899    {
9900        return false;
9901    }
9902    let mut cursor = declaration.walk();
9903    let mut errors = declaration
9904        .named_children(&mut cursor)
9905        .filter(|child| child.kind() == "ERROR");
9906    let Some(error) = errors.next() else {
9907        return false;
9908    };
9909    errors.next().is_none()
9910        && error.named_child_count() == 1
9911        && error.named_child(0).is_some_and(|class| {
9912            class.kind() == "identifier" && node_text(class, source) == "class"
9913        })
9914}
9915
9916pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
9917    if !matches!(node.kind(), "identifier" | "field_identifier") || is_declaration_name(node) {
9918        return false;
9919    }
9920    if let Some(parent) = node.parent() {
9921        if parent.kind() == "call_expression"
9922            && parent.child_by_field_name("function") == Some(node)
9923        {
9924            return false;
9925        }
9926        if matches!(parent.kind(), "labeled_statement" | "goto_statement")
9927            && parent.child_by_field_name("label") == Some(node)
9928        {
9929            return false;
9930        }
9931    }
9932    let mut current = node.parent();
9933    while let Some(ancestor) = current {
9934        if ancestor.kind().starts_with("preproc_") {
9935            return false;
9936        }
9937        if matches!(
9938            ancestor.kind(),
9939            "translation_unit" | "function_definition" | "compound_statement"
9940        ) {
9941            break;
9942        }
9943        current = ancestor.parent();
9944    }
9945    true
9946}
9947
9948fn recovered_c_reference_node(
9949    visibility: &VisibilityIndex<'_>,
9950    file: &ProjectFile,
9951    node: Node<'_>,
9952    source: &str,
9953) -> bool {
9954    if node.start_byte() >= node.end_byte()
9955        || node.is_error()
9956        || node.is_missing()
9957        || !matches!(
9958            node.kind(),
9959            "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
9960        )
9961        || recovered_c_macro_binding_role(node)
9962        || recovered_c_label_role(node)
9963    {
9964        return false;
9965    }
9966
9967    let name = node_text(node, source);
9968    if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
9969        return true;
9970    }
9971    if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
9972        return true;
9973    }
9974    if is_declaration_name(node) {
9975        return false;
9976    }
9977    if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
9978        return true;
9979    }
9980    recovered_c_reference_anchor(node)
9981}
9982
9983fn recovered_c_explicit_assignment_callee(
9984    visibility: &VisibilityIndex<'_>,
9985    file: &ProjectFile,
9986    node: Node<'_>,
9987    name: &str,
9988) -> bool {
9989    let mut current = node;
9990    let error = loop {
9991        let Some(parent) = current.parent() else {
9992            return false;
9993        };
9994        if parent.is_error() {
9995            break parent;
9996        }
9997        current = parent;
9998    };
9999    let mut cursor = error.walk();
10000    let explicit_recovery_precedes_callee = error
10001        .named_children(&mut cursor)
10002        .take_while(|child| child.start_byte() < node.start_byte())
10003        .any(|child| child.kind() == "explicit_function_specifier");
10004    if !explicit_recovery_precedes_callee {
10005        return false;
10006    }
10007    visibility
10008        .cpp
10009        .declarations(file)
10010        .iter()
10011        .chain(visibility.visible_by_file.get(file).into_iter().flatten())
10012        .any(|candidate| candidate.identifier() == name && candidate.is_function())
10013}
10014
10015fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
10016    while let Some(parent) = node.parent() {
10017        if matches!(
10018            parent.kind(),
10019            "preproc_def" | "preproc_function_def" | "preproc_params"
10020        ) {
10021            return true;
10022        }
10023        if parent.is_error()
10024            || matches!(
10025                parent.kind(),
10026                "translation_unit" | "function_definition" | "compound_statement"
10027            )
10028        {
10029            return false;
10030        }
10031        node = parent;
10032    }
10033    false
10034}
10035
10036fn recovered_c_label_role(node: Node<'_>) -> bool {
10037    node.parent().is_some_and(|parent| {
10038        matches!(parent.kind(), "labeled_statement" | "goto_statement")
10039            && parent.child_by_field_name("label") == Some(node)
10040    })
10041}
10042
10043fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
10044    while let Some(parent) = node.parent() {
10045        if parent.is_error() {
10046            return false;
10047        }
10048        if parent.kind().ends_with("_expression")
10049            || matches!(
10050                parent.kind(),
10051                "argument_list"
10052                    | "return_statement"
10053                    | "expression_statement"
10054                    | "case_statement"
10055                    | "initializer_list"
10056                    | "init_declarator"
10057                    | "array_declarator"
10058                    | "field_designator"
10059                    | "enumerator"
10060            )
10061        {
10062            return true;
10063        }
10064        if matches!(
10065            parent.kind(),
10066            "translation_unit"
10067                | "function_definition"
10068                | "compound_statement"
10069                | "declaration"
10070                | "field_declaration"
10071                | "parameter_declaration"
10072        ) {
10073            return false;
10074        }
10075        node = parent;
10076    }
10077    false
10078}
10079
10080/// Whether a parameter declaration belongs to the callable scope whose body can
10081/// contain references to it.
10082///
10083/// Error recovery can wrap a macro-decorated class body in a synthetic outer
10084/// `function_definition`. Merely finding any callable ancestor would then leak
10085/// parameters from member prototypes into later member bodies. Require the
10086/// parameter to be inside that definition's own declarator instead.
10087pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
10088    let mut current = parameter.parent();
10089    while let Some(ancestor) = current {
10090        if ancestor.kind() == "lambda_expression" {
10091            return ancestor
10092                .child_by_field_name("declarator")
10093                .is_some_and(|declarator| {
10094                    declarator.start_byte() <= parameter.start_byte()
10095                        && parameter.end_byte() <= declarator.end_byte()
10096                });
10097        }
10098        if ancestor.kind() == "function_definition" {
10099            return ancestor
10100                .child_by_field_name("declarator")
10101                .is_some_and(|declarator| {
10102                    declarator.start_byte() <= parameter.start_byte()
10103                        && parameter.end_byte() <= declarator.end_byte()
10104                });
10105        }
10106        current = ancestor.parent();
10107    }
10108    false
10109}
10110
10111pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
10112    let mut current = node.parent();
10113    while let Some(ancestor) = current {
10114        if matches!(
10115            ancestor.kind(),
10116            "parameter_declaration" | "optional_parameter_declaration"
10117        ) {
10118            return ancestor
10119                .child_by_field_name("type")
10120                .is_some_and(|type_node| {
10121                    type_node.start_byte() <= node.start_byte()
10122                        && node.end_byte() <= type_node.end_byte()
10123                });
10124        }
10125        if matches!(
10126            ancestor.kind(),
10127            "function_definition" | "lambda_expression" | "compound_statement"
10128        ) {
10129            return false;
10130        }
10131        current = ancestor.parent();
10132    }
10133    false
10134}
10135
10136fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
10137    if specifier.child_by_field_name("body").is_some() {
10138        return true;
10139    }
10140    let mut current = specifier.parent();
10141    while let Some(ancestor) = current {
10142        match ancestor.kind() {
10143            "type_descriptor"
10144            | "parameter_declaration"
10145            | "optional_parameter_declaration"
10146            | "template_argument_list"
10147            | "cast_expression" => return false,
10148            "declaration" | "field_declaration" => {
10149                let mut cursor = ancestor.walk();
10150                return ancestor
10151                    .children_by_field_name("declarator", &mut cursor)
10152                    .next()
10153                    .is_none();
10154            }
10155            "translation_unit" => return true,
10156            _ => current = ancestor.parent(),
10157        }
10158    }
10159    false
10160}
10161
10162pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
10163    match node.kind() {
10164        "identifier"
10165        | "field_identifier"
10166        | "qualified_identifier"
10167        | "scoped_identifier"
10168        | "operator_name"
10169        | "destructor_name"
10170        | "literal_operator_name" => Some(node),
10171        "reference_declarator" | "parenthesized_declarator" => {
10172            node.named_child(0).and_then(declarator_name_node)
10173        }
10174        _ => node
10175            .child_by_field_name("declarator")
10176            .or_else(|| node.child_by_field_name("name"))
10177            .or_else(|| node.child_by_field_name("field"))
10178            .and_then(declarator_name_node),
10179    }
10180}
10181
10182fn declarator_name_path_contains(
10183    declarator: Node<'_>,
10184    candidate: Node<'_>,
10185    allow_type_identifier: bool,
10186) -> bool {
10187    let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
10188        return false;
10189    };
10190    let mut current = Some(declarator);
10191    while let Some(node) = current {
10192        if same_node(node, candidate) {
10193            return true;
10194        }
10195        if same_node(node, name) {
10196            return false;
10197        }
10198        current = node
10199            .child_by_field_name("declarator")
10200            .or_else(|| node.child_by_field_name("name"))
10201            .or_else(|| node.child_by_field_name("field"));
10202    }
10203    false
10204}
10205
10206fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
10207    match node.kind() {
10208        "identifier"
10209        | "field_identifier"
10210        | "operator_name"
10211        | "destructor_name"
10212        | "literal_operator_name" => Some(node),
10213        "type_identifier" if allow_type_identifier => Some(node),
10214        _ => node
10215            .child_by_field_name("declarator")
10216            .or_else(|| node.child_by_field_name("name"))
10217            .or_else(|| node.child_by_field_name("field"))
10218            .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
10219    }
10220}
10221
10222/// True when `node` is a component of a larger structured type node whose outer
10223/// range is the single reference surfaced to callers.
10224pub fn is_nested_type_node(node: Node<'_>) -> bool {
10225    node.parent().is_some_and(|parent| {
10226        matches!(
10227            parent.kind(),
10228            "qualified_identifier" | "scoped_type_identifier" | "template_type"
10229        )
10230    })
10231}
10232
10233pub struct OutOfLineMemberDefinitionOwners<'tree> {
10234    pub owners: Vec<(Node<'tree>, CodeUnit)>,
10235    innermost: Option<(Node<'tree>, CodeUnit)>,
10236}
10237
10238impl OutOfLineMemberDefinitionOwners<'_> {
10239    pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
10240        self.innermost.as_ref().map(|(node, owner)| (*node, owner))
10241    }
10242}
10243
10244pub struct QualifiedOwnerComponents<'tree> {
10245    pub nodes: Vec<Node<'tree>>,
10246    pub names: Vec<String>,
10247    pub global: bool,
10248}
10249
10250/// True when each structured qualifier on the callable-name path has a real
10251/// `::` token. A macro-prefixed return type can make tree-sitter insert a
10252/// zero-width missing separator and parse `TYPE Result<T> method()` as the
10253/// false qualified declarator `Result<T>::method`.
10254pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
10255    let mut stack = vec![node];
10256    let mut found_separator = false;
10257    while let Some(current) = stack.pop() {
10258        if !matches!(
10259            current.kind(),
10260            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
10261        ) {
10262            continue;
10263        }
10264        let mut current_has_separator = false;
10265        for index in 0..current.child_count() {
10266            let Some(child) = current.child(index) else {
10267                continue;
10268            };
10269            if child.kind() == "::" {
10270                if child.is_missing() {
10271                    return false;
10272                }
10273                current_has_separator = true;
10274                found_separator = true;
10275            }
10276        }
10277        if !current_has_separator {
10278            return false;
10279        }
10280        for field in ["scope", "name"] {
10281            if let Some(child) = current.child_by_field_name(field)
10282                && matches!(
10283                    child.kind(),
10284                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
10285                )
10286            {
10287                stack.push(child);
10288            }
10289        }
10290    }
10291    found_separator
10292}
10293
10294pub fn qualified_owner_components<'tree>(
10295    node: Node<'tree>,
10296    source: &str,
10297) -> Option<QualifiedOwnerComponents<'tree>> {
10298    if !qualified_name_has_concrete_scope_separators(node) {
10299        return None;
10300    }
10301    let mut nodes = cpp_name_component_nodes(node)?;
10302    nodes.pop()?;
10303    if nodes.is_empty() {
10304        return None;
10305    }
10306    let names = nodes
10307        .iter()
10308        .map(|component| node_text(*component, source).to_string())
10309        .collect();
10310    Some(QualifiedOwnerComponents {
10311        nodes,
10312        names,
10313        global: is_globally_qualified_cpp_name(node),
10314    })
10315}
10316
10317/// Return the terminal type-name occurrence in an out-of-line destructor
10318/// declarator such as `endpoint::~endpoint`.  Unlike an ordinary terminal
10319/// method name, this identifier is a second reference to the owner type.
10320///
10321/// Every extra qualifier nests another `qualified_identifier` in the `name`
10322/// field, so `zmq::pair_t::~pair_t` reaches the destructor only two levels
10323/// down. Reading one level dropped the terminal occurrence for every
10324/// file-scope out-of-line member libzmq writes (#1831).
10325pub fn out_of_line_destructor_type_reference(node: Node<'_>) -> Option<Node<'_>> {
10326    if node.kind() != "qualified_identifier" {
10327        return None;
10328    }
10329    let mut qualified = node;
10330    let destructor = loop {
10331        let name = qualified.child_by_field_name("name")?;
10332        match name.kind() {
10333            "qualified_identifier" => qualified = name,
10334            "destructor_name" => break name,
10335            _ => return None,
10336        }
10337    };
10338    (0..destructor.named_child_count())
10339        .filter_map(|index| destructor.named_child(index))
10340        .find(|child| matches!(child.kind(), "identifier" | "type_identifier"))
10341}
10342
10343pub fn out_of_line_member_definition_owner<'tree>(
10344    analyzer: &CppGraphSource<'_>,
10345    visibility: &VisibilityIndex<'_>,
10346    file: &ProjectFile,
10347    source: &str,
10348    node: Node<'tree>,
10349) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
10350    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
10351        || !has_ancestor_kind(node, "function_definition")
10352        || !is_function_declarator_name_root(node)
10353    {
10354        return None;
10355    }
10356    let qualified = qualified_owner_components(node, source)?;
10357    let lexical_scope = enclosing_namespace_components(node, source)?;
10358    let mut owners = Vec::new();
10359    let mut innermost = None;
10360
10361    for component_count in 1..=qualified.names.len() {
10362        if let LexicalTypeResolution::Resolved { unit, .. } = visibility
10363            .resolve_type_components_lexically(
10364                analyzer,
10365                file,
10366                &qualified.names[..component_count],
10367                qualified.global,
10368                &lexical_scope,
10369            )
10370            && !owners
10371                .iter()
10372                .any(|(_, existing)| same_visible_symbol(existing, &unit))
10373        {
10374            if component_count == qualified.names.len() {
10375                innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
10376            }
10377            owners.push((qualified.nodes[component_count - 1], unit));
10378        }
10379    }
10380
10381    // The C++ analyzer has already reconciled an indexed out-of-line callable
10382    // against the include-visible class table. Consult that canonical owner
10383    // chain only when ordinary lexical lookup could not recover the innermost
10384    // owner.  A one-segment qualifier is safe here only when the enclosing
10385    // indexed callable has an authoritative class owner and the parser's
10386    // namespace path is a (possibly sparse) subsequence of that owner path.
10387    // The latter is what lets macro-wrapped namespace sentinels recover a
10388    // missing `time_internal`/`cord_internal` component without guessing an
10389    // unrelated short name.
10390    if innermost.is_none() {
10391        let indexed_owner_components = visibility
10392            .indexed_enclosing_owner_scope(analyzer, file, node)
10393            .or_else(|| {
10394                // Retain the legacy rendered-name fallback for the existing
10395                // multi-segment path when an enclosing owner chain is not
10396                // available (for example, cache-loaded units without parent
10397                // links).  One-segment recovery must stay canonical-only.
10398                if qualified.names.len() <= 1 {
10399                    return None;
10400                }
10401                let range = Range {
10402                    start_byte: node.start_byte(),
10403                    end_byte: node.end_byte(),
10404                    start_line: node.start_position().row,
10405                    end_line: node.end_position().row,
10406                };
10407                let start = analyzer.enclosing_code_unit(file, &range)?;
10408                let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10409                    brokk_bifrost_core::analyzer::Language::Cpp,
10410                    &cpp_name_for(&start),
10411                );
10412                components.pop();
10413                Some(components)
10414            });
10415        if let Some(indexed_owner_components) = indexed_owner_components
10416            && indexed_owner_components.len() > qualified.names.len()
10417            && indexed_owner_components.ends_with(&qualified.names)
10418            && indexed_namespace_path_is_recoverable(
10419                &lexical_scope,
10420                &indexed_owner_components,
10421                qualified.names.len(),
10422            )
10423            // A globally-qualified one-segment owner is an explicit request
10424            // for the top-level binding; do not reinterpret it as a missing
10425            // namespace component.  Existing multi-segment global lookups
10426            // retain their historical indexed recovery.
10427            && (qualified.names.len() > 1 || !qualified.global)
10428        {
10429            let namespace_count = indexed_owner_components.len() - qualified.names.len();
10430            for component_count in 1..=qualified.names.len() {
10431                let expected = &indexed_owner_components[..namespace_count + component_count];
10432                let owner_node = qualified.nodes[component_count - 1];
10433                for owner in visibility
10434                    .visible_identifier_candidates(file, &qualified.names[component_count - 1])
10435                    .filter(|candidate| candidate.is_class())
10436                    .filter(|candidate| {
10437                        canonical_cpp_scope_components(candidate) == expected
10438                            && visibility.external_type_candidate_visible_in_context(
10439                                analyzer, file, candidate, node,
10440                            )
10441                    })
10442                {
10443                    if component_count == qualified.names.len() && innermost.is_none() {
10444                        innermost = Some((owner_node, owner.clone()));
10445                    }
10446                    if !owners
10447                        .iter()
10448                        .any(|(_, existing)| same_symbol(existing, owner))
10449                    {
10450                        owners.push((owner_node, owner.clone()));
10451                    }
10452                }
10453            }
10454        }
10455    }
10456    (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
10457}
10458
10459fn is_function_declarator_name_root(node: Node<'_>) -> bool {
10460    let mut current = node;
10461    while let Some(parent) = current.parent() {
10462        if parent.kind() == "function_declarator" {
10463            return parent.child_by_field_name("declarator") == Some(current);
10464        }
10465        if matches!(
10466            parent.kind(),
10467            "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
10468        ) && parent.child_by_field_name("declarator") == Some(current)
10469        {
10470            current = parent;
10471            continue;
10472        }
10473        return false;
10474    }
10475    false
10476}
10477
10478pub fn append_cpp_name_components(
10479    node: Node<'_>,
10480    source: &str,
10481    out: &mut Vec<String>,
10482) -> Option<()> {
10483    out.extend(
10484        cpp_name_component_nodes(node)?
10485            .into_iter()
10486            .map(|component| node_text(component, source).to_string()),
10487    );
10488    Some(())
10489}
10490
10491pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
10492    let mut components = Vec::new();
10493    append_cpp_name_components(node, source, &mut components)?;
10494    Some(components)
10495}
10496
10497/// The base scopes named by member using-declarations for `member` in one
10498/// class source range.
10499///
10500/// The grammar supplies the qualified identifier and each component. Keep
10501/// this interpretation shared between forward overload lookup and inverse
10502/// owner routing rather than reparsing a rendered `Base::member` string at
10503/// either call site.
10504pub fn cpp_member_using_declaration_scopes(source: &str, member: &str) -> Vec<String> {
10505    let mut parser = Parser::new();
10506    if parser
10507        .set_language(&tree_sitter_cpp::LANGUAGE.into())
10508        .is_err()
10509    {
10510        return Vec::new();
10511    }
10512    let Some(tree) = parser.parse(source, None) else {
10513        return Vec::new();
10514    };
10515    let mut scopes = Vec::new();
10516    let mut pending = vec![tree.root_node()];
10517    while let Some(node) = pending.pop() {
10518        if node.kind() == "using_declaration" {
10519            let Some(imported) = node.named_child(0) else {
10520                continue;
10521            };
10522            let Some(mut components) = cpp_type_name_components(imported, source) else {
10523                continue;
10524            };
10525            if components.pop().as_deref() == Some(member) && !components.is_empty() {
10526                scopes.push(components.join("::"));
10527            }
10528            continue;
10529        }
10530        for index in (0..node.named_child_count()).rev() {
10531            if let Some(child) = node.named_child(index) {
10532                pending.push(child);
10533            }
10534        }
10535    }
10536    scopes
10537}
10538
10539/// Whether a structured using-declaration scope can name `qualified` as an
10540/// ancestor class. The boundary check prevents `Base` from matching
10541/// `OtherBase` while allowing a relative `Base` spelling to match `ns::Base`.
10542pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
10543    qualified == scope
10544        || qualified
10545            .strip_suffix(scope)
10546            .is_some_and(|prefix| prefix.ends_with("::"))
10547}
10548
10549/// Whether `node` is the direct structured type payload of a template
10550/// argument. This role remains meaningful even when a surrounding expression
10551/// is below tree-sitter recovery, because both the `template_argument_list`
10552/// and the `type_descriptor` retain their named fields.
10553pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
10554    let Some(type_descriptor) = node.parent() else {
10555        return false;
10556    };
10557    if type_descriptor.kind() != "type_descriptor"
10558        || type_descriptor.child_by_field_name("type") != Some(node)
10559    {
10560        return false;
10561    }
10562    let Some(arguments) = type_descriptor.parent() else {
10563        return false;
10564    };
10565    if arguments.kind() != "template_argument_list" {
10566        return false;
10567    }
10568    arguments.parent().is_some_and(|parent| {
10569        matches!(parent.kind(), "template_type" | "template_function")
10570            && parent.child_by_field_name("arguments") == Some(arguments)
10571    })
10572}
10573
10574pub fn cpp_template_reference_arguments(
10575    mut node: Node<'_>,
10576    source: &str,
10577) -> Option<Vec<CppTemplateExpression>> {
10578    loop {
10579        match node.kind() {
10580            "template_type" | "template_function" => {
10581                let arguments = node.child_by_field_name("arguments")?;
10582                let mut cursor = arguments.walk();
10583                return Some(
10584                    arguments
10585                        .named_children(&mut cursor)
10586                        .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
10587                        .map(|argument| CppTemplateExpression {
10588                            text: normalize_cpp_whitespace(node_text(argument, source)),
10589                            term: cpp_template_term(argument, source, &[]),
10590                        })
10591                        .collect(),
10592                );
10593            }
10594            "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
10595                node = node
10596                    .child_by_field_name("name")
10597                    .or_else(|| node.child_by_field_name("type"))?;
10598            }
10599            _ => return None,
10600        }
10601    }
10602}
10603
10604fn cpp_reconcile_primary_template_parameters(
10605    candidates: &[(&CodeUnit, &CppTemplateMetadata)],
10606    preferred: &CodeUnit,
10607) -> Option<Vec<CppTemplateParameterMetadata>> {
10608    let canonical = candidates
10609        .iter()
10610        .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
10611    let mut merged = canonical
10612        .parameters
10613        .iter()
10614        .map(|parameter| CppTemplateParameterMetadata {
10615            name: parameter.name.clone(),
10616            kind: parameter.kind,
10617            variadic: parameter.variadic,
10618            default: None,
10619        })
10620        .collect::<Vec<_>>();
10621
10622    for (_, metadata) in candidates {
10623        if metadata.parameters.len() != merged.len() {
10624            return None;
10625        }
10626        let rename_bindings = metadata
10627            .parameters
10628            .iter()
10629            .zip(&merged)
10630            .map(|(parameter, canonical)| {
10631                (
10632                    parameter.name.clone(),
10633                    CppTemplateTerm::Parameter(canonical.name.clone()),
10634                )
10635            })
10636            .collect::<HashMap<_, _>>();
10637        for ((parameter, canonical), merged_parameter) in metadata
10638            .parameters
10639            .iter()
10640            .zip(&canonical.parameters)
10641            .zip(&mut merged)
10642        {
10643            if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
10644                return None;
10645            }
10646            let Some(default) = &parameter.default else {
10647                continue;
10648            };
10649            let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
10650            if let Some(existing) = &merged_parameter.default {
10651                if !cpp_template_terms_equal(&existing.term, &normalized_term) {
10652                    return None;
10653                }
10654            } else {
10655                merged_parameter.default = Some(CppTemplateExpression {
10656                    text: default.text.clone(),
10657                    term: normalized_term,
10658                });
10659            }
10660        }
10661    }
10662    Some(merged)
10663}
10664
10665pub fn cpp_bind_template_arguments(
10666    parameters: &[CppTemplateParameterMetadata],
10667    explicit_arguments: &[CppTemplateExpression],
10668) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
10669    let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
10670    if variadic_index.is_some_and(|index| {
10671        index + 1 != parameters.len()
10672            || parameters[index + 1..]
10673                .iter()
10674                .any(|parameter| parameter.variadic)
10675    }) {
10676        return None;
10677    }
10678    let fixed_count = variadic_index.unwrap_or(parameters.len());
10679    if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
10680        return None;
10681    }
10682    let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
10683    let mut expanded = explicit_arguments[..explicit_fixed_count]
10684        .iter()
10685        .map(cpp_clone_template_expression_iterative)
10686        .collect::<Vec<_>>();
10687    let mut bindings = HashMap::default();
10688    for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
10689        bindings.insert(
10690            parameter.name.clone(),
10691            cpp_clone_template_term_iterative(&argument.term),
10692        );
10693    }
10694    for parameter in &parameters[explicit_fixed_count..fixed_count] {
10695        let default = parameter.default.as_ref()?;
10696        let term = cpp_substitute_template_term(&default.term, &bindings)?;
10697        bindings.insert(parameter.name.clone(), term.clone());
10698        expanded.push(CppTemplateExpression {
10699            text: default.text.clone(),
10700            term,
10701        });
10702    }
10703    if let Some(index) = variadic_index {
10704        let packed_arguments = &explicit_arguments[explicit_fixed_count..];
10705        expanded.extend(
10706            packed_arguments
10707                .iter()
10708                .map(cpp_clone_template_expression_iterative),
10709        );
10710        bindings.insert(
10711            parameters[index].name.clone(),
10712            CppTemplateTerm::Node {
10713                kind: "parameter_pack".to_string(),
10714                children: packed_arguments
10715                    .iter()
10716                    .map(|argument| cpp_clone_template_term_iterative(&argument.term))
10717                    .collect(),
10718            },
10719        );
10720    }
10721    Some((expanded, bindings))
10722}
10723
10724fn cpp_specialization_matches(
10725    metadata: &CppTemplateMetadata,
10726    arguments: &[CppTemplateExpression],
10727) -> bool {
10728    if metadata.specialization_arguments.len() != arguments.len() {
10729        return false;
10730    }
10731    let parameter_names = metadata
10732        .parameters
10733        .iter()
10734        .map(|parameter| parameter.name.as_str())
10735        .collect::<HashSet<_>>();
10736    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
10737    for (pattern, argument) in metadata.specialization_arguments.iter().zip(arguments) {
10738        if !cpp_unify_template_term(
10739            &pattern.term,
10740            &argument.term,
10741            &parameter_names,
10742            &mut bindings,
10743        ) {
10744            return false;
10745        }
10746    }
10747    true
10748}
10749
10750fn cpp_specialization_more_specialized(
10751    candidate: &CppTemplateMetadata,
10752    other: &CppTemplateMetadata,
10753) -> bool {
10754    cpp_specialization_pattern_accepts(other, candidate)
10755        && !cpp_specialization_pattern_accepts(candidate, other)
10756}
10757
10758fn cpp_specialization_pattern_accepts(
10759    broader: &CppTemplateMetadata,
10760    narrower: &CppTemplateMetadata,
10761) -> bool {
10762    if broader.specialization_arguments.len() != narrower.specialization_arguments.len() {
10763        return false;
10764    }
10765    let parameter_names = broader
10766        .parameters
10767        .iter()
10768        .map(|parameter| parameter.name.as_str())
10769        .collect::<HashSet<_>>();
10770    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
10771    broader
10772        .specialization_arguments
10773        .iter()
10774        .zip(&narrower.specialization_arguments)
10775        .all(|(pattern, argument)| {
10776            cpp_unify_template_term(
10777                &pattern.term,
10778                &argument.term,
10779                &parameter_names,
10780                &mut bindings,
10781            )
10782        })
10783}
10784
10785pub fn cpp_substitute_template_term(
10786    term: &CppTemplateTerm,
10787    bindings: &HashMap<String, CppTemplateTerm>,
10788) -> Option<CppTemplateTerm> {
10789    enum Work<'a> {
10790        Visit(&'a CppTemplateTerm),
10791        Build { kind: String, child_count: usize },
10792    }
10793
10794    let mut work = vec![Work::Visit(term)];
10795    let mut substituted = Vec::new();
10796    while let Some(next) = work.pop() {
10797        match next {
10798            Work::Visit(CppTemplateTerm::Parameter(name)) => {
10799                substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
10800            }
10801            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
10802                substituted.push(CppTemplateTerm::Atom {
10803                    kind: kind.clone(),
10804                    text: text.clone(),
10805                });
10806            }
10807            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
10808                work.push(Work::Build {
10809                    kind: kind.clone(),
10810                    child_count: children.len(),
10811                });
10812                work.extend(children.iter().rev().map(Work::Visit));
10813            }
10814            Work::Build { kind, child_count } => {
10815                let children = substituted.split_off(substituted.len() - child_count);
10816                substituted.push(CppTemplateTerm::Node { kind, children });
10817            }
10818        }
10819    }
10820    substituted.pop()
10821}
10822
10823pub fn cpp_substitute_template_arguments(
10824    arguments: &[CppTemplateExpression],
10825    bindings: &HashMap<String, CppTemplateTerm>,
10826) -> Option<Vec<CppTemplateExpression>> {
10827    let mut substituted = Vec::new();
10828    for argument in arguments {
10829        let CppTemplateTerm::Node { kind, children } = &argument.term else {
10830            substituted.push(CppTemplateExpression {
10831                text: argument.text.clone(),
10832                term: cpp_substitute_template_term(&argument.term, bindings)?,
10833            });
10834            continue;
10835        };
10836        if kind != "parameter_pack_expansion" {
10837            substituted.push(CppTemplateExpression {
10838                text: argument.text.clone(),
10839                term: cpp_substitute_template_term(&argument.term, bindings)?,
10840            });
10841            continue;
10842        }
10843        let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
10844            return None;
10845        };
10846        if ellipsis != "..." {
10847            return None;
10848        }
10849
10850        let mut pack_names = Vec::new();
10851        let mut work = vec![pattern];
10852        while let Some(term) = work.pop() {
10853            match term {
10854                CppTemplateTerm::Parameter(name)
10855                    if matches!(
10856                        bindings.get(name),
10857                        Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
10858                    ) =>
10859                {
10860                    if !pack_names.contains(name) {
10861                        pack_names.push(name.clone());
10862                    }
10863                }
10864                CppTemplateTerm::Node { children, .. } => work.extend(children),
10865                CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
10866            }
10867        }
10868        let first_pack = pack_names.first()?;
10869        let CppTemplateTerm::Node {
10870            children: first_elements,
10871            ..
10872        } = bindings.get(first_pack)?
10873        else {
10874            return None;
10875        };
10876        let pack_len = first_elements.len();
10877        for pack_name in &pack_names {
10878            let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
10879                return None;
10880            };
10881            if children.len() != pack_len {
10882                return None;
10883            }
10884        }
10885        for index in 0..pack_len {
10886            let mut element_bindings = bindings.clone();
10887            for pack_name in &pack_names {
10888                let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
10889                    return None;
10890                };
10891                element_bindings.insert(
10892                    pack_name.clone(),
10893                    cpp_clone_template_term_iterative(&children[index]),
10894                );
10895            }
10896            substituted.push(CppTemplateExpression {
10897                text: argument.text.clone(),
10898                term: cpp_substitute_template_term(pattern, &element_bindings)?,
10899            });
10900        }
10901    }
10902    Some(substituted)
10903}
10904
10905fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
10906    enum Work<'a> {
10907        Visit(&'a CppTemplateTerm),
10908        Build { kind: String, child_count: usize },
10909    }
10910
10911    let mut work = vec![Work::Visit(term)];
10912    let mut cloned = Vec::new();
10913    while let Some(next) = work.pop() {
10914        match next {
10915            Work::Visit(CppTemplateTerm::Parameter(name)) => {
10916                cloned.push(CppTemplateTerm::Parameter(name.clone()));
10917            }
10918            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
10919                cloned.push(CppTemplateTerm::Atom {
10920                    kind: kind.clone(),
10921                    text: text.clone(),
10922                });
10923            }
10924            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
10925                work.push(Work::Build {
10926                    kind: kind.clone(),
10927                    child_count: children.len(),
10928                });
10929                work.extend(children.iter().rev().map(Work::Visit));
10930            }
10931            Work::Build { kind, child_count } => {
10932                let children = cloned.split_off(cloned.len() - child_count);
10933                cloned.push(CppTemplateTerm::Node { kind, children });
10934            }
10935        }
10936    }
10937    cloned
10938        .pop()
10939        .expect("template term traversal emits one root")
10940}
10941
10942fn cpp_clone_template_expression_iterative(
10943    expression: &CppTemplateExpression,
10944) -> CppTemplateExpression {
10945    CppTemplateExpression {
10946        text: expression.text.clone(),
10947        term: cpp_clone_template_term_iterative(&expression.term),
10948    }
10949}
10950
10951pub fn cpp_unify_template_term(
10952    pattern: &CppTemplateTerm,
10953    argument: &CppTemplateTerm,
10954    parameters: &HashSet<&str>,
10955    bindings: &mut HashMap<String, CppTemplateTerm>,
10956) -> bool {
10957    let mut work = vec![(pattern, argument)];
10958    while let Some((pattern, argument)) = work.pop() {
10959        match pattern {
10960            CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
10961                if let Some(bound) = bindings.get(name) {
10962                    if !cpp_template_terms_equal(bound, argument) {
10963                        return false;
10964                    }
10965                } else {
10966                    bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
10967                }
10968            }
10969            CppTemplateTerm::Atom {
10970                kind: pattern_kind,
10971                text: pattern_text,
10972            } => {
10973                if !matches!(
10974                    argument,
10975                    CppTemplateTerm::Atom { kind, text }
10976                        if kind == pattern_kind && text == pattern_text
10977                ) {
10978                    return false;
10979                }
10980            }
10981            CppTemplateTerm::Node {
10982                kind: pattern_kind,
10983                children: pattern_children,
10984            } => {
10985                let CppTemplateTerm::Node { kind, children } = argument else {
10986                    return false;
10987                };
10988                if kind != pattern_kind || children.len() != pattern_children.len() {
10989                    return false;
10990                }
10991                work.extend(pattern_children.iter().zip(children).rev());
10992            }
10993            CppTemplateTerm::Parameter(_) => return false,
10994        }
10995    }
10996    true
10997}
10998
10999fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
11000    let mut work = vec![(left, right)];
11001    while let Some((left, right)) = work.pop() {
11002        match (left, right) {
11003            (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
11004                if left != right {
11005                    return false;
11006                }
11007            }
11008            (
11009                CppTemplateTerm::Atom {
11010                    kind: left_kind,
11011                    text: left_text,
11012                },
11013                CppTemplateTerm::Atom {
11014                    kind: right_kind,
11015                    text: right_text,
11016                },
11017            ) => {
11018                if left_kind != right_kind || left_text != right_text {
11019                    return false;
11020                }
11021            }
11022            (
11023                CppTemplateTerm::Node {
11024                    kind: left_kind,
11025                    children: left_children,
11026                },
11027                CppTemplateTerm::Node {
11028                    kind: right_kind,
11029                    children: right_children,
11030                },
11031            ) => {
11032                if left_kind != right_kind || left_children.len() != right_children.len() {
11033                    return false;
11034                }
11035                work.extend(left_children.iter().zip(right_children).rev());
11036            }
11037            _ => return false,
11038        }
11039    }
11040    true
11041}
11042
11043pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
11044    let mut components = Vec::new();
11045    let mut stack = vec![node];
11046    while let Some(current) = stack.pop() {
11047        match current.kind() {
11048            "identifier"
11049            | "field_identifier"
11050            | "namespace_identifier"
11051            | "type_identifier"
11052            | "operator_name"
11053            | "destructor_name" => components.push(current),
11054            "template_type" | "template_function" => {
11055                stack.push(current.child_by_field_name("name")?);
11056            }
11057            "dependent_name" => stack.push(current.named_child(0)?),
11058            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
11059                stack.push(current.child_by_field_name("name")?);
11060                if let Some(scope) = current.child_by_field_name("scope") {
11061                    stack.push(scope);
11062                }
11063            }
11064            "nested_namespace_specifier" => {
11065                for index in (0..current.named_child_count()).rev() {
11066                    stack.push(current.named_child(index)?);
11067                }
11068            }
11069            _ => return None,
11070        }
11071    }
11072    Some(components)
11073}
11074
11075pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
11076    node.child_by_field_name("scope").is_none()
11077        && node.child(0).is_some_and(|child| child.kind() == "::")
11078}
11079
11080fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
11081    let mut namespaces = Vec::new();
11082    let mut current = node.parent();
11083    while let Some(parent) = current {
11084        if parent.kind() == "namespace_definition"
11085            && let Some(name) = parent.child_by_field_name("name")
11086        {
11087            let mut components = Vec::new();
11088            append_cpp_name_components(name, source, &mut components)?;
11089            namespaces.push(components);
11090        }
11091        current = parent.parent();
11092    }
11093    namespaces.reverse();
11094    Some(namespaces.into_iter().flatten().collect())
11095}
11096
11097/// Whether a parser-derived namespace path can be reconciled with an indexed
11098/// owner scope without inventing an unrelated short-name binding.
11099///
11100/// Macro namespace sentinels can make tree-sitter omit one or more namespace
11101/// definitions from the ancestor chain. Preserve the order of every namespace
11102/// that did survive parsing, but allow indexed components between them. An
11103/// empty path is accepted only when the declarator itself supplies a nested
11104/// owner suffix such as `Outer::Inner`: together with the indexed enclosing
11105/// owner chain, that suffix is structural evidence that a namespace was lost.
11106/// A one-segment owner at the translation-unit root remains insufficient.
11107fn indexed_namespace_path_is_recoverable(
11108    lexical_scope: &[String],
11109    indexed_owner_scope: &[String],
11110    explicit_owner_component_count: usize,
11111) -> bool {
11112    if lexical_scope.is_empty() {
11113        return explicit_owner_component_count > 1;
11114    }
11115    if lexical_scope.len() >= indexed_owner_scope.len() {
11116        return false;
11117    }
11118    let mut indexed = indexed_owner_scope.iter();
11119    lexical_scope
11120        .iter()
11121        .all(|component| indexed.any(|candidate| candidate == component))
11122}
11123
11124pub fn has_ancestor_kind(node: Node<'_>, kind: &str) -> bool {
11125    let mut current = node.parent();
11126    while let Some(parent) = current {
11127        if parent.kind() == kind {
11128            return true;
11129        }
11130        current = parent.parent();
11131    }
11132    false
11133}
11134
11135/// Return the terminal identifier represented by a callable or type callee.
11136///
11137/// Qualified, scoped, template, and field wrappers are traversed through their
11138/// grammar fields so both function calls and type constructions emit the token
11139/// that names the referenced declaration.
11140pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
11141    loop {
11142        let next = match node.kind() {
11143            "qualified_identifier"
11144            | "scoped_identifier"
11145            | "template_method"
11146            | "template_function"
11147            | "template_type" => node.child_by_field_name("name"),
11148            "field_expression" => node.child_by_field_name("field"),
11149            _ => None,
11150        };
11151        let Some(next) = next else {
11152            return node;
11153        };
11154        node = next;
11155    }
11156}
11157
11158#[derive(Clone, Copy)]
11159pub struct RecoveredRelationalTemplateMemberCall<'tree> {
11160    pub receiver: Node<'tree>,
11161    pub member: Node<'tree>,
11162    pub arity: usize,
11163}
11164
11165/// Recover `receiver.member<argument>(call_arguments)` when tree-sitter chose
11166/// nested relational expressions instead of a `template_method` call.
11167///
11168/// The recovery uses only grammar fields: the selected field must be the left
11169/// side of `<`, that expression must be the left side of `>`, and the right
11170/// side of `>` must be the parenthesized call arguments. Semantic callers must
11171/// additionally prove the receiver owner and the member's template status.
11172pub fn recovered_relational_template_member_call(
11173    field: Node<'_>,
11174) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
11175    if field.kind() != "field_expression" {
11176        return None;
11177    }
11178    let receiver = field
11179        .child_by_field_name("argument")
11180        .or_else(|| field.child_by_field_name("object"))?;
11181    let member = field.child_by_field_name("field")?;
11182    let less = field.parent()?;
11183    if less.kind() != "binary_expression"
11184        || less.child_by_field_name("left") != Some(field)
11185        || less
11186            .child_by_field_name("operator")
11187            .is_none_or(|operator| operator.kind() != "<")
11188        || less.child_by_field_name("right").is_none()
11189    {
11190        return None;
11191    }
11192    let greater = less.parent()?;
11193    if greater.kind() != "binary_expression"
11194        || greater.child_by_field_name("left") != Some(less)
11195        || greater
11196            .child_by_field_name("operator")
11197            .is_none_or(|operator| operator.kind() != ">")
11198    {
11199        return None;
11200    }
11201    let arguments = greater.child_by_field_name("right")?;
11202    if arguments.kind() != "parenthesized_expression" {
11203        return None;
11204    }
11205    let arity = parenthesized_call_argument_arity(arguments)?;
11206    Some(RecoveredRelationalTemplateMemberCall {
11207        receiver,
11208        member,
11209        arity,
11210    })
11211}
11212
11213fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
11214    let expression = arguments.named_child(0)?;
11215    if expression.kind() != "comma_expression" {
11216        return Some(1);
11217    }
11218    let mut arity = 0usize;
11219    let mut stack = vec![expression];
11220    while let Some(node) = stack.pop() {
11221        if node.kind() == "comma_expression" {
11222            stack.push(node.child_by_field_name("right")?);
11223            stack.push(node.child_by_field_name("left")?);
11224        } else {
11225            arity += 1;
11226        }
11227    }
11228    Some(arity)
11229}
11230
11231/// Whether `node` is part of a call's callee expression, walking only through
11232/// the grammar wrappers that can structurally contain that callee.
11233pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
11234    while let Some(parent) = node.parent() {
11235        match parent.kind() {
11236            "call_expression" => {
11237                return parent
11238                    .child_by_field_name("function")
11239                    .or_else(|| parent.named_child(0))
11240                    == Some(node);
11241            }
11242            "qualified_identifier"
11243            | "scoped_identifier"
11244            | "template_function"
11245            | "template_type"
11246            | "field_expression" => node = parent,
11247            _ => return false,
11248        }
11249    }
11250    false
11251}
11252
11253pub fn type_reference_hit_node(node: Node<'_>) -> Node<'_> {
11254    if is_call_callee_node(node) {
11255        function_terminal_node(node)
11256    } else {
11257        node
11258    }
11259}
11260
11261pub fn normalize_type_text(value: &str) -> String {
11262    strip_tag_type_prefix(
11263        normalize_cpp_whitespace(value)
11264            .trim_start_matches("const ")
11265            .trim_end_matches('*')
11266            .trim_end_matches('&')
11267            .trim(),
11268    )
11269    .to_string()
11270}
11271
11272fn strip_tag_type_prefix(value: &str) -> &str {
11273    let value = value.trim_start_matches("const ");
11274    value
11275        .strip_prefix("struct ")
11276        .or_else(|| value.strip_prefix("class "))
11277        .or_else(|| value.strip_prefix("enum "))
11278        .unwrap_or(value)
11279        .trim()
11280}
11281
11282pub fn normalize_reference_name(value: &str) -> Option<String> {
11283    let normalized = normalize_cpp_reference_text(value);
11284    (!normalized.is_empty()).then_some(normalized)
11285}
11286
11287pub fn normalize_cpp_reference_text(value: &str) -> String {
11288    let mut text = normalize_cpp_whitespace(value)
11289        .trim_start_matches("new ")
11290        .trim()
11291        .to_string();
11292    if let Some(index) = text.find(['(', '{']) {
11293        text.truncate(index);
11294    }
11295    if let Some(index) = text.find('<') {
11296        text.truncate(index);
11297    }
11298    let normalized = text
11299        .trim()
11300        .trim_start_matches("const ")
11301        .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
11302        .trim_matches(':')
11303        .trim();
11304    strip_tag_type_prefix(normalized).to_string()
11305}
11306
11307pub fn cpp_name_for(unit: &CodeUnit) -> String {
11308    let short = unit.short_name().replace(['.', '$'], "::");
11309    if unit.package_name().is_empty() {
11310        short
11311    } else {
11312        format!("{}::{}", unit.package_name(), short)
11313    }
11314}
11315
11316/// Render an indexed C++ qualified name from its authoritative FqName
11317/// segments. Unlike the legacy `cpp_name_for` renderer, this preserves dots
11318/// that belong to a template argument (for example `Args...`).
11319fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
11320    let fq = unit.fq();
11321    if fq.is_empty() {
11322        return None;
11323    }
11324    let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
11325    Some(
11326        fq.segments()
11327            .iter()
11328            .map(|&segment| interner.resolve(segment).0)
11329            .collect::<Vec<_>>()
11330            .join("::"),
11331    )
11332}
11333
11334fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
11335    canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
11336        || unit.fq().is_empty() && cpp_name_for(unit) == expected
11337}
11338
11339/// Return the indexed C++ owner scope without reparsing its rendered name.
11340///
11341/// Template spellings are opaque within an indexed `FqName` segment.  In
11342/// particular, the ellipsis in a parameter pack (`Args...`) is part of the
11343/// `AtomicHook<...>` type segment; feeding the legacy all-`::` rendering back
11344/// through `parse_symbol_path` would mistake those dots for component
11345/// separators.  Cache-loaded/legacy units may still have an empty structured
11346/// name, so retain the parser only as that explicit fallback.
11347pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
11348    let fq = unit.fq();
11349    if !fq.is_empty() {
11350        let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
11351        let scope = fq
11352            .segments()
11353            .iter()
11354            .filter_map(|&segment| {
11355                let (text, kind) = interner.resolve(segment);
11356                matches!(
11357                    kind,
11358                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
11359                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
11360                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
11361                )
11362                .then(|| text.to_string())
11363            })
11364            .collect();
11365        return scope;
11366    }
11367    brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11368        brokk_bifrost_core::analyzer::Language::Cpp,
11369        &cpp_name_for(unit),
11370    )
11371}
11372
11373// fqname-M4: the second stage splits on the individual chars '.', '-', '>'
11374// (not the substring "->"), which deliberately reduces an `operator->`-style
11375// terminal segment to an empty tail rather than keeping it intact; the shared
11376// structured splitter's cpp operator-token merge would keep `operator->`
11377// whole instead, changing this function's result — `name_matches_callable`'s
11378// `expected.starts_with("operator")` fallback exists specifically to
11379// compensate for that reduction, and a pinned regression test
11380// (`operator-> must not be reduced with terminal_name-style punctuation
11381// splitting`) asserts today's char-class behavior. Not equivalence-provable;
11382// revisit alongside that pinned test if it is ever relaxed.
11383pub fn terminal_name(value: &str) -> &str {
11384    value
11385        .rsplit("::")
11386        .next()
11387        .unwrap_or(value)
11388        .rsplit(['.', '-', '>'])
11389        .next()
11390        .unwrap_or(value)
11391        .trim()
11392}
11393
11394pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
11395    terminal_name(&normalize_cpp_reference_text(value)) == expected
11396}
11397
11398pub fn name_matches_callable(value: &str, expected: &str) -> bool {
11399    name_matches_terminal(value, expected)
11400        || expected.starts_with("operator")
11401            && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
11402}
11403
11404pub fn name_mentions(value: &str, expected: &str) -> bool {
11405    normalize_cpp_reference_text(value)
11406        .split("::")
11407        .any(|part| part == expected)
11408}
11409
11410pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
11411    let cpp_name = cpp_name_for(unit);
11412    if reference.contains("::") {
11413        return reference == cpp_name;
11414    }
11415    reference == cpp_name
11416        || terminal_name(reference) == unit.identifier()
11417            && (unit.package_name().is_empty() || reference == unit.identifier())
11418}
11419
11420pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
11421    match kind {
11422        TargetKind::Type
11423        | TargetKind::Constructor
11424        | TargetKind::Method
11425        | TargetKind::MemberField => true,
11426        TargetKind::FreeFunction => unit.is_function(),
11427        TargetKind::GlobalField => unit.is_field(),
11428        TargetKind::Macro => unit.is_macro(),
11429    }
11430}
11431
11432pub fn is_type_alias(unit: &CodeUnit) -> bool {
11433    unit.kind() == CodeUnitType::Field
11434        && unit.signature().is_some_and(|signature| {
11435            signature.starts_with("typedef ") || signature.starts_with("using ")
11436        })
11437}
11438
11439fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
11440    let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
11441    let target_name = cpp_name_for(target);
11442    if normalized.contains("::") {
11443        return normalized == target_name;
11444    }
11445    if let Some(namespace) = alias.namespace.as_deref() {
11446        return namespace_prefixes(namespace)
11447            .into_iter()
11448            .any(|prefix| format!("{prefix}::{normalized}") == target_name);
11449    }
11450    target.package_name().is_empty() && normalized == target.identifier()
11451}
11452
11453fn parser_alias_target_names(alias: &CppAlias) -> Vec<String> {
11454    let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
11455    if normalized.contains("::") {
11456        return vec![normalized];
11457    }
11458    alias
11459        .namespace
11460        .as_deref()
11461        .map(namespace_prefixes)
11462        .map(|prefixes| {
11463            prefixes
11464                .into_iter()
11465                .map(|prefix| format!("{prefix}::{normalized}"))
11466                .collect()
11467        })
11468        .unwrap_or_else(|| vec![normalized])
11469}
11470
11471/// The declared return type text of a C++ function unit, with leading declaration specifiers
11472/// stripped, e.g. `T*` for `T* operator->()`.
11473pub fn cpp_function_return_type_text(
11474    analyzer: &CppGraphSource<'_>,
11475    function: &CodeUnit,
11476) -> Option<String> {
11477    let metadata = analyzer.signature_metadata(function);
11478    if !metadata.is_empty() {
11479        let first = metadata.first()?.return_type_text()?;
11480        return metadata
11481            .iter()
11482            .all(|metadata| metadata.return_type_text() == Some(first))
11483            .then(|| first.to_string());
11484    }
11485    let signature = cpp_function_signature_text(analyzer, function)?;
11486    cpp_function_return_type_text_from_signature(&signature)
11487}
11488
11489fn cpp_function_signature_text(
11490    analyzer: &CppGraphSource<'_>,
11491    function: &CodeUnit,
11492) -> Option<String> {
11493    function
11494        .signature()
11495        .filter(|signature| signature.contains(function.identifier()))
11496        .map(str::to_string)
11497        .or_else(|| analyzer.signatures(function).first().cloned())
11498        .or_else(|| analyzer.get_source(function, false))
11499}
11500
11501fn cpp_function_return_type_text_from_signature(signature: &str) -> Option<String> {
11502    let open = signature.find('(')?;
11503    let name_at = cpp_function_name_start(signature, open)?;
11504    if let Some(return_type) = cpp_trailing_return_type(&signature[name_at..]) {
11505        return Some(return_type);
11506    }
11507    let type_text = cpp_strip_leading_template_clause(&signature[..name_at])
11508        .split_whitespace()
11509        .filter(|token| {
11510            !matches!(
11511                *token,
11512                "static" | "virtual" | "inline" | "constexpr" | "explicit" | "friend"
11513            )
11514        })
11515        .collect::<Vec<_>>()
11516        .join(" ");
11517    let type_text = type_text.trim();
11518    (!type_text.is_empty()).then(|| type_text.to_string())
11519}
11520
11521fn cpp_function_name_start(signature: &str, open: usize) -> Option<usize> {
11522    let before_parameters = &signature[..open];
11523    if let Some(operator_at) = before_parameters.rfind("operator") {
11524        let boundary = operator_at == 0
11525            || before_parameters[..operator_at]
11526                .chars()
11527                .next_back()
11528                .is_some_and(|ch| !(ch == '_' || ch.is_ascii_alphanumeric()));
11529        if boundary {
11530            return Some(operator_at);
11531        }
11532    }
11533    before_parameters
11534        .rfind(|ch: char| !(ch == '_' || ch.is_ascii_alphanumeric()))
11535        .map(|index| index + 1)
11536}
11537
11538fn cpp_trailing_return_type(signature_from_name: &str) -> Option<String> {
11539    let open = signature_from_name.find('(')?;
11540    let mut depth = 0i32;
11541    for (offset, ch) in signature_from_name[open..].char_indices() {
11542        match ch {
11543            '(' => depth += 1,
11544            ')' => {
11545                depth -= 1;
11546                if depth == 0 {
11547                    let rest = signature_from_name[open + offset + ch.len_utf8()..].trim_start();
11548                    let arrow = rest.find("->")?;
11549                    let return_type = rest[arrow + 2..].trim_start();
11550                    let return_type = return_type
11551                        .split(['{', ';'])
11552                        .next()
11553                        .unwrap_or(return_type)
11554                        .trim();
11555                    return (!return_type.is_empty()).then(|| return_type.to_string());
11556                }
11557            }
11558            _ => {}
11559        }
11560    }
11561    None
11562}
11563
11564/// Strip a leading `template <...>` parameter clause, leaving the declaration that follows.
11565/// Returns the input unchanged when there is no such clause.
11566fn cpp_strip_leading_template_clause(text: &str) -> &str {
11567    let trimmed = text.trim_start();
11568    let Some(rest) = trimmed.strip_prefix("template") else {
11569        return text;
11570    };
11571    let rest = rest.trim_start();
11572    if !rest.starts_with('<') {
11573        return text;
11574    }
11575    let mut depth = 0i32;
11576    for (offset, ch) in rest.char_indices() {
11577        match ch {
11578            '<' => depth += 1,
11579            '>' => {
11580                depth -= 1;
11581                if depth == 0 {
11582                    return rest[offset + ch.len_utf8()..].trim_start();
11583                }
11584            }
11585            _ => {}
11586        }
11587    }
11588    text
11589}
11590
11591pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
11592    // fqname-M4: `cpp_name_for` is a bespoke all-`::` rendering of the unit's
11593    // name (it replaces every `.`/`$` in `short_name` with `::`), which is NOT
11594    // the same string `default_parent_fq_name`/`fq().parent()` would render:
11595    // the structured `FqName`'s native cpp display deliberately keeps `.` (not
11596    // `::`) between a trailing `Package` segment and a following `Type`
11597    // segment (see `separator` in `fq_name.rs`, landed for issue #1163), so
11598    // popping the unit's own `fq()` segment would NOT reproduce this
11599    // fully-`::`-joined string. Left as a split on the locally-built
11600    // all-colon string rather than the unit's structured name.
11601    cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
11602        namespace
11603            .strip_prefix("anonymous_namespace::")
11604            .unwrap_or(namespace)
11605            .to_string()
11606    })
11607}
11608
11609fn namespace_prefixes(namespace: &str) -> Vec<String> {
11610    // `namespace` is built by `cpp_name_for`/`cpp_namespace_for` with every
11611    // non-`::` separator already converted to `::`, so re-tokenizing it with
11612    // the shared structured splitter and progressively popping the last
11613    // component reproduces the `rsplit_once("::")` outward walk exactly (same
11614    // shape as `cpp_qualifier_lookup_tiers`'s namespace-chain walk).
11615    let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11616        brokk_bifrost_core::analyzer::Language::Cpp,
11617        namespace,
11618    );
11619    let mut prefixes = Vec::new();
11620    while !parts.is_empty() {
11621        prefixes.push(parts.join("::"));
11622        parts.pop();
11623    }
11624    prefixes
11625}
11626
11627fn nearest_namespace_candidates(
11628    candidates: Vec<CodeUnit>,
11629    normalized: &str,
11630    lexical_namespace: Option<&str>,
11631) -> Vec<CodeUnit> {
11632    if normalized.contains("::") {
11633        return candidates;
11634    }
11635    if let Some(namespace) = lexical_namespace {
11636        for prefix in namespace_prefixes(namespace) {
11637            let scoped = candidates
11638                .iter()
11639                .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
11640                .cloned()
11641                .collect::<Vec<_>>();
11642            if !scoped.is_empty() {
11643                return scoped;
11644            }
11645        }
11646    }
11647    candidates
11648        .into_iter()
11649        .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
11650        .collect()
11651}
11652
11653pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
11654    let mut namespaces = Vec::new();
11655    let mut current = node.parent();
11656    while let Some(parent) = current {
11657        if parent.kind() == "namespace_definition"
11658            && let Some(name) = parent.child_by_field_name("name")
11659        {
11660            let namespace = normalize_cpp_reference_text(node_text(name, source));
11661            if !namespace.is_empty() {
11662                namespaces.push(namespace);
11663            }
11664        }
11665        current = parent.parent();
11666    }
11667    if namespaces.is_empty() {
11668        None
11669    } else {
11670        namespaces.reverse();
11671        Some(namespaces.join("::"))
11672    }
11673}
11674
11675/// Like [`precise_parent_of`], but drops module (namespace) parents. A namespace is a scope, not a
11676/// type or receiver, so namespace-scoped functions and constants resolve as free functions and
11677/// globals rather than members.
11678pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
11679    type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
11680}
11681
11682fn type_owner_resolution(
11683    analyzer: &CppGraphSource<'_>,
11684    code_unit: &CodeUnit,
11685) -> Option<ResolvedTypeOwner> {
11686    precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
11687}
11688
11689fn target_type_owner_resolution(
11690    analyzer: &CppGraphSource<'_>,
11691    code_unit: &CodeUnit,
11692) -> Option<ResolvedTypeOwner> {
11693    match type_owner_resolution(analyzer, code_unit) {
11694        Some(owner) if !owner.is_forward_declaration => Some(owner),
11695        Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
11696    }
11697}
11698
11699/// Recover method identity for an indexed out-of-line definition when the
11700/// analyzer has retained only its unique include-visible class forward
11701/// declaration. This is deliberately target-only: canonical declaration
11702/// resolution must continue to prefer the callable definition rather than
11703/// replacing it with the forward owner.
11704fn target_forward_owner_resolution(
11705    analyzer: &CppGraphSource<'_>,
11706    code_unit: &CodeUnit,
11707) -> Option<ResolvedTypeOwner> {
11708    if !code_unit.is_function() {
11709        return None;
11710    }
11711    let owner_fqn = brokk_bifrost_core::analyzer::default_parent_fq_name(code_unit)?;
11712    let cpp = analyzer.cpp?;
11713    let mut visible_files = HashSet::default();
11714    collect_include_closure(
11715        analyzer,
11716        cpp.include_target_index(),
11717        code_unit.source(),
11718        &mut visible_files,
11719        None,
11720    );
11721    let mut forward = None;
11722    for candidate in analyzer
11723        .global_usage_definition_index()
11724        .fqn(&owner_fqn)
11725        .into_iter()
11726        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
11727    {
11728        match cpp_class_declaration_strength(analyzer, candidate) {
11729            CppClassDeclarationStrength::Forward if forward.is_none() => {
11730                forward = Some(candidate.clone());
11731            }
11732            CppClassDeclarationStrength::Forward
11733            | CppClassDeclarationStrength::Full
11734            | CppClassDeclarationStrength::Unknown => return None,
11735        }
11736    }
11737    forward.map(|unit| ResolvedTypeOwner {
11738        unit,
11739        is_forward_declaration: true,
11740    })
11741}
11742
11743pub fn precise_parent_of(
11744    analyzer: &CppGraphSource<'_>,
11745    visibility: &VisibilityIndex<'_>,
11746    code_unit: &CodeUnit,
11747) -> Option<CodeUnit> {
11748    visibility.cached_precise_parent_of(analyzer, code_unit)
11749}
11750
11751fn precise_parent_resolution(
11752    analyzer: &CppGraphSource<'_>,
11753    code_unit: &CodeUnit,
11754) -> Option<ResolvedTypeOwner> {
11755    #[cfg(any(test, feature = "test-support"))]
11756    if let Some(cpp) = analyzer.cpp {
11757        cpp.record_cpp_parent_resolution_for_test();
11758    }
11759    if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
11760        return Some(ResolvedTypeOwner {
11761            unit,
11762            is_forward_declaration: false,
11763        });
11764    }
11765    let fallback = analyzer.parent_of(code_unit);
11766    // fqname-M4: `owner_name` is used both bare (passed standalone to the
11767    // owner-resolution calls below) and manually recombined with
11768    // `package_name()` a few lines down, so this needs the package-less
11769    // `short_name` owner specifically; `default_parent_fq_name`/`fq.parent()`
11770    // would render the package-qualified owner instead, changing both uses.
11771    let Some(owner_name) = code_unit
11772        .short_name()
11773        .rsplit_once('.')
11774        .map(|(owner, _)| owner)
11775    else {
11776        return fallback.map(|unit| ResolvedTypeOwner {
11777            unit,
11778            is_forward_declaration: false,
11779        });
11780    };
11781    let owner_fqn = if code_unit.package_name().is_empty() {
11782        owner_name.to_string()
11783    } else {
11784        format!("{}.{}", code_unit.package_name(), owner_name)
11785    };
11786    match same_source_owner(analyzer, code_unit, &owner_fqn, owner_name) {
11787        DirectOwnerResolution::UniqueFull(owner) => {
11788            return Some(ResolvedTypeOwner {
11789                unit: owner,
11790                is_forward_declaration: false,
11791            });
11792        }
11793        DirectOwnerResolution::Ambiguous => return None,
11794        DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
11795    }
11796    match directly_included_owner(analyzer, code_unit, &owner_fqn, owner_name) {
11797        DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
11798            unit: owner,
11799            is_forward_declaration: false,
11800        }),
11801        DirectOwnerResolution::Ambiguous => None,
11802        DirectOwnerResolution::ForwardsOnly(forwards) => {
11803            match visible_full_cpp_owner(analyzer, code_unit, &owner_fqn, owner_name) {
11804                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
11805                    unit: owner,
11806                    is_forward_declaration: false,
11807                }),
11808                FullOwnerResolution::None => {
11809                    unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
11810                        unit,
11811                        is_forward_declaration: true,
11812                    })
11813                }
11814                FullOwnerResolution::Ambiguous => None,
11815            }
11816        }
11817        DirectOwnerResolution::None => {
11818            match visible_full_cpp_owner(analyzer, code_unit, &owner_fqn, owner_name) {
11819                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
11820                    unit: owner,
11821                    is_forward_declaration: false,
11822                }),
11823                FullOwnerResolution::Ambiguous => None,
11824                FullOwnerResolution::None => fallback
11825                    .filter(|parent| {
11826                        parent.source() == code_unit.source()
11827                            && parent.short_name() == owner_name
11828                            && parent.package_name() == code_unit.package_name()
11829                            && (!parent.is_class()
11830                                || cpp_class_declaration_strength(analyzer, parent)
11831                                    == CppClassDeclarationStrength::Full)
11832                    })
11833                    .map(|unit| ResolvedTypeOwner {
11834                        unit,
11835                        is_forward_declaration: false,
11836                    }),
11837            }
11838        }
11839    }
11840}
11841
11842fn exact_structural_type_parent(
11843    analyzer: &CppGraphSource<'_>,
11844    code_unit: &CodeUnit,
11845) -> Option<CodeUnit> {
11846    if !code_unit.is_function() && !code_unit.is_field() {
11847        return None;
11848    }
11849    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
11850    let cpp = analyzer.cpp?;
11851    let parent = cpp.structural_parent_of(code_unit)?;
11852    (!parent.is_module()
11853        && parent.source() == code_unit.source()
11854        && parent.package_name() == code_unit.package_name()
11855        && parent.short_name() == encoded_owner)
11856        .then_some(parent)
11857}
11858
11859fn same_source_owner(
11860    analyzer: &CppGraphSource<'_>,
11861    code_unit: &CodeUnit,
11862    owner_fqn: &str,
11863    owner_name: &str,
11864) -> DirectOwnerResolution {
11865    let candidates = analyzer
11866        .global_usage_definition_index()
11867        .fqn(owner_fqn)
11868        .into_iter()
11869        .filter(|candidate| {
11870            candidate.is_class()
11871                && candidate.source() == code_unit.source()
11872                && candidate.short_name() == owner_name
11873                && candidate.package_name() == code_unit.package_name()
11874        })
11875        .collect::<Vec<_>>();
11876    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
11877    classify_direct_owner_candidates(analyzer, candidates.into_iter())
11878}
11879
11880fn visible_full_cpp_owner(
11881    analyzer: &CppGraphSource<'_>,
11882    code_unit: &CodeUnit,
11883    owner_fqn: &str,
11884    owner_name: &str,
11885) -> FullOwnerResolution {
11886    let Some(cpp) = analyzer.cpp else {
11887        return FullOwnerResolution::None;
11888    };
11889    let mut visible_files = HashSet::default();
11890    collect_include_closure(
11891        analyzer,
11892        cpp.include_target_index(),
11893        code_unit.source(),
11894        &mut visible_files,
11895        None,
11896    );
11897    let candidates = analyzer
11898        .global_usage_definition_index()
11899        .fqn(owner_fqn)
11900        .into_iter()
11901        .filter(|candidate| {
11902            candidate.is_class()
11903                && candidate.short_name() == owner_name
11904                && candidate.package_name() == code_unit.package_name()
11905                && visible_files.contains(candidate.source())
11906        })
11907        .collect::<Vec<_>>();
11908    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
11909    let mut full_definition = None;
11910    for candidate in candidates {
11911        match cpp_class_declaration_strength(analyzer, candidate) {
11912            CppClassDeclarationStrength::Full if full_definition.is_some() => {
11913                return FullOwnerResolution::Ambiguous;
11914            }
11915            CppClassDeclarationStrength::Full => full_definition = Some(candidate.clone()),
11916            CppClassDeclarationStrength::Forward => {}
11917            CppClassDeclarationStrength::Unknown => return FullOwnerResolution::Ambiguous,
11918        }
11919    }
11920    full_definition.map_or(FullOwnerResolution::None, FullOwnerResolution::Unique)
11921}
11922
11923pub enum DirectOwnerResolution {
11924    None,
11925    ForwardsOnly(Vec<CodeUnit>),
11926    UniqueFull(CodeUnit),
11927    Ambiguous,
11928}
11929
11930enum FullOwnerResolution {
11931    None,
11932    Unique(CodeUnit),
11933    Ambiguous,
11934}
11935
11936#[derive(Clone, Copy, PartialEq, Eq)]
11937pub enum CppClassDeclarationStrength {
11938    Full,
11939    Forward,
11940    Unknown,
11941}
11942
11943fn directly_included_owner(
11944    analyzer: &CppGraphSource<'_>,
11945    code_unit: &CodeUnit,
11946    owner_fqn: &str,
11947    owner_name: &str,
11948) -> DirectOwnerResolution {
11949    let Some(cpp) = analyzer.cpp else {
11950        return DirectOwnerResolution::None;
11951    };
11952    let imports = analyzer.import_statements(code_unit.source());
11953    let direct_includes: HashSet<ProjectFile> = cpp_include_paths(&imports)
11954        .into_iter()
11955        .flat_map(|include| {
11956            resolve_include_targets_with_index(
11957                code_unit.source(),
11958                &include,
11959                cpp.include_target_index(),
11960            )
11961        })
11962        .collect();
11963    let candidates = analyzer
11964        .global_usage_definition_index()
11965        .fqn(owner_fqn)
11966        .into_iter()
11967        .filter(|candidate| {
11968            candidate.is_class()
11969                && candidate.short_name() == owner_name
11970                && candidate.package_name() == code_unit.package_name()
11971                && direct_includes.contains(candidate.source())
11972        })
11973        .collect::<Vec<_>>();
11974    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
11975    classify_direct_owner_candidates(analyzer, candidates.into_iter())
11976}
11977
11978fn prefer_member_declaring_owners<'a>(
11979    analyzer: &CppGraphSource<'_>,
11980    member: &CodeUnit,
11981    candidates: Vec<&'a CodeUnit>,
11982) -> Vec<&'a CodeUnit> {
11983    let matching = candidates
11984        .iter()
11985        .copied()
11986        .filter(|owner| owner_declares_member(analyzer, owner, member))
11987        .collect::<Vec<_>>();
11988    if matching.is_empty() {
11989        candidates
11990    } else {
11991        matching
11992    }
11993}
11994
11995fn owner_declares_member(
11996    analyzer: &CppGraphSource<'_>,
11997    owner: &CodeUnit,
11998    member: &CodeUnit,
11999) -> bool {
12000    analyzer.direct_children(owner).into_iter().any(|child| {
12001        child.kind() == member.kind()
12002            && child.identifier() == member.identifier()
12003            && child.signature() == member.signature()
12004    })
12005}
12006
12007fn classify_direct_owner_candidates<'a>(
12008    analyzer: &CppGraphSource<'_>,
12009    candidates: impl Iterator<Item = &'a CodeUnit>,
12010) -> DirectOwnerResolution {
12011    collapse_owner_candidates(candidates.map(|candidate| {
12012        (
12013            candidate.clone(),
12014            cpp_class_declaration_strength(analyzer, candidate),
12015        )
12016    }))
12017}
12018
12019pub fn collapse_owner_candidates(
12020    candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
12021) -> DirectOwnerResolution {
12022    let mut full_definition = None;
12023    let mut forwards = Vec::new();
12024    for (candidate, strength) in candidates {
12025        match strength {
12026            CppClassDeclarationStrength::Full if full_definition.is_some() => {
12027                return DirectOwnerResolution::Ambiguous;
12028            }
12029            CppClassDeclarationStrength::Full => full_definition = Some(candidate),
12030            CppClassDeclarationStrength::Forward => forwards.push(candidate),
12031            CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
12032        }
12033    }
12034    if let Some(owner) = full_definition {
12035        DirectOwnerResolution::UniqueFull(owner)
12036    } else if !forwards.is_empty() {
12037        DirectOwnerResolution::ForwardsOnly(forwards)
12038    } else {
12039        DirectOwnerResolution::None
12040    }
12041}
12042
12043#[cfg(any(test, feature = "test-support"))]
12044pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
12045    unique_logical_forward_owner(forwards)
12046}
12047
12048fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
12049    let first = forwards.pop()?;
12050    forwards
12051        .iter()
12052        .all(|forward| same_logical_symbol(forward, &first))
12053        .then_some(first)
12054}
12055
12056pub fn cpp_class_declaration_strength(
12057    analyzer: &CppGraphSource<'_>,
12058    candidate: &CodeUnit,
12059) -> CppClassDeclarationStrength {
12060    if let Some(prepared) = analyzer
12061        .cpp
12062        .and_then(|cpp| cpp.prepared_syntax(candidate.source()))
12063    {
12064        return cpp_class_declaration_strength_in_tree(
12065            analyzer,
12066            candidate,
12067            prepared.source(),
12068            prepared.tree().root_node(),
12069        );
12070    }
12071    let Some(source) = analyzer.indexed_source(candidate.source()) else {
12072        return CppClassDeclarationStrength::Unknown;
12073    };
12074    #[cfg(any(test, feature = "test-support"))]
12075    if let Some(cpp) = analyzer.cpp {
12076        cpp.record_cpp_class_strength_parse_for_test();
12077    }
12078    let mut parser = Parser::new();
12079    if parser
12080        .set_language(&tree_sitter_cpp::LANGUAGE.into())
12081        .is_err()
12082    {
12083        return CppClassDeclarationStrength::Unknown;
12084    }
12085    let Some(tree) = parser.parse(&source, None) else {
12086        return CppClassDeclarationStrength::Unknown;
12087    };
12088    cpp_class_declaration_strength_in_tree(analyzer, candidate, &source, tree.root_node())
12089}
12090
12091fn cpp_class_declaration_strength_in_tree(
12092    analyzer: &CppGraphSource<'_>,
12093    candidate: &CodeUnit,
12094    source: &str,
12095    root: Node<'_>,
12096) -> CppClassDeclarationStrength {
12097    let ranges = analyzer.ranges(candidate);
12098    let mut saw_forward = false;
12099    for range in ranges {
12100        let mut stack = vec![root];
12101        while let Some(node) = stack.pop() {
12102            if node.start_byte() == range.start_byte
12103                && recovered_fragmented_plain_class_has_body(
12104                    node,
12105                    source,
12106                    candidate.identifier(),
12107                    &range,
12108                )
12109            {
12110                return CppClassDeclarationStrength::Full;
12111            }
12112            if node.start_byte() > range.start_byte || node.end_byte() < range.start_byte {
12113                continue;
12114            }
12115            if node.start_byte() == range.start_byte && node.end_byte() == range.end_byte {
12116                if matches!(
12117                    node.kind(),
12118                    "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
12119                ) {
12120                    if cpp_class_node_has_body(node) {
12121                        return CppClassDeclarationStrength::Full;
12122                    }
12123                    saw_forward = true;
12124                } else if let Some(has_body) =
12125                    recovered_exported_class_has_body(node, source, candidate.identifier())
12126                {
12127                    if has_body {
12128                        return CppClassDeclarationStrength::Full;
12129                    }
12130                    saw_forward = true;
12131                }
12132            }
12133            let mut cursor = node.walk();
12134            stack.extend(node.named_children(&mut cursor));
12135        }
12136    }
12137    if saw_forward {
12138        CppClassDeclarationStrength::Forward
12139    } else {
12140        CppClassDeclarationStrength::Unknown
12141    }
12142}
12143
12144fn cpp_class_node_has_body(node: Node<'_>) -> bool {
12145    node.child_by_field_name("body").is_some() || {
12146        let mut cursor = node.walk();
12147        node.named_children(&mut cursor).any(|child| {
12148            matches!(
12149                child.kind(),
12150                "declaration_list" | "field_declaration_list" | "enumerator_list"
12151            )
12152        })
12153    }
12154}
12155
12156pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
12157    // fqname-M4: `owner_name` is used both bare and manually recombined with
12158    // `package_name()` below (same package-less short_name owner shape as
12159    // `precise_parent_resolution` above); `default_parent_fq_name` would
12160    // render the package-qualified owner instead, changing both uses.
12161    let owner_name = code_unit
12162        .short_name()
12163        .rsplit_once('.')
12164        .map(|(owner, _)| owner)?;
12165    let owner_fqn = if code_unit.package_name().is_empty() {
12166        owner_name.to_string()
12167    } else {
12168        format!("{}.{}", code_unit.package_name(), owner_name)
12169    };
12170    ctx.analyzer
12171        .global_usage_definition_index()
12172        .fqn(&owner_fqn)
12173        .into_iter()
12174        .find(|candidate| {
12175            candidate.is_class()
12176                && ctx.visibility.is_visible(ctx.file, candidate)
12177                && candidate.short_name() == owner_name
12178                && candidate.package_name() == code_unit.package_name()
12179        })
12180        .cloned()
12181}
12182
12183pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
12184    left.kind() == right.kind()
12185        && left.fq_name() == right.fq_name()
12186        && left.signature() == right.signature()
12187        && left.source() == right.source()
12188}
12189
12190pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
12191    same_symbol(left, right) || same_logical_symbol(left, right)
12192}
12193
12194pub fn same_visible_global_field_symbol(
12195    analyzer: &CppGraphSource<'_>,
12196    internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
12197    left: &CodeUnit,
12198    right: &CodeUnit,
12199) -> bool {
12200    if same_symbol(left, right) {
12201        return true;
12202    }
12203    if !same_logical_symbol(left, right) {
12204        return false;
12205    }
12206    if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
12207        || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
12208    {
12209        left.source() == right.source()
12210    } else {
12211        true
12212    }
12213}
12214
12215fn cpp_global_field_has_internal_linkage_cached(
12216    analyzer: &CppGraphSource<'_>,
12217    cache: &mut HashMap<CodeUnit, bool>,
12218    candidate: &CodeUnit,
12219) -> bool {
12220    if let Some(internal) = cache.get(candidate) {
12221        return *internal;
12222    }
12223    #[cfg(any(test, feature = "test-support"))]
12224    note_cpp_global_field_internal_linkage_classification_for_test();
12225    let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
12226    cache.insert(candidate.clone(), internal);
12227    internal
12228}
12229
12230#[cfg(any(test, feature = "test-support"))]
12231thread_local! {
12232    static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
12233}
12234
12235#[cfg(any(test, feature = "test-support"))]
12236fn note_cpp_global_field_internal_linkage_classification_for_test() {
12237    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
12238        count.set(count.get() + 1);
12239    });
12240}
12241
12242#[cfg(any(test, feature = "test-support"))]
12243pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
12244    body: impl FnOnce() -> T,
12245) -> (T, usize) {
12246    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
12247        count.set(0);
12248        let result = body();
12249        let observed = count.get();
12250        count.set(0);
12251        (result, observed)
12252    })
12253}
12254
12255pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
12256    left.kind() == right.kind()
12257        && left.fq_name() == right.fq_name()
12258        && left.signature() == right.signature()
12259}
12260
12261pub fn cpp_global_field_has_internal_linkage(
12262    analyzer: &CppGraphSource<'_>,
12263    candidate: &CodeUnit,
12264) -> bool {
12265    if !candidate.is_field() || candidate.short_name().contains('.') {
12266        return false;
12267    }
12268    let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
12269        return false;
12270    };
12271    match local_linkage {
12272        CppFieldLinkage::Internal => true,
12273        CppFieldLinkage::External => false,
12274        CppFieldLinkage::InternalUnlessExternalPeer => {
12275            !cpp_global_field_linkage_peers(analyzer, candidate)
12276                .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, peer))
12277                .any(|linkage| matches!(linkage, CppFieldLinkage::External))
12278        }
12279    }
12280}
12281
12282fn cpp_global_field_linkage_peers<'a>(
12283    analyzer: &CppGraphSource<'a>,
12284    candidate: &'a CodeUnit,
12285) -> impl Iterator<Item = &'a CodeUnit> + 'a {
12286    // These peers are returned to the caller, so they must borrow the analyzer
12287    // for `'a` rather than a handle that dies with this call. `fqn` reads the
12288    // shards directly for exactly that reason.
12289    let fq_name = candidate.fq_name();
12290    analyzer
12291        .global_usage_definition_index()
12292        .fqn(&fq_name)
12293        .into_iter()
12294        .filter(move |peer| {
12295            if *peer == candidate {
12296                return false;
12297            }
12298            #[cfg(any(test, feature = "test-support"))]
12299            note_cpp_global_field_linkage_peer_inspection_for_test();
12300            same_logical_symbol(peer, candidate)
12301        })
12302}
12303
12304#[cfg(any(test, feature = "test-support"))]
12305thread_local! {
12306    static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
12307}
12308
12309#[cfg(any(test, feature = "test-support"))]
12310fn note_cpp_global_field_linkage_peer_inspection_for_test() {
12311    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
12312        count.set(count.get() + 1);
12313    });
12314}
12315
12316#[cfg(any(test, feature = "test-support"))]
12317pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
12318    body: impl FnOnce() -> T,
12319) -> (T, usize) {
12320    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
12321        count.set(0);
12322        let result = body();
12323        let observed = count.get();
12324        count.set(0);
12325        (result, observed)
12326    })
12327}
12328
12329fn cpp_global_field_declaration_linkage(
12330    analyzer: &CppGraphSource<'_>,
12331    candidate: &CodeUnit,
12332) -> Option<CppFieldLinkage> {
12333    if let Some(linkage) = analyzer.cpp_field_linkage(candidate) {
12334        return Some(linkage);
12335    }
12336    let cpp = analyzer.cpp?;
12337    if let Some(prepared) = cpp.prepared_syntax(candidate.source()) {
12338        return cpp_global_field_declaration_linkage_in_tree(
12339            analyzer,
12340            candidate,
12341            prepared.source(),
12342            prepared.tree().root_node(),
12343        );
12344    }
12345    let source = analyzer.indexed_source(candidate.source())?;
12346    let mut parser = Parser::new();
12347    if parser
12348        .set_language(&tree_sitter_cpp::LANGUAGE.into())
12349        .is_err()
12350    {
12351        return None;
12352    }
12353    let tree = parser.parse(&source, None)?;
12354    cpp_global_field_declaration_linkage_in_tree(analyzer, candidate, &source, tree.root_node())
12355}
12356
12357fn cpp_global_field_declaration_linkage_in_tree(
12358    analyzer: &CppGraphSource<'_>,
12359    candidate: &CodeUnit,
12360    source: &str,
12361    root: Node<'_>,
12362) -> Option<CppFieldLinkage> {
12363    analyzer.ranges(candidate).iter().find_map(|range| {
12364        node_for_exact_range(root, range)
12365            .and_then(enclosing_cpp_field_declaration)
12366            .map(|declaration| cpp_field_declaration_linkage(declaration, source))
12367    })
12368}
12369
12370fn enclosing_cpp_field_declaration(mut node: Node<'_>) -> Option<Node<'_>> {
12371    loop {
12372        if matches!(node.kind(), "declaration" | "field_declaration") {
12373            return Some(node);
12374        }
12375        node = node.parent()?;
12376    }
12377}
12378
12379#[cfg(test)]
12380mod tests {
12381    use super::*;
12382
12383    #[test]
12384    fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
12385        let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
12386        assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
12387        assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
12388        assert!(indexed_namespace_path_is_recoverable(
12389            &["cache".to_string()],
12390            &indexed,
12391            1,
12392        ));
12393    }
12394
12395    #[test]
12396    fn sort_lookup_units_totally_orders_every_identity_field() {
12397        let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
12398        let base = CodeUnit::with_signature(
12399            file.clone(),
12400            CodeUnitType::Function,
12401            "scope",
12402            "value",
12403            Some("()".to_string()),
12404            false,
12405        );
12406        let different_kind = CodeUnit::with_signature(
12407            file.clone(),
12408            CodeUnitType::Field,
12409            "scope",
12410            "value",
12411            Some("()".to_string()),
12412            false,
12413        );
12414        let synthetic = base.with_synthetic(true);
12415
12416        let interner = segment_interner();
12417        let mut member_fq = FqName::new();
12418        member_fq.push(interner.intern("scope", SegmentKind::Package));
12419        member_fq.push(interner.intern("value", SegmentKind::Member));
12420        let different_package_boundary = CodeUnit::from_fq(
12421            file.clone(),
12422            CodeUnitType::Function,
12423            member_fq,
12424            0,
12425            Some("()".to_string()),
12426            false,
12427        );
12428
12429        let mut unknown_fq = FqName::new();
12430        unknown_fq.push(interner.intern("scope", SegmentKind::Package));
12431        unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
12432        let different_segment_kind = CodeUnit::from_fq(
12433            file,
12434            CodeUnitType::Function,
12435            unknown_fq,
12436            1,
12437            Some("()".to_string()),
12438            false,
12439        );
12440
12441        let input = vec![
12442            base,
12443            different_kind,
12444            synthetic,
12445            different_package_boundary,
12446            different_segment_kind,
12447        ];
12448        let mut expected = input.clone();
12449        sort_lookup_units(&mut expected);
12450        assert!(expected.windows(2).all(|pair| {
12451            let mut ordered = pair.to_vec();
12452            sort_lookup_units(&mut ordered);
12453            ordered == pair && pair[0] != pair[1]
12454        }));
12455
12456        let mut reversed = input.clone();
12457        reversed.reverse();
12458        sort_lookup_units(&mut reversed);
12459        assert_eq!(reversed, expected);
12460
12461        let mut rotated = input;
12462        rotated.rotate_left(2);
12463        sort_lookup_units(&mut rotated);
12464        assert_eq!(rotated, expected);
12465    }
12466
12467    #[test]
12468    fn displaced_preprocessor_terminator_bounds_the_real_guard() {
12469        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";
12470        let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
12471        let parse = |source: &str| {
12472            let mut parser = Parser::new();
12473            parser
12474                .set_language(&tree_sitter_cpp::LANGUAGE.into())
12475                .expect("C++ grammar");
12476            parser.parse(source, None).expect("fixture tree")
12477        };
12478
12479        let tree = parse(damaged);
12480        let root = tree.root_node();
12481        let target = damaged.find("target").expect("target byte");
12482        let declaration = root
12483            .descendant_for_byte_range(target, target + "target".len())
12484            .and_then(|mut node| {
12485                loop {
12486                    if node.kind() == "declaration" {
12487                        break Some(node);
12488                    }
12489                    node = node.parent()?;
12490                }
12491            })
12492            .expect("declaration after the displaced terminator");
12493        let conditional = declaration
12494            .parent()
12495            .filter(|node| node.kind() == "preproc_ifdef")
12496            .expect("damaged inner conditional");
12497        let outer = conditional
12498            .parent()
12499            .filter(|node| node.kind() == "preproc_ifdef")
12500            .expect("ordinary outer include guard");
12501        let terminator = cpp_displaced_preprocessor_terminator(conditional)
12502            .expect("structured displaced #endif");
12503        assert_eq!(node_text(terminator, damaged), "#endif");
12504        assert!(terminator.end_byte() <= declaration.start_byte());
12505        assert!(!preprocessor_conditional_contains_descendant(
12506            conditional,
12507            declaration
12508        ));
12509        assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
12510        assert!(preprocessor_conditional_contains_descendant(
12511            outer,
12512            declaration
12513        ));
12514
12515        let tree = parse(guarded);
12516        let conditional = tree
12517            .root_node()
12518            .named_child(0)
12519            .filter(|node| node.kind() == "preproc_ifdef")
12520            .expect("ordinary conditional");
12521        let declaration = conditional
12522            .named_children(&mut conditional.walk())
12523            .find(|node| node.kind() == "declaration")
12524            .expect("guarded declaration");
12525        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
12526        assert!(preprocessor_conditional_contains_descendant(
12527            conditional,
12528            declaration
12529        ));
12530
12531        let damaged_alternative = format!(
12532            "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
12533            "UNUSED(value)\n".repeat(64)
12534        );
12535        let tree = parse(&damaged_alternative);
12536        let conditional = tree
12537            .root_node()
12538            .named_child(0)
12539            .filter(|node| node.kind() == "preproc_ifdef")
12540            .expect("outer conditional with an alternative");
12541        assert!(conditional.has_error());
12542        assert!(conditional.child_by_field_name("alternative").is_some());
12543        assert!(
12544            conditional
12545                .child(conditional.child_count() - 1)
12546                .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
12547        );
12548        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
12549
12550        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";
12551        let tree = parse(split_declaration);
12552        let root = tree.root_node();
12553        let conditional = root
12554            .named_children(&mut root.walk())
12555            .find(|node| node.kind() == "preproc_ifdef" && node.start_position().row == 3)
12556            .expect("split declaration conditional");
12557        let target = split_declaration
12558            .find("static int target")
12559            .expect("target byte");
12560        let boundary =
12561            cpp_displaced_preprocessor_boundary(conditional).expect("split declaration boundary");
12562        assert!(boundary.end_byte <= target, "{boundary:?}");
12563        assert_eq!(boundary.end_line, 9, "{boundary:?}");
12564        let target_node = root
12565            .descendant_for_byte_range(target, target + "static".len())
12566            .expect("target node");
12567        assert!(!preprocessor_conditional_contains_descendant(
12568            conditional,
12569            target_node
12570        ));
12571    }
12572
12573    #[test]
12574    fn fragmented_reference_guard_is_recovered() {
12575        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";
12576        let mut parser = Parser::new();
12577        parser
12578            .set_language(&tree_sitter_cpp::LANGUAGE.into())
12579            .expect("C++ grammar");
12580        let tree = parser.parse(source, None).expect("fixture tree");
12581        let start = source.rfind("helper").expect("reference byte");
12582        let node = tree
12583            .root_node()
12584            .descendant_for_byte_range(start, start + "helper".len())
12585            .expect("reference node");
12586        let mut expected = HashSet::default();
12587        expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
12588            vec![
12589                BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
12590                BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
12591            ],
12592        )));
12593        assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
12594    }
12595
12596    #[test]
12597    fn boolean_guard_normalization_proves_equivalence_and_implication() {
12598        let windows = BooleanGuardExpression::Defined("WIN32".to_string());
12599        let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
12600        let negated_windows_branch =
12601            BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
12602        let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
12603        assert_eq!(negated_windows_branch, portable);
12604
12605        let missing_a = BooleanGuardExpression::Undefined("A".to_string());
12606        let missing_b = BooleanGuardExpression::Undefined("B".to_string());
12607        let missing_c = BooleanGuardExpression::Undefined("C".to_string());
12608        let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
12609        let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
12610        assert!(fallback_branch.implies(&fallback_declaration));
12611        assert!(!fallback_declaration.implies(&fallback_branch));
12612    }
12613
12614    #[test]
12615    fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
12616        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";
12617        let mut parser = Parser::new();
12618        parser
12619            .set_language(&tree_sitter_cpp::LANGUAGE.into())
12620            .expect("C++ grammar");
12621        let tree = parser.parse(source, None).expect("fixture tree");
12622        let root = tree.root_node();
12623        let call = |marker: &str| {
12624            let start = source.find(marker).expect("call marker");
12625            let mut node = root
12626                .descendant_for_byte_range(start, start + "helper".len())
12627                .expect("call name node");
12628            loop {
12629                if node.kind() == "call_expression" {
12630                    break node;
12631                }
12632                node = node.parent().expect("call expression ancestor");
12633            }
12634        };
12635        let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
12636        let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
12637        let keyword_call = call("helper(NULL, template); /* bound */");
12638        let keyword_arguments = keyword_call
12639            .child_by_field_name("arguments")
12640            .expect("keyword argument list");
12641        assert_eq!(
12642            recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
12643            1
12644        );
12645        assert_eq!(
12646            recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
12647            0
12648        );
12649
12650        let unbound_call = call("helper(NULL, template); /* unbound */");
12651        let unbound_arguments = unbound_call
12652            .child_by_field_name("arguments")
12653            .expect("unbound argument list");
12654        assert_eq!(
12655            recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
12656            0
12657        );
12658    }
12659
12660    fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
12661        let mut parser = Parser::new();
12662        parser
12663            .set_language(&tree_sitter_cpp::LANGUAGE.into())
12664            .expect("C++ grammar");
12665        let tree = parser.parse(source, None).expect("C++ fixture tree");
12666        let mut stack = vec![tree.root_node()];
12667        while let Some(node) = stack.pop() {
12668            if node.kind() == "enum_specifier" {
12669                return flattened_macro_namespace_components(node, source);
12670            }
12671            let mut cursor = node.walk();
12672            let children = node.named_children(&mut cursor).collect::<Vec<_>>();
12673            stack.extend(children.into_iter().rev());
12674        }
12675        None
12676    }
12677
12678    #[test]
12679    fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
12680        let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
12681namespace detail
12682{
12683enum class value_t { null };
12684}
12685NLOHMANN_JSON_NAMESPACE_END
12686NLOHMANN_JSON_NAMESPACE_BEGIN
12687namespace next
12688{
12689struct next_type {};
12690}
12691NLOHMANN_JSON_NAMESPACE_END
12692"#;
12693        assert_eq!(
12694            first_enum_flattened_namespace(complete),
12695            Some(vec!["detail".to_string()])
12696        );
12697
12698        let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
12699        assert_eq!(
12700            first_enum_flattened_namespace(&stale_end),
12701            Some(vec!["detail".to_string()]),
12702            "a stale end marker before the begin marker must not replace the intended namespace"
12703        );
12704
12705        let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
12706namespace detail
12707{
12708enum class value_t { null };
12709}
12710struct next_type {};
12711"#;
12712        assert_eq!(first_enum_flattened_namespace(incomplete), None);
12713    }
12714}