Skip to main content

brokk_bifrost_cpp/graph/
resolver.rs

1use crate::call_match::{
2    CppArgType, cpp_signature_param_types, cpp_split_top_level_commas, normalize_cpp_type_name,
3};
4use crate::compile_context::CppCompileContext;
5#[cfg(test)]
6use crate::declarations::cpp_displaced_preprocessor_terminator;
7use crate::declarations::{
8    CppComparableNode, CppComparableParameter, CppComparableSlot, CppRecoveredExportClassIndex,
9    cpp_callable_identity_suffix, cpp_comparable_parameter_shapes, cpp_declarator_adds_indirection,
10    cpp_displaced_preprocessor_boundary, cpp_export_macro_token, cpp_field_declaration_linkage,
11    cpp_function_declarator_at, cpp_template_term, node_text, normalize_cpp_whitespace,
12    recovered_class_body_at, recovered_function_like_field_declarator,
13    recovered_pyobject_head_field,
14};
15use crate::graph::CppGraphSource;
16use crate::graph::extractor::ScanCtx;
17use crate::graph::syntax::object_macro_replacement_type_references;
18use crate::graph_support::CppSource;
19use crate::imports::{
20    IncludeTargetIndex, include_paths as cpp_include_paths, resolve_include_targets_with_index,
21};
22use brokk_bifrost_core::analyzer::fq_name::{FqName, SegmentKind, segment_interner};
23use brokk_bifrost_core::analyzer::model::{
24    CallableArity, CodeUnitType, CppFieldLinkage, CppTemplateExpression, CppTemplateMetadata,
25    CppTemplateParameterMetadata, CppTemplateTerm, Language, LanguageDialect, StructuredTypeName,
26};
27use brokk_bifrost_core::analyzer::pool_memo::PoolSafeMemo;
28use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
29use brokk_bifrost_core::analyzer::query_token::QueryToken;
30use brokk_bifrost_core::analyzer::tree_walk::{ParentIndex, node_for_exact_range};
31use brokk_bifrost_core::analyzer::usages::common::same_node;
32use brokk_bifrost_core::analyzer::usages::local_inference::LocalInferenceEngine;
33use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
34use brokk_bifrost_core::cancellation::CancellationToken;
35use brokk_bifrost_core::hash::{HashMap, HashSet};
36use std::borrow::Cow;
37#[cfg(any(test, feature = "test-support"))]
38use std::cell::Cell;
39use std::cell::OnceCell;
40use std::cmp::Ordering as CmpOrdering;
41use std::collections::BTreeSet;
42use std::hash::Hash;
43use std::sync::atomic::{AtomicUsize, Ordering};
44use std::sync::{Arc, Mutex, OnceLock, RwLock};
45use std::time::{Duration, Instant};
46use tree_sitter::{Node, Parser, Tree};
47
48#[cfg(any(test, feature = "test-support"))]
49thread_local! {
50    static BOUNDED_VISIBILITY_DECLARATION_READ_COUNT: Cell<usize> = const { Cell::new(0) };
51}
52
53#[derive(Clone, Copy, PartialEq, Eq)]
54pub enum TargetKind {
55    Type,
56    Constructor,
57    FreeFunction,
58    Method,
59    GlobalField,
60    MemberField,
61    Macro,
62}
63
64pub enum LexicalTypeResolution {
65    Resolved {
66        unit: CodeUnit,
67        components: Vec<String>,
68        candidates: Vec<CodeUnit>,
69    },
70    Ambiguous,
71    Missing,
72}
73
74#[derive(Clone, Copy)]
75enum TypeCandidateResolution<'a> {
76    Canonical,
77    PreserveAlias,
78    PreserveTarget(&'a CodeUnit),
79}
80
81/// Why a name did not reduce to one indexed type declaration.
82///
83/// The two answers are not interchangeable. `Ambiguous` means the index holds
84/// several declarations and the caller must choose; `Unresolvable` means the
85/// index holds none, which is a boundary the workspace cannot see past. A
86/// `using`/`typedef` alias to a template parameter or to a standard-library
87/// type is unresolvable, and reporting it as ambiguity produced an `ambiguous`
88/// answer with an empty candidate list (#1828).
89#[derive(Clone, Copy, Debug, PartialEq, Eq)]
90enum TypeCandidateFailure {
91    Ambiguous,
92    Unresolvable,
93}
94
95impl TypeCandidateFailure {
96    fn lexical_resolution(self) -> LexicalTypeResolution {
97        match self {
98            Self::Ambiguous => LexicalTypeResolution::Ambiguous,
99            Self::Unresolvable => LexicalTypeResolution::Missing,
100        }
101    }
102}
103
104pub enum LexicalCallableValueResolution {
105    Type(CodeUnit),
106    FreeFunction(CodeUnit),
107    Ambiguous,
108    Missing,
109}
110
111pub enum UsingEnumMemberResolution {
112    Resolved { owner: CodeUnit, member: CodeUnit },
113    Ambiguous,
114    Missing,
115}
116
117pub enum NamespaceValueResolution {
118    Resolved,
119    Ambiguous,
120    Missing,
121}
122
123#[derive(Clone, Debug, PartialEq, Eq)]
124pub enum OrdinaryMacroReferenceResolution {
125    Resolved(CodeUnit),
126    Ambiguous,
127    Missing,
128}
129
130#[derive(Clone, Debug, PartialEq, Eq)]
131pub enum RecoveredCReferenceRanges {
132    Complete(Vec<Range>),
133    LimitExceeded,
134}
135
136pub fn resolve_namespace_value(
137    analyzer: &CppGraphSource<'_>,
138    visibility: &VisibilityIndex<'_>,
139    file: &ProjectFile,
140    namespace: &str,
141    name: &str,
142    before_byte: usize,
143) -> NamespaceValueResolution {
144    let mut matches = Vec::new();
145    for candidate in visibility.visible_identifier_candidates(file, name) {
146        if type_owner_of(analyzer, candidate).is_some()
147            || candidate.package_name() != namespace
148            || (candidate.source() == file
149                && !analyzer
150                    .ranges(candidate)
151                    .iter()
152                    .any(|range| range.start_byte < before_byte))
153            || matches
154                .iter()
155                .any(|existing| same_visible_symbol(existing, candidate))
156        {
157            continue;
158        }
159        matches.push(candidate.clone());
160        if matches.len() > 1 {
161            return NamespaceValueResolution::Ambiguous;
162        }
163    }
164    matches
165        .pop()
166        .map(|_| NamespaceValueResolution::Resolved)
167        .unwrap_or(NamespaceValueResolution::Missing)
168}
169
170pub(crate) struct ScopedUsingEnumOwners {
171    scopes: Vec<Vec<CodeUnit>>,
172}
173
174/// Same-file class and namespace imports collected by the targeted scanner's AST prepass.
175/// Cross-file and inherited class imports are deliberately not inferred without persisted
176/// evidence; a missing imported enumerator therefore remains unproven rather than being
177/// misresolved.
178pub(crate) struct SemanticUsingEnumOwners {
179    class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
180    namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
181}
182
183pub(crate) enum SemanticUsingEnumMemberResolution {
184    Class(UsingEnumMemberResolution),
185    Namespace(UsingEnumMemberResolution),
186    Missing,
187}
188
189impl SemanticUsingEnumOwners {
190    pub(crate) fn new() -> Self {
191        Self {
192            class_imports: HashMap::default(),
193            namespace_imports: HashMap::default(),
194        }
195    }
196
197    pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
198        let imports = self.class_imports.entry(class).or_default();
199        if !imports
200            .iter()
201            .any(|existing| same_visible_symbol(existing, &enum_owner))
202        {
203            imports.push(enum_owner);
204        }
205    }
206
207    pub fn import_namespace(
208        &mut self,
209        namespace: Vec<String>,
210        declaration_byte: usize,
211        enum_owner: CodeUnit,
212    ) {
213        let imports = self.namespace_imports.entry(namespace).or_default();
214        if !imports
215            .iter()
216            .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
217        {
218            imports.push((declaration_byte, enum_owner));
219        }
220    }
221
222    pub fn resolve_member(
223        &self,
224        visibility: &VisibilityIndex<'_>,
225        file: &ProjectFile,
226        class: Option<&CodeUnit>,
227        namespace: &[String],
228        before_byte: usize,
229        name: &str,
230    ) -> SemanticUsingEnumMemberResolution {
231        if let Some(class) = class
232            && let Some((_, imports)) = self
233                .class_imports
234                .iter()
235                .find(|(owner, _)| same_visible_symbol(owner, class))
236        {
237            let resolution =
238                resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
239            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
240                return SemanticUsingEnumMemberResolution::Class(resolution);
241            }
242        }
243        for prefix_len in (0..=namespace.len()).rev() {
244            let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
245                continue;
246            };
247            let owners = imports
248                .iter()
249                .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
250                .map(|(_, owner)| owner);
251            let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
252            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
253                return SemanticUsingEnumMemberResolution::Namespace(resolution);
254            }
255        }
256        SemanticUsingEnumMemberResolution::Missing
257    }
258}
259
260fn resolve_using_enum_member_for_owners<'a>(
261    visibility: &VisibilityIndex<'_>,
262    file: &ProjectFile,
263    owners: impl IntoIterator<Item = &'a CodeUnit>,
264    name: &str,
265) -> UsingEnumMemberResolution {
266    let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
267    for owner in owners {
268        for member in visibility.visible_members_for_owner_name(file, owner, name) {
269            if !member.is_field()
270                || matches.iter().any(|(existing_owner, existing_member)| {
271                    same_visible_symbol(existing_owner, owner)
272                        && same_visible_symbol(existing_member, member)
273                })
274            {
275                continue;
276            }
277            matches.push((owner.clone(), member.clone()));
278        }
279    }
280    match matches.len() {
281        0 => UsingEnumMemberResolution::Missing,
282        1 => {
283            let (owner, member) = matches.pop().expect("one using-enum match");
284            UsingEnumMemberResolution::Resolved { owner, member }
285        }
286        _ => UsingEnumMemberResolution::Ambiguous,
287    }
288}
289
290impl ScopedUsingEnumOwners {
291    pub(crate) fn new() -> Self {
292        Self {
293            scopes: vec![Vec::new()],
294        }
295    }
296
297    pub fn enter_scope(&mut self) {
298        self.scopes.push(Vec::new());
299    }
300
301    pub fn exit_scope(&mut self) {
302        if self.scopes.len() > 1 {
303            self.scopes.pop();
304        }
305    }
306
307    pub fn import(&mut self, owner: CodeUnit) {
308        let scope = self
309            .scopes
310            .last_mut()
311            .expect("using-enum scope stack is never empty");
312        if !scope
313            .iter()
314            .any(|existing| same_visible_symbol(existing, &owner))
315        {
316            scope.push(owner);
317        }
318    }
319
320    pub fn resolve_member(
321        &self,
322        visibility: &VisibilityIndex<'_>,
323        file: &ProjectFile,
324        name: &str,
325    ) -> UsingEnumMemberResolution {
326        for scope in self.scopes.iter().rev() {
327            let resolution =
328                resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
329            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
330                return resolution;
331            }
332        }
333        UsingEnumMemberResolution::Missing
334    }
335}
336
337#[derive(Clone)]
338pub struct TargetSpec {
339    pub target: CodeUnit,
340    pub kind: TargetKind,
341    pub owner: Option<CodeUnit>,
342    pub member_name: String,
343    pub callable_arity: Option<CallableArity>,
344    pub activated_callable_arities: Vec<ActivatedCallableArity>,
345    pub param_types: Option<Vec<String>>,
346    pub enum_owner_kind: EnumOwnerKind,
347    pub owner_is_forward_declaration: bool,
348    pub callable_has_definition_body: bool,
349}
350
351#[derive(Clone, Copy)]
352pub struct ActivatedCallableArity {
353    pub activation_byte: usize,
354    pub arity: CallableArity,
355}
356
357#[derive(Debug, PartialEq, Eq, Hash)]
358pub struct TypeScanKey {
359    target: LogicalSymbolKey,
360    member_name: String,
361}
362
363#[derive(Clone, Debug, PartialEq, Eq, Hash)]
364struct LogicalSymbolKey {
365    kind: CodeUnitType,
366    fq_name: String,
367    signature: Option<String>,
368}
369
370struct ResolvedTypeOwner {
371    unit: CodeUnit,
372    is_forward_declaration: bool,
373}
374
375#[derive(Clone, Copy, PartialEq, Eq)]
376pub enum EnumOwnerKind {
377    Scoped,
378    Unscoped,
379    NonEnum,
380}
381
382impl TargetSpec {
383    pub fn type_scan_key(&self) -> Option<TypeScanKey> {
384        (self.kind == TargetKind::Type).then(|| TypeScanKey {
385            target: logical_symbol_key(&self.target),
386            member_name: self.member_name.clone(),
387        })
388    }
389
390    pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
391        if target.is_class() {
392            return Some(Self::new(
393                target.clone(),
394                TargetKind::Type,
395                Some(target.clone()),
396                target.identifier().to_string(),
397                None,
398                None,
399            ));
400        }
401
402        if target.is_field() {
403            // A namespace (module) is not a receiver: a namespace-scoped constant such as
404            // `example::DefaultPrefix` is referenced unqualified from inside the namespace and
405            // qualified from outside, exactly like a global. Treating a module owner as a
406            // member-field owner makes the receiver/owner-context match reject every valid
407            // reference, so resolve it as a global field instead.
408            let owner = type_owner_of(analyzer, target);
409            let kind = if owner.is_some() {
410                TargetKind::MemberField
411            } else {
412                TargetKind::GlobalField
413            };
414            let enum_owner_kind = owner
415                .as_ref()
416                .map(|owner| classify_enum_owner(analyzer, owner))
417                .unwrap_or(EnumOwnerKind::NonEnum);
418            let mut spec = Self::new(
419                target.clone(),
420                kind,
421                owner,
422                target.identifier().to_string(),
423                None,
424                None,
425            );
426            spec.enum_owner_kind = enum_owner_kind;
427            return Some(spec);
428        }
429
430        if target.is_function() {
431            // Free functions declared inside a namespace have a module owner; that namespace is
432            // not a call receiver, so resolve them as free functions rather than methods.
433            let owner_resolution = target_type_owner_resolution(analyzer, target);
434            let owner_is_forward_declaration = owner_resolution
435                .as_ref()
436                .is_some_and(|owner| owner.is_forward_declaration);
437            let owner = owner_resolution.map(|owner| owner.unit);
438            let kind = if owner.as_ref().is_some_and(|owner| {
439                target.identifier() == owner.identifier()
440                    || analyzer
441                        .cpp
442                        .and_then(|cpp| cpp.template_metadata(owner))
443                        .is_some_and(|metadata| metadata.primary_name == target.identifier())
444            }) {
445                TargetKind::Constructor
446            } else if owner.is_some() {
447                TargetKind::Method
448            } else {
449                TargetKind::FreeFunction
450            };
451            let mut spec = Self::new(
452                target.clone(),
453                kind,
454                owner,
455                target.identifier().to_string(),
456                Some(cpp_callable_arity(analyzer, target)),
457                cpp_callable_parameter_types(analyzer, target),
458            );
459            spec.owner_is_forward_declaration = owner_is_forward_declaration;
460            spec.callable_has_definition_body =
461                callable_target_has_definition_body(analyzer, target);
462            return Some(spec);
463        }
464
465        if target.is_macro() {
466            return Some(Self::new(
467                target.clone(),
468                TargetKind::Macro,
469                None,
470                target.identifier().to_string(),
471                None,
472                None,
473            ));
474        }
475
476        None
477    }
478
479    pub fn with_visible_callable_arities<'a>(
480        &'a self,
481        analyzer: &CppGraphSource<'_>,
482        cpp: &dyn CppSource,
483        visibility: &VisibilityIndex<'_>,
484        file: &ProjectFile,
485        prepared: &PreparedSyntaxTree,
486    ) -> Cow<'a, Self> {
487        let macro_parameter_arity =
488            visibility.callable_parameter_macro_arity(&self.target, self.target.signature());
489        let activated_callable_arities =
490            visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
491        if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
492            return Cow::Borrowed(self);
493        }
494        let mut effective = self.clone();
495        if let Some(macro_parameter_arity) = macro_parameter_arity {
496            effective.callable_arity = Some(macro_parameter_arity);
497        }
498        effective.activated_callable_arities = activated_callable_arities;
499        Cow::Owned(effective)
500    }
501
502    pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
503        let base = self.callable_arity?;
504        Some(
505            self.activated_callable_arities
506                .iter()
507                .filter(|candidate| candidate.activation_byte <= byte)
508                .fold(base, |arity, candidate| {
509                    merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
510                }),
511        )
512    }
513
514    pub fn new(
515        target: CodeUnit,
516        kind: TargetKind,
517        owner: Option<CodeUnit>,
518        member_name: String,
519        callable_arity: Option<CallableArity>,
520        param_types: Option<Vec<String>>,
521    ) -> Self {
522        Self {
523            target,
524            kind,
525            owner,
526            member_name,
527            callable_arity,
528            activated_callable_arities: Vec::new(),
529            param_types,
530            enum_owner_kind: EnumOwnerKind::NonEnum,
531            owner_is_forward_declaration: false,
532            callable_has_definition_body: false,
533        }
534    }
535}
536
537fn callable_target_has_definition_body(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> bool {
538    let Some(cpp) = analyzer.cpp else {
539        return false;
540    };
541    let Some(prepared) = cpp.prepared_syntax(analyzer.token, target.source()) else {
542        return false;
543    };
544    analyzer.ranges(target).into_iter().any(|range| {
545        let end = range
546            .start_byte
547            .saturating_add(1)
548            .min(prepared.source().len());
549        let mut current = prepared
550            .tree()
551            .root_node()
552            .descendant_for_byte_range(range.start_byte, end);
553        while let Some(node) = current {
554            match node.kind() {
555                "function_definition" => return true,
556                "declaration" => return false,
557                _ => current = node.parent(),
558            }
559        }
560        false
561    })
562}
563
564fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
565    LogicalSymbolKey {
566        kind: unit.kind(),
567        fq_name: unit.fq_name(),
568        signature: unit.signature().map(str::to_string),
569    }
570}
571
572fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
573    let classify = |source: &str| {
574        let source = source.trim_start();
575        if source.starts_with("enum class ") || source.starts_with("enum struct ") {
576            Some(EnumOwnerKind::Scoped)
577        } else if source.starts_with("enum ") {
578            Some(EnumOwnerKind::Unscoped)
579        } else {
580            None
581        }
582    };
583    owner
584        .signature()
585        .and_then(classify)
586        .or_else(|| {
587            analyzer
588                .get_source(owner, false)
589                .as_deref()
590                .and_then(classify)
591        })
592        .unwrap_or(EnumOwnerKind::NonEnum)
593}
594
595#[derive(Clone, PartialEq, Eq, Hash)]
596pub struct CppScanBinding {
597    pub unit: Option<CodeUnit>,
598    pub type_name: Option<String>,
599    pub indirection: i32,
600}
601
602impl CppScanBinding {
603    pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
604        Self {
605            type_name: Some(cpp_name_for(&unit)),
606            unit: Some(unit),
607            indirection,
608        }
609    }
610
611    pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
612        Self {
613            type_name: Some(type_name),
614            unit,
615            indirection,
616        }
617    }
618
619    pub fn as_arg_type(&self) -> Option<CppArgType> {
620        let name = self
621            .type_name
622            .clone()
623            .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
624        Some(CppArgType {
625            name,
626            unit: self.unit.clone(),
627            indirection: self.indirection,
628            pointee_const: false,
629        })
630    }
631}
632
633type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
634pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
635pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
636type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
637type MacroEnvironmentCheckpointCell = Arc<OnceLock<MacroEnvironmentCheckpoints>>;
638type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
639type MacroLocalBindingTemplateCache =
640    HashMap<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>;
641type MacroReplacementBodyCache = HashMap<(ProjectFile, usize), Option<Arc<ParsedReplacementBody>>>;
642
643#[derive(Clone, Default)]
644pub struct MacroEnvironment {
645    bindings: HashMap<String, MacroBinding>,
646    known_undefined_names: HashSet<String>,
647    /// Names the translation unit's compile command proves defined (#2011):
648    /// the `-D`s that survive command ordering, intersected across every
649    /// configuration naming the TU. Seeded once at TU start. An explicit
650    /// `#undef` seen later lands in `known_undefined_names` and wins.
651    build_proven_defines: HashSet<String>,
652    unknown_names: bool,
653    applied_pragma_once_files: HashSet<ProjectFile>,
654    maybe_applied_pragma_once_files: HashSet<ProjectFile>,
655}
656
657/// How many macro events one checkpoint window may cover.
658///
659/// A request for an environment replays only the events between the nearest
660/// earlier checkpoint and its own frontier, so one file's whole scan costs its
661/// event count (the checkpoint build) plus this many applications per request,
662/// whatever order the requests arrive in. The forward cursor this replaced was
663/// optimal for one worker reading one file in byte order and quadratic for the
664/// inverse, which asks many workers for positions that move backwards (#1496).
665pub const MACRO_ENVIRONMENT_CHECKPOINT_STRIDE: usize = 32;
666
667/// One event prefix of a file whose environment the index keeps.
668struct MacroEnvironmentCheckpoint {
669    /// How many of the file's events this environment has applied.
670    frontier: usize,
671    environment: Arc<MacroEnvironment>,
672}
673
674/// The replay checkpoints for one file's macro events, ascending by frontier
675/// and always starting at frontier zero (the compile-proven defines alone).
676///
677/// Checkpoints use an adaptive stride no greater than
678/// [`MACRO_ENVIRONMENT_CHECKPOINT_STRIDE`], and one lands directly after every
679/// `#include` event. Applying an include event replays the included file's
680/// complete event list, so keeping one there holds that unbounded cost out of
681/// every later replay window.
682struct MacroEnvironmentCheckpoints {
683    checkpoints: Vec<MacroEnvironmentCheckpoint>,
684}
685
686impl MacroEnvironmentCheckpoints {
687    /// The latest checkpoint at or before `frontier`.
688    fn at_or_before(&self, frontier: usize) -> &MacroEnvironmentCheckpoint {
689        let index = self
690            .checkpoints
691            .partition_point(|checkpoint| checkpoint.frontier <= frontier);
692        assert!(
693            index > 0,
694            "a checkpoint vector starts at frontier zero, which precedes every request"
695        );
696        &self.checkpoints[index - 1]
697    }
698}
699
700impl MacroEnvironment {
701    fn binding(&self, name: &str) -> Option<&MacroBinding> {
702        self.bindings.get(name)
703    }
704
705    fn may_bind(&self, name: &str) -> bool {
706        self.bindings.contains_key(name) || self.unknown_names
707    }
708
709    fn insert(&mut self, name: String, binding: MacroBinding) {
710        self.known_undefined_names.remove(&name);
711        self.bindings.insert(name, binding);
712    }
713
714    fn remove(&mut self, name: &str) {
715        self.bindings.remove(name);
716        self.known_undefined_names.insert(name.to_string());
717    }
718
719    fn remove_known_undefined(&mut self, name: &str) {
720        self.known_undefined_names.remove(name);
721    }
722
723    fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
724        for binding in self.bindings.values_mut() {
725            *binding = MacroBinding::uncertain_from(binding, source, byte);
726        }
727        self.known_undefined_names.clear();
728        // An untracked include could `#undef` a command-line define, so the
729        // may-hold filter must stop treating the build facts as decisive from
730        // here on. The additive proof path keeps its facts: they still hold at
731        // the include chain's activation point.
732        self.build_proven_defines.clear();
733        self.unknown_names = true;
734    }
735
736    fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
737        guards.iter().all(|guard| self.guard_may_hold(guard))
738    }
739
740    fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
741        let Some(expression) = guard.as_boolean_expression() else {
742            return true;
743        };
744        self.boolean_guard_may_hold(&expression)
745    }
746
747    fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
748        match expression {
749            BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
750            BooleanGuardExpression::Undefined(name) => {
751                self.bindings
752                    .get(name)
753                    .is_none_or(|binding| !binding.is_exact())
754                    && (!self.build_proven_defines.contains(name)
755                        || self.known_undefined_names.contains(name))
756            }
757            BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
758            BooleanGuardExpression::Opaque(_)
759            | BooleanGuardExpression::NegatedOpaque(_)
760            | BooleanGuardExpression::Constant(true) => true,
761            BooleanGuardExpression::Constant(false) => false,
762            BooleanGuardExpression::All(expressions) => expressions
763                .iter()
764                .all(|expression| self.boolean_guard_may_hold(expression)),
765            BooleanGuardExpression::Any(expressions) => expressions
766                .iter()
767                .any(|expression| self.boolean_guard_may_hold(expression)),
768        }
769    }
770}
771
772#[derive(Clone)]
773pub enum EffectiveUsingTarget {
774    Ordinary {
775        name: String,
776        target_components: Vec<String>,
777        global: bool,
778    },
779    Namespace {
780        namespace_components: Vec<String>,
781        global: bool,
782    },
783}
784
785#[derive(Clone)]
786pub struct OrdinaryTypeImport {
787    pub target: EffectiveUsingTarget,
788    pub source: ProjectFile,
789    pub declaration_byte: usize,
790    pub scope_start: usize,
791    pub scope_end: usize,
792    pub scope_depth: usize,
793    pub block_scope: bool,
794    pub lexical_depth: usize,
795    pub declaration_namespace: Vec<String>,
796    pub namespace_scope: Option<Vec<String>>,
797    pub resolved_target_components: Option<Vec<String>>,
798    pub required_guards: HashSet<PreprocessorGuard>,
799}
800
801#[derive(Clone)]
802pub struct ConditionalIncludeProjection {
803    pub activation_byte: usize,
804    pub required_guards: HashSet<PreprocessorGuard>,
805}
806
807#[derive(Default)]
808pub struct SourceUsingIndex {
809    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
810    pub directives: Vec<OrdinaryTypeImport>,
811}
812
813#[derive(Default)]
814pub struct ProjectUsingIndex {
815    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
816    pub directives: Vec<OrdinaryTypeImport>,
817}
818
819type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
820
821pub struct EffectiveUsingIndex {
822    projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
823}
824
825impl EffectiveUsingIndex {
826    fn new(_root: ProjectFile) -> Self {
827        Self {
828            projected_by_name: Mutex::new(HashMap::default()),
829        }
830    }
831
832    pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
833        self.projected_by_name
834            .lock()
835            .expect("C++ effective-using projection cache poisoned")
836            .entry(name.to_string())
837            .or_default()
838            .clone()
839    }
840}
841
842pub enum OrdinaryTypeImportResolution {
843    Resolved {
844        target: CodeUnit,
845        target_components: Vec<String>,
846        lexical_depth: usize,
847        is_direct: bool,
848    },
849    Ambiguous {
850        lexical_depth: usize,
851    },
852    Missing,
853}
854
855type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
856type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
857type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
858type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
859type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
860type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
861type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
862
863/// One callable declaration's inputs to [`VisibilityIndex::same_logical_callable`],
864/// read from its declaration syntax rather than from its persisted signature
865/// string: the comparable shape of each parameter, and the trailing identity
866/// suffix that shape does not carry.
867struct ExtractedComparable {
868    shapes: Vec<CppComparableSlot>,
869    suffix: String,
870}
871
872/// How many alias hops [`VisibilityIndex::same_logical_callable`] follows
873/// before giving up on a written type name. A visited set already stops a
874/// cycle; this stops an adversarially long chain from costing a lookup per hop.
875const MAX_COMPARABLE_ALIAS_HOPS: usize = 32;
876
877/// Per-query C++ visibility facts.
878///
879/// The analyzer is *borrowed*, never cloned: `TreeSitterAnalyzer::clone` gives
880/// the clone a fresh, empty `QueryReadCache` on purpose (clones cross
881/// generations and overlays, where another generation's hydrated states would
882/// be wrong). An index that owned a clone would therefore see an inactive read
883/// cache for every `prepared_syntax` call it makes, re-reading and re-parsing
884/// the same source from the store once per candidate instead of once per query
885/// — the #1175 blow-up, where one scan re-parsed a 4.8 MB generated header
886/// tens of thousands of times.
887pub struct VisibilityIndex<'a> {
888    cpp: &'a dyn CppSource,
889    /// Proof that the request scope the index was built under is still open.
890    /// The index is a per-query object whose lifetime is inside the scope's,
891    /// so carrying the token here instead of on ninety method signatures is
892    /// the same guarantee for far less plumbing (issue #2414 step 3).
893    token: QueryToken<'a>,
894    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
895    visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
896    global_field_internal_linkage: HashMap<CodeUnit, bool>,
897    visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
898    alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
899    visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
900    ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
901    project_using_index: OnceLock<ProjectUsingIndex>,
902    callable_reference_specs:
903        Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
904    include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
905    compile_proven_guard_cells: Mutex<HashMap<ProjectFile, Arc<HashSet<PreprocessorGuard>>>>,
906    conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
907    #[cfg(any(test, feature = "test-support"))]
908    conditional_include_projection_index_build_count: AtomicUsize,
909    #[cfg(any(test, feature = "test-support"))]
910    conditional_include_projection_state_count: AtomicUsize,
911    #[cfg(any(test, feature = "test-support"))]
912    conditional_include_target_state_count: AtomicUsize,
913    #[cfg(any(test, feature = "test-support"))]
914    include_activation_build_count: AtomicUsize,
915    #[cfg(any(test, feature = "test-support"))]
916    using_donor_activation_count: AtomicUsize,
917    #[cfg(any(test, feature = "test-support"))]
918    using_namespace_lookup_count: AtomicUsize,
919    #[cfg(any(test, feature = "test-support"))]
920    using_name_candidate_inspection_count: AtomicUsize,
921    #[cfg(any(test, feature = "test-support"))]
922    callable_reference_spec_build_count: AtomicUsize,
923    #[cfg(any(test, feature = "test-support"))]
924    alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
925    #[cfg(any(test, feature = "test-support"))]
926    visible_parser_alias_name_set_build_count: AtomicUsize,
927    parser_alias_fallback_calls: AtomicUsize,
928    parser_alias_fallback_files: AtomicUsize,
929    parser_alias_source_parses: AtomicUsize,
930    parser_alias_fallback_elapsed_micros: AtomicUsize,
931    field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
932    structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
933    callable_comparables: Mutex<HashMap<CodeUnit, Option<Arc<ExtractedComparable>>>>,
934    indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
935    indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
936    precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
937    c_tag_kind_cache: Mutex<HashMap<CodeUnit, Option<CppCTagKind>>>,
938    c_tag_complete_definition_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
939    macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
940    pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
941    // The environment at selected event prefixes of each file, built once and read by every
942    // worker. The authoritative differential shares this index across target workers whose
943    // frontiers interleave arbitrarily and move backwards, so a forward cursor -- per worker or
944    // not -- replayed a file's events once per backward request. Checkpoints answer any position
945    // with a binary search and at most one stride of replay, and being immutable they need no
946    // per-worker copy (#1496).
947    macro_environment_checkpoints: Mutex<HashMap<ProjectFile, MacroEnvironmentCheckpointCell>>,
948    macro_replacements: Mutex<MacroReplacementCache>,
949    macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
950    macro_replacement_bodies: Mutex<MacroReplacementBodyCache>,
951    callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
952    #[cfg(any(test, feature = "test-support"))]
953    pub macro_replacement_parse_count: AtomicUsize,
954    #[cfg(any(test, feature = "test-support"))]
955    pub macro_event_application_count: AtomicUsize,
956    /// How many files had their checkpoint vector built. One build per file
957    /// per query even when workers race for the same file.
958    #[cfg(any(test, feature = "test-support"))]
959    pub macro_environment_checkpoint_build_count: AtomicUsize,
960    /// How many requests landed off a checkpoint and so had to copy one and
961    /// replay the events after it.
962    #[cfg(any(test, feature = "test-support"))]
963    pub macro_environment_copy_count: AtomicUsize,
964    #[cfg(any(test, feature = "test-support"))]
965    pub macro_environment_request_count: AtomicUsize,
966    cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
967    cpp_template_families: HashMap<String, Vec<CodeUnit>>,
968    #[cfg(any(test, feature = "test-support"))]
969    qualified_candidate_inspections: AtomicUsize,
970    #[cfg(any(test, feature = "test-support"))]
971    target_preserving_type_resolution_count: AtomicUsize,
972}
973
974impl Drop for VisibilityIndex<'_> {
975    fn drop(&mut self) {
976        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_none() {
977            return;
978        }
979        #[cfg(any(test, feature = "test-support"))]
980        eprintln!(
981            "BIFROST_CPP_MACRO_STATS requests={} copies={} checkpoint_builds={} applications={}",
982            self.macro_environment_request_count.load(Ordering::Relaxed),
983            self.macro_environment_copy_count.load(Ordering::Relaxed),
984            self.macro_environment_checkpoint_build_count
985                .load(Ordering::Relaxed),
986            self.macro_event_application_count.load(Ordering::Relaxed),
987        );
988        let calls = self.parser_alias_fallback_calls.load(Ordering::Relaxed);
989        if calls == 0 {
990            return;
991        }
992        eprintln!(
993            "BIFROST_CPP_ALIAS_FALLBACK_STATS calls={} files={} source_parses={} elapsed_ms={}",
994            calls,
995            self.parser_alias_fallback_files.load(Ordering::Relaxed),
996            self.parser_alias_source_parses.load(Ordering::Relaxed),
997            self.parser_alias_fallback_elapsed_micros
998                .load(Ordering::Relaxed)
999                / 1_000,
1000        );
1001    }
1002}
1003
1004#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1005pub enum PreprocessorGuard {
1006    Defined(String),
1007    Undefined(String),
1008    Boolean(BooleanGuardExpression),
1009    Expression(String),
1010    NegatedExpression(String),
1011    Constant(bool),
1012}
1013
1014#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1015pub enum BooleanGuardExpression {
1016    Defined(String),
1017    Undefined(String),
1018    Truthy(String),
1019    Falsy(String),
1020    Opaque(String),
1021    NegatedOpaque(String),
1022    All(Vec<BooleanGuardExpression>),
1023    Any(Vec<BooleanGuardExpression>),
1024    Constant(bool),
1025}
1026
1027impl BooleanGuardExpression {
1028    fn negated(&self) -> Self {
1029        match self {
1030            Self::Defined(name) => Self::Undefined(name.clone()),
1031            Self::Undefined(name) => Self::Defined(name.clone()),
1032            Self::Truthy(name) => Self::Falsy(name.clone()),
1033            Self::Falsy(name) => Self::Truthy(name.clone()),
1034            Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
1035            Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
1036            Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
1037            Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
1038            Self::Constant(value) => Self::Constant(!value),
1039        }
1040    }
1041
1042    fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
1043        Self::normalized(expressions, true)
1044    }
1045
1046    fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
1047        Self::normalized(expressions, false)
1048    }
1049
1050    fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
1051        let mut normalized = Vec::new();
1052        for expression in expressions {
1053            match expression {
1054                Self::All(nested) if conjunction => normalized.extend(nested),
1055                Self::Any(nested) if !conjunction => normalized.extend(nested),
1056                Self::Constant(value) if value == conjunction => {}
1057                Self::Constant(value) => return Self::Constant(value),
1058                expression => normalized.push(expression),
1059            }
1060        }
1061        normalized.sort_unstable();
1062        normalized.dedup();
1063        match normalized.len() {
1064            0 => Self::Constant(conjunction),
1065            1 => normalized.pop().expect("one Boolean guard expression"),
1066            _ if conjunction => Self::All(normalized),
1067            _ => Self::Any(normalized),
1068        }
1069    }
1070
1071    fn implies(&self, required: &Self) -> bool {
1072        if self == required
1073            || matches!(self, Self::Constant(false))
1074            || matches!(required, Self::Constant(true))
1075        {
1076            return true;
1077        }
1078        if matches!(
1079            (self, required),
1080            (Self::Truthy(active), Self::Defined(required))
1081                | (Self::Undefined(active), Self::Falsy(required))
1082                if active == required
1083        ) {
1084            return true;
1085        }
1086        match self {
1087            Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
1088            Self::All(active) => match required {
1089                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1090                _ => active.iter().any(|expression| expression.implies(required)),
1091            },
1092            _ => match required {
1093                Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
1094                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1095                _ => false,
1096            },
1097        }
1098    }
1099
1100    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1101        match self {
1102            Self::Defined(name)
1103            | Self::Undefined(name)
1104            | Self::Truthy(name)
1105            | Self::Falsy(name) => name == macro_name,
1106            // Opaque expressions have structured conditional ownership but no
1107            // structured macro operands, so any mutation may change them.
1108            Self::Opaque(_) | Self::NegatedOpaque(_) => true,
1109            Self::All(expressions) | Self::Any(expressions) => expressions
1110                .iter()
1111                .any(|expression| expression.may_depend_on_macro(macro_name)),
1112            Self::Constant(_) => false,
1113        }
1114    }
1115
1116    pub fn heap_size(&self) -> usize {
1117        match self {
1118            Self::Defined(value)
1119            | Self::Undefined(value)
1120            | Self::Truthy(value)
1121            | Self::Falsy(value)
1122            | Self::Opaque(value)
1123            | Self::NegatedOpaque(value) => value.len(),
1124            Self::All(expressions) | Self::Any(expressions) => {
1125                expressions
1126                    .iter()
1127                    .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
1128                        size.saturating_add(std::mem::size_of::<Self>())
1129                            .saturating_add(expression.heap_size())
1130                    })
1131            }
1132            Self::Constant(_) => 0,
1133        }
1134    }
1135}
1136
1137impl PreprocessorGuard {
1138    fn as_boolean_expression(&self) -> Option<BooleanGuardExpression> {
1139        match self {
1140            Self::Defined(name) => Some(BooleanGuardExpression::Defined(name.clone())),
1141            Self::Undefined(name) => Some(BooleanGuardExpression::Undefined(name.clone())),
1142            Self::Boolean(expression) => Some(expression.clone()),
1143            Self::Constant(value) => Some(BooleanGuardExpression::Constant(*value)),
1144            Self::Expression(_) | Self::NegatedExpression(_) => None,
1145        }
1146    }
1147
1148    fn negated(&self) -> Self {
1149        match self {
1150            Self::Defined(name) => Self::Undefined(name.clone()),
1151            Self::Undefined(name) => Self::Defined(name.clone()),
1152            Self::Boolean(expression) => Self::Boolean(expression.negated()),
1153            Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1154            Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1155            Self::Constant(value) => Self::Constant(!value),
1156        }
1157    }
1158
1159    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1160        match self {
1161            Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1162            Self::Boolean(expression) => expression.may_depend_on_macro(macro_name),
1163            // These expressions could not be lowered to a Boolean operand
1164            // tree, so their dependencies remain unknown.
1165            Self::Expression(_) | Self::NegatedExpression(_) => true,
1166            Self::Constant(_) => false,
1167        }
1168    }
1169}
1170
1171#[derive(Clone, PartialEq, Eq)]
1172pub enum MacroDefinition {
1173    Object {
1174        replacement: String,
1175    },
1176    Function {
1177        parameters: Vec<String>,
1178        replacement: String,
1179    },
1180    Unsupported,
1181}
1182
1183#[derive(Clone, Debug, PartialEq, Eq)]
1184pub enum MacroIncludeProtection {
1185    MacroGuard(String),
1186    PragmaOnce,
1187    None,
1188}
1189
1190enum ParsedMacroReplacement {
1191    Parsed { source: String, tree: Tree },
1192    Unsupported,
1193}
1194
1195/// The sentinel that gives a function-like macro replacement a parseable
1196/// statement context. The replacement text is copied in verbatim, so the only
1197/// bytes ahead of it are this prefix.
1198const MACRO_BODY_SENTINEL_PREFIX: &str = "void __bifrost_macro_body() { ";
1199
1200/// A function-like macro replacement parsed inside a sentinel function body.
1201///
1202/// Tree-sitter keeps a `#define NAME(a) ...` replacement as one opaque
1203/// `preproc_arg`. Wrapping that exact byte slice in a function body recovers
1204/// its statements, declarations, and member calls as ordinary C++ structure.
1205/// The slice is copied verbatim at [`Self::body_offset`], so a node range in
1206/// [`Self::tree`] maps back onto the defining `preproc_arg` by subtracting
1207/// that offset.
1208pub struct ParsedReplacementBody {
1209    pub source: String,
1210    pub tree: Tree,
1211    pub body_offset: usize,
1212    pub parameters: Vec<String>,
1213}
1214
1215impl ParsedReplacementBody {
1216    /// The sentinel function body holding the replacement's statements.
1217    pub fn statements(&self) -> Option<Node<'_>> {
1218        first_descendant_of_kind(self.tree.root_node(), "function_definition")?
1219            .child_by_field_name("body")
1220    }
1221
1222    /// The byte range `node` occupies in the file that defines the macro.
1223    ///
1224    /// `replacement_start` is the defining `preproc_arg`'s start byte. The
1225    /// replacement is copied into the sentinel verbatim, so subtracting the
1226    /// body offset and adding that start is exact.
1227    pub fn file_range(&self, node: Node<'_>, replacement_start: usize) -> std::ops::Range<usize> {
1228        debug_assert!(node.start_byte() >= self.body_offset);
1229        let start = replacement_start + (node.start_byte() - self.body_offset);
1230        start..start + (node.end_byte() - node.start_byte())
1231    }
1232
1233    /// Whether the replacement names the variadic argument pack.
1234    ///
1235    /// `__VA_ARGS__` parses as an ordinary identifier, so the sentinel tree
1236    /// gives no error for it even though the expansion it stands for is
1237    /// unknown at the definition. Reject it from the parsed tree rather than
1238    /// by scanning the replacement text.
1239    fn expands_variadic_arguments(&self) -> bool {
1240        let mut stack = vec![self.tree.root_node()];
1241        while let Some(node) = stack.pop() {
1242            if matches!(
1243                node.kind(),
1244                "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
1245            ) && node_text(node, &self.source) == "__VA_ARGS__"
1246            {
1247                return true;
1248            }
1249            for index in (0..node.named_child_count()).rev() {
1250                if let Some(child) = node.named_child(index) {
1251                    stack.push(child);
1252                }
1253            }
1254        }
1255        false
1256    }
1257}
1258
1259fn parse_cpp_integer_literal(text: &str) -> Option<i128> {
1260    let compact = text.chars().filter(|ch| *ch != '\'').collect::<String>();
1261    let (radix, digits_start, digit_matches): (u32, usize, fn(char) -> bool) =
1262        if compact.starts_with("0x") || compact.starts_with("0X") {
1263            (16, 2, |ch| ch.is_ascii_hexdigit())
1264        } else if compact.starts_with("0b") || compact.starts_with("0B") {
1265            (2, 2, |ch| matches!(ch, '0' | '1'))
1266        } else if compact.starts_with('0') && compact.len() > 1 {
1267            (8, 0, |ch| matches!(ch, '0'..='7'))
1268        } else {
1269            (10, 0, |ch| ch.is_ascii_digit())
1270        };
1271    let digit_len = compact[digits_start..]
1272        .chars()
1273        .take_while(|ch| digit_matches(*ch))
1274        .map(char::len_utf8)
1275        .sum::<usize>();
1276    if digit_len == 0 {
1277        return None;
1278    }
1279    let digits_end = digits_start + digit_len;
1280    if !compact[digits_end..]
1281        .chars()
1282        .all(|ch| matches!(ch, 'u' | 'U' | 'l' | 'L' | 'z' | 'Z'))
1283    {
1284        return None;
1285    }
1286    i128::from_str_radix(&compact[digits_start..digits_end], radix).ok()
1287}
1288
1289#[derive(Clone)]
1290enum MacroLocalBindingTypeTemplate {
1291    Parameter(usize),
1292    Fixed(String),
1293}
1294
1295#[derive(Clone)]
1296struct MacroLocalBindingTemplate {
1297    name: String,
1298    declared_type: MacroLocalBindingTypeTemplate,
1299    pointer_depth: i32,
1300}
1301
1302/// A local declaration contributed by one structurally known function-like macro.
1303///
1304/// `type_node` points into the invocation syntax when the replacement's type
1305/// is one of the macro parameters. Consumers can therefore use their normal
1306/// lexical type resolver without parsing replacement text themselves.
1307/// `proven_unit` carries a structured C-tag resolution when the grammar splits
1308/// an explicit `struct` or `union` argument across recovery nodes.
1309pub struct MacroLocalBinding<'tree> {
1310    pub name: String,
1311    pub type_name: String,
1312    pub type_node: Option<Node<'tree>>,
1313    pub pointer_depth: i32,
1314    pub proven_unit: Option<CodeUnit>,
1315}
1316
1317fn macro_replacement_type_parameter(
1318    body: &ParsedReplacementBody,
1319    parameters: &[String],
1320) -> Option<usize> {
1321    let mut found = None;
1322    let mut stack = vec![body.tree.root_node()];
1323    while let Some(node) = stack.pop() {
1324        let type_position = node.kind() == "type_identifier"
1325            || (node.kind() == "identifier"
1326                && node.parent().is_some_and(|parent| {
1327                    parent.kind() == "type_descriptor"
1328                        && parent.child_by_field_name("type") == Some(node)
1329                }));
1330        let offsetof_type_position = node.kind() == "identifier"
1331            && node
1332                .parent()
1333                .filter(|parent| parent.kind() == "argument_list")
1334                .and_then(|arguments| arguments.parent())
1335                .is_some_and(|call| {
1336                    call.kind() == "call_expression"
1337                        && call
1338                            .child_by_field_name("function")
1339                            .is_some_and(|function| {
1340                                function.kind() == "identifier"
1341                                    && node_text(function, &body.source) == "offsetof"
1342                            })
1343                        && call
1344                            .child_by_field_name("arguments")
1345                            .is_some_and(|arguments| {
1346                                argument_children(arguments).next() == Some(node)
1347                            })
1348                });
1349        if (type_position || offsetof_type_position)
1350            && let Some(index) = parameters
1351                .iter()
1352                .position(|parameter| parameter == node_text(node, &body.source))
1353        {
1354            if found.is_some_and(|existing| existing != index) {
1355                return None;
1356            }
1357            found = Some(index);
1358        }
1359        for index in (0..node.named_child_count()).rev() {
1360            if let Some(child) = node.named_child(index) {
1361                stack.push(child);
1362            }
1363        }
1364    }
1365    found
1366}
1367
1368fn macro_type_argument_node<'tree>(node: Node<'tree>, source: &str) -> Option<Node<'tree>> {
1369    match node.kind() {
1370        "type_descriptor" => {
1371            let type_child = node
1372                .child_by_field_name("type")
1373                .or_else(|| first_type_child(node))?;
1374            for index in (0..node.named_child_count()).rev() {
1375                let child = node.named_child(index)?;
1376                if child != type_child
1377                    && matches!(
1378                        child.kind(),
1379                        "identifier"
1380                            | "type_identifier"
1381                            | "qualified_identifier"
1382                            | "scoped_type_identifier"
1383                    )
1384                {
1385                    return Some(child);
1386                }
1387            }
1388            macro_type_argument_node(type_child, source)
1389        }
1390        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => {
1391            node.child_by_field_name("name")
1392        }
1393        "identifier" if matches!(node_text(node, source), "struct" | "union") => node
1394            .next_named_sibling()
1395            .filter(|sibling| sibling.is_error() && sibling.named_child_count() == 1)
1396            .and_then(|error| error.named_child(0))
1397            .filter(|name| matches!(name.kind(), "identifier" | "type_identifier")),
1398        _ => cpp_name_component_nodes(node).is_some().then_some(node),
1399    }
1400}
1401
1402/// Recover GLib's `g_autoptr(T) name = value` declaration from the CST shape
1403/// produced by tree-sitter-cpp for C source. The grammar retains the macro
1404/// invocation as the assignment's left operand and the declared name as one
1405/// adjacent `ERROR(identifier)` node, so no macro text splitting is needed.
1406fn recognized_c_macro_declarator_binding<'tree>(
1407    statement: Node<'tree>,
1408    source: &str,
1409) -> Option<MacroLocalBinding<'tree>> {
1410    let assignment = match statement.kind() {
1411        "assignment_expression" => statement,
1412        "expression_statement" if statement.named_child_count() == 1 => statement.named_child(0)?,
1413        _ => return None,
1414    };
1415    if assignment.kind() != "assignment_expression" {
1416        return None;
1417    }
1418    let call = assignment.child_by_field_name("left")?;
1419    if call.kind() != "call_expression" {
1420        return None;
1421    }
1422    let function = call.child_by_field_name("function")?;
1423    if function.kind() != "identifier" || node_text(function, source) != "g_autoptr" {
1424        return None;
1425    }
1426    let arguments = call.child_by_field_name("arguments")?;
1427    let mut actuals = argument_children(arguments);
1428    let type_node = actuals.next()?;
1429    if actuals.next().is_some()
1430        || !matches!(
1431            type_node.kind(),
1432            "identifier"
1433                | "type_identifier"
1434                | "qualified_identifier"
1435                | "scoped_type_identifier"
1436                | "template_type"
1437        )
1438    {
1439        return None;
1440    }
1441    let name_node = (0..assignment.named_child_count())
1442        .filter_map(|index| assignment.named_child(index))
1443        .filter(|child| child.kind() == "ERROR")
1444        .filter_map(|error| {
1445            (error.named_child_count() == 1)
1446                .then(|| error.named_child(0))
1447                .flatten()
1448        })
1449        .find(|node| node.kind() == "identifier")?;
1450    let name = node_text(name_node, source).trim();
1451    let type_name = node_text(type_node, source).trim();
1452    if name.is_empty() || type_name.is_empty() {
1453        return None;
1454    }
1455    Some(MacroLocalBinding {
1456        name: name.to_string(),
1457        type_name: type_name.to_string(),
1458        type_node: Some(type_node),
1459        pointer_depth: 1,
1460        proven_unit: None,
1461    })
1462}
1463
1464#[derive(Clone, PartialEq, Eq)]
1465pub struct MacroBinding {
1466    source: ProjectFile,
1467    declaration_byte: usize,
1468    definition: MacroDefinition,
1469    exact: bool,
1470}
1471
1472impl MacroBinding {
1473    fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1474        Self {
1475            source: source.clone(),
1476            declaration_byte,
1477            definition: MacroDefinition::Unsupported,
1478            exact: false,
1479        }
1480    }
1481
1482    fn is_exact(&self) -> bool {
1483        self.exact
1484    }
1485
1486    fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1487        Self {
1488            source: source.clone(),
1489            declaration_byte,
1490            definition: current.definition.clone(),
1491            exact: false,
1492        }
1493    }
1494}
1495
1496/// The preprocessor conditionals whose truth decides whether one macro event
1497/// applies, by the start byte of each conditional node. Empty means the event
1498/// is unconditional.
1499///
1500/// [`VisibilityIndex::macro_event_condition_value`] needs exactly the
1501/// conditional ancestors that structurally contain the event, and deciding
1502/// containment means asking [`cpp_displaced_preprocessor_boundary`] for an
1503/// `#endif` tree-sitter displaced into error recovery -- a walk of the
1504/// conditional's whole subtree. That answer is a fact about the tree alone, so
1505/// it is settled once, when the events are collected, instead of on every
1506/// replay of the event (#1496).
1507type OwningPreprocessorConditionals = Box<[usize]>;
1508
1509#[derive(Clone)]
1510pub enum MacroEvent {
1511    Define {
1512        name: String,
1513        binding: MacroBinding,
1514        byte: usize,
1515        conditionals: OwningPreprocessorConditionals,
1516    },
1517    Undef {
1518        name: String,
1519        byte: usize,
1520        conditionals: OwningPreprocessorConditionals,
1521    },
1522    Include {
1523        targets: Vec<ProjectFile>,
1524        byte: usize,
1525        conditionals: OwningPreprocessorConditionals,
1526    },
1527    Invalidate {
1528        byte: usize,
1529    },
1530}
1531
1532impl MacroEvent {
1533    pub fn byte(&self) -> usize {
1534        match self {
1535            Self::Define { byte, .. }
1536            | Self::Undef { byte, .. }
1537            | Self::Include { byte, .. }
1538            | Self::Invalidate { byte } => *byte,
1539        }
1540    }
1541}
1542
1543#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1544pub enum CallArityEvidence {
1545    Exact(usize),
1546    Unknown,
1547}
1548
1549impl CallArityEvidence {
1550    pub fn exact(self) -> Option<usize> {
1551        match self {
1552            Self::Exact(arity) => Some(arity),
1553            Self::Unknown => None,
1554        }
1555    }
1556
1557    pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1558        self.exact().map(|arity| expected.accepts(arity))
1559    }
1560}
1561
1562#[derive(Clone)]
1563struct DeclaredFieldTypeFact {
1564    type_text: String,
1565    indirection: i32,
1566    template_arguments: Option<Vec<CppTemplateExpression>>,
1567}
1568
1569#[derive(Clone, PartialEq, Eq)]
1570enum StructuredAliasTarget {
1571    Builtin,
1572    Named {
1573        components: Vec<String>,
1574        global: bool,
1575        arguments: Option<Vec<CppTemplateExpression>>,
1576    },
1577}
1578
1579struct CppAlias {
1580    name: String,
1581    target: String,
1582    namespace: Option<String>,
1583}
1584
1585type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1586
1587/// Why template-argument resolution failed. Definition diagnostics render
1588/// each mode differently; graph scans only care that the resolution is
1589/// unproven and match `Err(_)`.
1590#[derive(Debug, Clone, PartialEq, Eq)]
1591pub enum CppTemplateResolutionError {
1592    /// A template alias expansion revisited `alias`.
1593    AliasCycle { alias: CodeUnit },
1594    /// The explicit arguments do not bind to the declared template parameters.
1595    ArgumentBinding,
1596    /// Bound arguments do not substitute into the alias target's arguments.
1597    Substitution,
1598    /// No visible primary template declaration could be selected and
1599    /// reconciled for the specialization family.
1600    PrimarySelection,
1601    /// More than one applicable specialization remains and none is strictly
1602    /// more specialized than every other candidate.
1603    AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1604}
1605
1606/// The ambiguity candidates, deduplicated to one representative per visible
1607/// symbol so a diagnostic lists each contender once.
1608fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1609    let mut distinct: Vec<CodeUnit> = Vec::new();
1610    for unit in units {
1611        if !distinct
1612            .iter()
1613            .any(|existing| same_visible_symbol(existing, unit))
1614        {
1615            distinct.push(unit.clone());
1616        }
1617    }
1618    distinct
1619}
1620
1621impl<'a> VisibilityIndex<'a> {
1622    pub fn cpp(&self) -> &'a dyn CppSource {
1623        self.cpp
1624    }
1625
1626    /// The request-scope proof this index was built with (issue #2414 step 3).
1627    pub fn token(&self) -> QueryToken<'a> {
1628        self.token
1629    }
1630
1631    /// A [`VisibilityIndex`] over a caller-supplied visible-declaration map,
1632    /// bypassing the include-closure walk [`Self::build`] performs.
1633    ///
1634    /// The resolver's own unit tests drive the type-resolution paths against a
1635    /// hand-written visibility table; they live in `brokk-bifrost-analysis`
1636    /// because they need a real `CppAnalyzer`, so the struct literal they used
1637    /// to write inline is here instead of thirty-three public fields.
1638    #[cfg(any(test, feature = "test-support"))]
1639    pub fn from_visible_files_for_test(
1640        cpp: &'a dyn CppSource,
1641        token: QueryToken<'a>,
1642        visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1643    ) -> Self {
1644        let visible_source_files_by_root = visible_by_file
1645            .iter()
1646            .map(|(file, visible)| {
1647                (
1648                    file.clone(),
1649                    visible
1650                        .iter()
1651                        .map(|unit| unit.source().clone())
1652                        .chain(std::iter::once(file.clone()))
1653                        .collect(),
1654                )
1655            })
1656            .collect();
1657        let mut global_field_internal_linkage = HashMap::default();
1658        Self {
1659            cpp,
1660            token,
1661            visible_by_identifier: build_visible_identifier_index(
1662                &CppGraphSource::from_source(cpp, token),
1663                &visible_by_file,
1664                &visible_source_files_by_root,
1665                &mut global_field_internal_linkage,
1666            ),
1667            global_field_internal_linkage,
1668            visible_by_file,
1669            visible_source_files_by_root,
1670            alias_cells: Mutex::new(HashMap::default()),
1671            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1672            ordinary_type_import_cells: Mutex::new(HashMap::default()),
1673            project_using_index: OnceLock::new(),
1674            callable_reference_specs: Mutex::new(HashMap::default()),
1675            include_activation_cells: Mutex::new(HashMap::default()),
1676            compile_proven_guard_cells: Mutex::new(HashMap::default()),
1677            conditional_include_projection_cells: Mutex::new(HashMap::default()),
1678            conditional_include_projection_index_build_count: AtomicUsize::new(0),
1679            conditional_include_projection_state_count: AtomicUsize::new(0),
1680            conditional_include_target_state_count: AtomicUsize::new(0),
1681            include_activation_build_count: AtomicUsize::new(0),
1682            using_donor_activation_count: AtomicUsize::new(0),
1683            using_namespace_lookup_count: AtomicUsize::new(0),
1684            using_name_candidate_inspection_count: AtomicUsize::new(0),
1685            callable_reference_spec_build_count: AtomicUsize::new(0),
1686            alias_source_parse_counts: Mutex::new(HashMap::default()),
1687            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1688            parser_alias_fallback_calls: AtomicUsize::new(0),
1689            parser_alias_fallback_files: AtomicUsize::new(0),
1690            parser_alias_source_parses: AtomicUsize::new(0),
1691            parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
1692            field_type_facts: Mutex::new(HashMap::default()),
1693            structured_alias_targets: Mutex::new(HashMap::default()),
1694            callable_comparables: Mutex::new(HashMap::default()),
1695            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1696            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1697            precise_parent_cache: Mutex::new(HashMap::default()),
1698            c_tag_kind_cache: Mutex::new(HashMap::default()),
1699            c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
1700            macro_event_cells: Mutex::new(HashMap::default()),
1701            macro_include_protection_cells: Mutex::new(HashMap::default()),
1702            macro_environment_checkpoints: Mutex::new(HashMap::default()),
1703            macro_replacements: Mutex::new(HashMap::default()),
1704            macro_local_binding_templates: Mutex::new(HashMap::default()),
1705            macro_replacement_bodies: Mutex::new(HashMap::default()),
1706            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1707            macro_replacement_parse_count: AtomicUsize::new(0),
1708            macro_event_application_count: AtomicUsize::new(0),
1709            macro_environment_checkpoint_build_count: AtomicUsize::new(0),
1710            macro_environment_copy_count: AtomicUsize::new(0),
1711            macro_environment_request_count: AtomicUsize::new(0),
1712            cpp_template_metadata: HashMap::default(),
1713            cpp_template_families: HashMap::default(),
1714            qualified_candidate_inspections: AtomicUsize::new(0),
1715            target_preserving_type_resolution_count: AtomicUsize::new(0),
1716        }
1717    }
1718
1719    /// The index's own C++ source, in the dispatching-analyzer shape.
1720    ///
1721    /// Four resolution paths reach the workspace through the C++ analyzer they
1722    /// already hold rather than through the analyzer the query was issued
1723    /// against; before the move they passed `&CppAnalyzer` straight into a
1724    /// `&dyn IAnalyzer` parameter. See [`CppGraphSource::from_source`].
1725    fn cpp_source(&self) -> CppGraphSource<'a> {
1726        CppGraphSource::from_source(self.cpp, self.token)
1727    }
1728
1729    pub fn build(
1730        cpp: &'a dyn CppSource,
1731        token: QueryToken<'a>,
1732        analyzer: &CppGraphSource<'_>,
1733        roots: &HashSet<ProjectFile>,
1734    ) -> Self {
1735        Self::build_with_cancellation(cpp, token, analyzer, roots, None)
1736    }
1737
1738    pub fn build_with_cancellation(
1739        cpp: &'a dyn CppSource,
1740        token: QueryToken<'a>,
1741        analyzer: &CppGraphSource<'_>,
1742        roots: &HashSet<ProjectFile>,
1743        cancellation: Option<&CancellationToken>,
1744    ) -> Self {
1745        let visibility_started = Instant::now();
1746        let include_targets = cpp.include_target_index();
1747        let includes_started = Instant::now();
1748        let mut include_graph = IncludeGraph::default();
1749        for root in roots {
1750            include_graph.extend_with(root, cancellation, &mut |file| {
1751                cpp_include_paths(&cpp.visibility_import_statements(token, file))
1752                    .into_iter()
1753                    .flat_map(|include| {
1754                        resolve_include_targets_with_index(file, &include, include_targets)
1755                    })
1756                    .collect()
1757            });
1758        }
1759        let include_elapsed = includes_started.elapsed();
1760        let include_file_count = include_graph.files().count();
1761        let visible_source_files_by_root = roots
1762            .iter()
1763            .map(|root| {
1764                (
1765                    root.clone(),
1766                    include_graph.reachable_files(root, cancellation),
1767                )
1768            })
1769            .collect::<HashMap<_, _>>();
1770        let mut visibility_stats = BoundedVisibilityStats::default();
1771        let mut visible_by_file = build_bounded_visible_declarations(
1772            cpp,
1773            token,
1774            analyzer,
1775            roots,
1776            &visible_source_files_by_root,
1777            cancellation,
1778            &mut visibility_stats,
1779        );
1780        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
1781            eprintln!(
1782                "BIFROST_CPP_VISIBILITY_STATS total_ms={} include_ms={} include_files={} rounds={} root_names={} identifier_lookups={} candidate_units={} candidate_sources={} declaration_reads={} declaration_units={} selected_units={} dependency_ast_nodes={} dependency_names={} lookup_ms={} declaration_ms={} dependency_ast_ms={}",
1783                visibility_started.elapsed().as_millis(),
1784                include_elapsed.as_millis(),
1785                include_file_count,
1786                visibility_stats.rounds,
1787                visibility_stats.root_names,
1788                visibility_stats.identifier_lookups,
1789                visibility_stats.candidate_units,
1790                visibility_stats.candidate_sources,
1791                visibility_stats.declaration_reads,
1792                visibility_stats.declaration_units,
1793                visibility_stats.selected_units,
1794                visibility_stats.dependency_ast_nodes,
1795                visibility_stats.dependency_names,
1796                visibility_stats.lookup_elapsed.as_millis(),
1797                visibility_stats.declaration_elapsed.as_millis(),
1798                visibility_stats.dependency_ast_elapsed.as_millis(),
1799            );
1800        }
1801        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
1802        let finalize_started = Instant::now();
1803        if report_stats {
1804            eprintln!(
1805                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=started roots={} visible_units={}",
1806                visible_by_file.len(),
1807                visible_by_file.values().map(HashSet::len).sum::<usize>(),
1808            );
1809        }
1810        let owner_started = Instant::now();
1811        if report_stats {
1812            eprintln!("BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=started");
1813        }
1814        let owner_stats = extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
1815        if report_stats {
1816            eprintln!(
1817                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=completed unseen_owners={} definition_lookups={} admitted={} elapsed_ms={}",
1818                owner_stats.unseen_owners,
1819                owner_stats.definition_lookups,
1820                owner_stats.admitted,
1821                owner_started.elapsed().as_millis(),
1822            );
1823        }
1824        let mut global_field_internal_linkage = HashMap::default();
1825        let identifier_started = Instant::now();
1826        if report_stats {
1827            eprintln!(
1828                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=started"
1829            );
1830        }
1831        let visible_by_identifier = build_visible_identifier_index(
1832            analyzer,
1833            &visible_by_file,
1834            &visible_source_files_by_root,
1835            &mut global_field_internal_linkage,
1836        );
1837        if report_stats {
1838            eprintln!(
1839                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=completed roots={} names={} candidates={} elapsed_ms={}",
1840                visible_by_identifier.len(),
1841                visible_by_identifier
1842                    .values()
1843                    .map(HashMap::len)
1844                    .sum::<usize>(),
1845                visible_by_identifier
1846                    .values()
1847                    .flat_map(HashMap::values)
1848                    .map(Vec::len)
1849                    .sum::<usize>(),
1850                identifier_started.elapsed().as_millis(),
1851            );
1852        }
1853        let mut cpp_template_metadata = HashMap::default();
1854        let metadata_started = Instant::now();
1855        let mut template_classes = 0usize;
1856        if report_stats {
1857            eprintln!(
1858                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=started"
1859            );
1860        }
1861        for unit in visible_by_file
1862            .values()
1863            .flatten()
1864            .filter(|unit| unit.is_class())
1865        {
1866            template_classes += 1;
1867            if cpp_template_metadata.contains_key(unit) {
1868                continue;
1869            }
1870            if let Some(metadata) = cpp.template_metadata(unit) {
1871                cpp_template_metadata.insert(unit.clone(), metadata);
1872            }
1873        }
1874        if report_stats {
1875            eprintln!(
1876                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=completed classes={} metadata={} elapsed_ms={}",
1877                template_classes,
1878                cpp_template_metadata.len(),
1879                metadata_started.elapsed().as_millis(),
1880            );
1881        }
1882        let families_started = Instant::now();
1883        if report_stats {
1884            eprintln!(
1885                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=started"
1886            );
1887        }
1888        let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
1889        for (unit, metadata) in &cpp_template_metadata {
1890            cpp_template_families
1891                .entry(metadata.primary_fq_name.clone())
1892                .or_default()
1893                .push(unit.clone());
1894        }
1895        // `cpp_template_metadata` is hash-keyed on `CodeUnit`, so the push
1896        // order above is a function of those hashes. Two mirrored headers can
1897        // declare one specialization; `select_template_specialization` treats
1898        // them as interchangeable and returns the family's first entry, so an
1899        // unsorted family made the reported declaration depend on the
1900        // workspace's absolute path and on unrelated files (#1836). Order the
1901        // family exactly as `build_visible_identifier_index` orders its
1902        // per-identifier candidate lists.
1903        for family in cpp_template_families.values_mut() {
1904            sort_lookup_units(family);
1905        }
1906        if report_stats {
1907            eprintln!(
1908                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=completed families={} members={} elapsed_ms={}",
1909                cpp_template_families.len(),
1910                cpp_template_families.values().map(Vec::len).sum::<usize>(),
1911                families_started.elapsed().as_millis(),
1912            );
1913            eprintln!(
1914                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=completed roots={} visible_units={} elapsed_ms={} total_ms={}",
1915                visible_by_file.len(),
1916                visible_by_file.values().map(HashSet::len).sum::<usize>(),
1917                finalize_started.elapsed().as_millis(),
1918                visibility_started.elapsed().as_millis(),
1919            );
1920        }
1921        Self {
1922            cpp,
1923            token,
1924            visible_by_file,
1925            visible_by_identifier,
1926            global_field_internal_linkage,
1927            visible_source_files_by_root,
1928            alias_cells: Mutex::new(HashMap::default()),
1929            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1930            ordinary_type_import_cells: Mutex::new(HashMap::default()),
1931            project_using_index: OnceLock::new(),
1932            callable_reference_specs: Mutex::new(HashMap::default()),
1933            include_activation_cells: Mutex::new(HashMap::default()),
1934            compile_proven_guard_cells: Mutex::new(HashMap::default()),
1935            conditional_include_projection_cells: Mutex::new(HashMap::default()),
1936            #[cfg(any(test, feature = "test-support"))]
1937            conditional_include_projection_index_build_count: AtomicUsize::new(0),
1938            #[cfg(any(test, feature = "test-support"))]
1939            conditional_include_projection_state_count: AtomicUsize::new(0),
1940            #[cfg(any(test, feature = "test-support"))]
1941            conditional_include_target_state_count: AtomicUsize::new(0),
1942            #[cfg(any(test, feature = "test-support"))]
1943            include_activation_build_count: AtomicUsize::new(0),
1944            #[cfg(any(test, feature = "test-support"))]
1945            using_donor_activation_count: AtomicUsize::new(0),
1946            #[cfg(any(test, feature = "test-support"))]
1947            using_namespace_lookup_count: AtomicUsize::new(0),
1948            #[cfg(any(test, feature = "test-support"))]
1949            using_name_candidate_inspection_count: AtomicUsize::new(0),
1950            #[cfg(any(test, feature = "test-support"))]
1951            callable_reference_spec_build_count: AtomicUsize::new(0),
1952            #[cfg(any(test, feature = "test-support"))]
1953            alias_source_parse_counts: Mutex::new(HashMap::default()),
1954            #[cfg(any(test, feature = "test-support"))]
1955            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1956            parser_alias_fallback_calls: AtomicUsize::new(0),
1957            parser_alias_fallback_files: AtomicUsize::new(0),
1958            parser_alias_source_parses: AtomicUsize::new(0),
1959            parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
1960            field_type_facts: Mutex::new(HashMap::default()),
1961            structured_alias_targets: Mutex::new(HashMap::default()),
1962            callable_comparables: Mutex::new(HashMap::default()),
1963            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1964            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1965            precise_parent_cache: Mutex::new(HashMap::default()),
1966            c_tag_kind_cache: Mutex::new(HashMap::default()),
1967            c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
1968            macro_event_cells: Mutex::new(HashMap::default()),
1969            macro_include_protection_cells: Mutex::new(HashMap::default()),
1970            macro_environment_checkpoints: Mutex::new(HashMap::default()),
1971            macro_replacements: Mutex::new(HashMap::default()),
1972            macro_local_binding_templates: Mutex::new(HashMap::default()),
1973            macro_replacement_bodies: Mutex::new(HashMap::default()),
1974            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1975            #[cfg(any(test, feature = "test-support"))]
1976            macro_replacement_parse_count: AtomicUsize::new(0),
1977            #[cfg(any(test, feature = "test-support"))]
1978            macro_event_application_count: AtomicUsize::new(0),
1979            #[cfg(any(test, feature = "test-support"))]
1980            macro_environment_checkpoint_build_count: AtomicUsize::new(0),
1981            #[cfg(any(test, feature = "test-support"))]
1982            macro_environment_copy_count: AtomicUsize::new(0),
1983            #[cfg(any(test, feature = "test-support"))]
1984            macro_environment_request_count: AtomicUsize::new(0),
1985            cpp_template_metadata,
1986            cpp_template_families,
1987            #[cfg(any(test, feature = "test-support"))]
1988            qualified_candidate_inspections: AtomicUsize::new(0),
1989            #[cfg(any(test, feature = "test-support"))]
1990            target_preserving_type_resolution_count: AtomicUsize::new(0),
1991        }
1992    }
1993
1994    pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
1995        if file == target.source() {
1996            return true;
1997        }
1998        if self.global_field_has_internal_linkage(target) {
1999            return self
2000                .visible_source_files_by_root
2001                .get(file)
2002                .is_some_and(|sources| sources.contains(target.source()));
2003        }
2004        self.visible_by_file
2005            .get(file)
2006            .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
2007    }
2008
2009    fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
2010        self.global_field_internal_linkage
2011            .get(unit)
2012            .copied()
2013            .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
2014    }
2015
2016    pub fn call_arity_evidence(
2017        &self,
2018        file: &ProjectFile,
2019        call: Node<'_>,
2020        source: &str,
2021    ) -> CallArityEvidence {
2022        self.call_arity_evidence_at(file, call, source, call.start_byte())
2023    }
2024
2025    /// Argument-count evidence for a call whose macro environment is not the
2026    /// one at its own byte offset.
2027    ///
2028    /// A call recovered from a macro replacement lives in a sentinel parse of
2029    /// its own, so its node offsets say nothing about which macros are active.
2030    /// `environment_byte` names the position in `file` whose macro environment
2031    /// governs the call: the macro definition site for a replacement body.
2032    pub fn call_arity_evidence_at(
2033        &self,
2034        file: &ProjectFile,
2035        call: Node<'_>,
2036        source: &str,
2037        environment_byte: usize,
2038    ) -> CallArityEvidence {
2039        let Some(arguments) = call
2040            .child_by_field_name("arguments")
2041            .or_else(|| call.child_by_field_name("parameters"))
2042            .or_else(|| call.child_by_field_name("value"))
2043            .or_else(|| first_named_child_of_kind(call, "argument_list"))
2044            .or_else(|| first_named_child_of_kind(call, "initializer_list"))
2045        else {
2046            return CallArityEvidence::Exact(0);
2047        };
2048        let recovered_c_keyword_arguments =
2049            recovered_c_keyword_argument_count(file, call, arguments, source);
2050        let c_semantics = reference_uses_c_semantics(self.cpp, file);
2051        let arguments = argument_children(arguments)
2052            .flat_map(|argument| {
2053                recovered_c_new_expression_arguments(argument, c_semantics)
2054                    .map(Vec::from)
2055                    .unwrap_or_else(|| vec![argument])
2056            })
2057            .collect::<Vec<_>>();
2058        if arguments
2059            .iter()
2060            .all(|argument| !argument_shape_may_change_arity(*argument))
2061        {
2062            return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
2063        }
2064        let environment = self.macro_environment(file, environment_byte);
2065        let mut stack = Vec::new();
2066        let mut total = recovered_c_keyword_arguments;
2067        for argument in arguments {
2068            if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
2069                return CallArityEvidence::Unknown;
2070            }
2071            let CallArityEvidence::Exact(spread) =
2072                self.argument_arity_evidence(argument, source, &environment, &mut stack)
2073            else {
2074                return CallArityEvidence::Unknown;
2075            };
2076            total += spread;
2077        }
2078        CallArityEvidence::Exact(total)
2079    }
2080
2081    fn argument_arity_evidence(
2082        &self,
2083        argument: Node<'_>,
2084        source: &str,
2085        environment: &MacroEnvironment,
2086        stack: &mut Vec<(ProjectFile, usize)>,
2087    ) -> CallArityEvidence {
2088        let (name, invocation_arguments, function_like) = match argument.kind() {
2089            "identifier" => (node_text(argument, source), None, false),
2090            "call_expression" => {
2091                let Some(function) = argument.child_by_field_name("function") else {
2092                    return CallArityEvidence::Exact(1);
2093                };
2094                if function.kind() != "identifier" {
2095                    return CallArityEvidence::Exact(1);
2096                }
2097                let Some(arguments) = argument.child_by_field_name("arguments") else {
2098                    return CallArityEvidence::Exact(1);
2099                };
2100                (node_text(function, source), Some(arguments), true)
2101            }
2102            _ => return CallArityEvidence::Exact(1),
2103        };
2104        let Some(binding) = environment.binding(name) else {
2105            return if environment.unknown_names {
2106                CallArityEvidence::Unknown
2107            } else {
2108                CallArityEvidence::Exact(1)
2109            };
2110        };
2111        if !binding.is_exact() {
2112            return CallArityEvidence::Unknown;
2113        }
2114        match (&binding.definition, invocation_arguments, function_like) {
2115            (MacroDefinition::Object { replacement }, None, false) => self
2116                .replacement_arity_evidence(
2117                    replacement,
2118                    &[],
2119                    &[],
2120                    source,
2121                    environment,
2122                    stack,
2123                    binding,
2124                ),
2125            (
2126                MacroDefinition::Function {
2127                    parameters,
2128                    replacement,
2129                },
2130                Some(arguments),
2131                true,
2132            ) => {
2133                let actuals = argument_children(arguments).collect::<Vec<_>>();
2134                if actuals.len() != parameters.len() {
2135                    CallArityEvidence::Unknown
2136                } else {
2137                    self.replacement_arity_evidence(
2138                        replacement,
2139                        parameters,
2140                        &actuals,
2141                        source,
2142                        environment,
2143                        stack,
2144                        binding,
2145                    )
2146                }
2147            }
2148            (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
2149            _ => CallArityEvidence::Unknown,
2150        }
2151    }
2152
2153    #[allow(clippy::too_many_arguments)]
2154    fn replacement_arity_evidence(
2155        &self,
2156        replacement: &str,
2157        parameters: &[String],
2158        actuals: &[Node<'_>],
2159        actual_source: &str,
2160        environment: &MacroEnvironment,
2161        stack: &mut Vec<(ProjectFile, usize)>,
2162        binding: &MacroBinding,
2163    ) -> CallArityEvidence {
2164        let identity = (binding.source.clone(), binding.declaration_byte);
2165        if stack.contains(&identity) || replacement.trim().is_empty() {
2166            return CallArityEvidence::Unknown;
2167        }
2168        stack.push(identity);
2169        let parsed = self.parsed_macro_replacement(binding, replacement);
2170        let evidence = (|| {
2171            let ParsedMacroReplacement::Parsed {
2172                source: sentinel,
2173                tree,
2174            } = parsed.as_ref()
2175            else {
2176                return None;
2177            };
2178            let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
2179            let arguments = call.child_by_field_name("arguments")?;
2180            let mut total = 0usize;
2181            for argument in argument_children(arguments) {
2182                if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
2183                    return None;
2184                }
2185                if argument.kind() == "identifier"
2186                    && let Some(parameter_index) = parameters
2187                        .iter()
2188                        .position(|parameter| parameter == node_text(argument, sentinel))
2189                {
2190                    if !macro_expansion_shape_is_safe(
2191                        actuals[parameter_index],
2192                        actual_source,
2193                        &[],
2194                        environment,
2195                    ) {
2196                        return None;
2197                    }
2198                    let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
2199                        actuals[parameter_index],
2200                        actual_source,
2201                        environment,
2202                        stack,
2203                    ) else {
2204                        return None;
2205                    };
2206                    total += spread;
2207                    continue;
2208                }
2209                let CallArityEvidence::Exact(spread) =
2210                    self.argument_arity_evidence(argument, sentinel, environment, stack)
2211                else {
2212                    return None;
2213                };
2214                total += spread;
2215            }
2216            Some(CallArityEvidence::Exact(total))
2217        })()
2218        .unwrap_or(CallArityEvidence::Unknown);
2219        stack.pop();
2220        evidence
2221    }
2222
2223    fn parsed_macro_replacement(
2224        &self,
2225        binding: &MacroBinding,
2226        replacement: &str,
2227    ) -> Arc<ParsedMacroReplacement> {
2228        let key = (binding.source.clone(), binding.declaration_byte);
2229        let mut cache = self
2230            .macro_replacements
2231            .lock()
2232            .expect("C++ macro replacement cache poisoned");
2233        if let Some(parsed) = cache.get(&key) {
2234            return Arc::clone(parsed);
2235        }
2236        #[cfg(any(test, feature = "test-support"))]
2237        self.macro_replacement_parse_count
2238            .fetch_add(1, Ordering::Relaxed);
2239        let source =
2240            format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
2241        let mut parser = Parser::new();
2242        let parsed = parser
2243            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2244            .ok()
2245            .and_then(|()| parser.parse(&source, None))
2246            .filter(|tree| !tree.root_node().has_error())
2247            .map_or(ParsedMacroReplacement::Unsupported, |tree| {
2248                ParsedMacroReplacement::Parsed { source, tree }
2249            });
2250        let parsed = Arc::new(parsed);
2251        cache.insert(key, Arc::clone(&parsed));
2252        parsed
2253    }
2254
2255    /// Recover a typed local declared by an active C function-like macro.
2256    ///
2257    /// This is intentionally narrower than macro expansion. The replacement
2258    /// must parse as one declaration, and the invocation must bind every
2259    /// formal parameter to one structured argument. That is sufficient for
2260    /// declaration macros such as `THIS(StorageAzure)`. An unavailable include
2261    /// can make the binding provisional without erasing its last known
2262    /// definition; an explicit conflicting definition still replaces it with
2263    /// Unsupported. Malformed and statement-producing macros also fail closed.
2264    pub fn function_macro_local_binding<'tree>(
2265        &self,
2266        file: &ProjectFile,
2267        statement: Node<'tree>,
2268        source: &str,
2269    ) -> Option<MacroLocalBinding<'tree>> {
2270        if !is_c_source_file(file) {
2271            return None;
2272        }
2273        if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
2274            return Some(binding);
2275        }
2276        let call = match statement.kind() {
2277            "call_expression" => statement,
2278            "expression_statement" if statement.named_child_count() == 1 => {
2279                statement.named_child(0)?
2280            }
2281            _ => return None,
2282        };
2283        if call.kind() != "call_expression" {
2284            return None;
2285        }
2286        let function = call.child_by_field_name("function")?;
2287        if function.kind() != "identifier" {
2288            return None;
2289        }
2290        let arguments = call.child_by_field_name("arguments")?;
2291        let actuals = argument_children(arguments).collect::<Vec<_>>();
2292        let environment = self.macro_environment(file, call.start_byte());
2293        let function_name = node_text(function, source);
2294        let binding = environment.binding(function_name)?;
2295        let MacroDefinition::Function {
2296            parameters,
2297            replacement,
2298        } = &binding.definition
2299        else {
2300            return None;
2301        };
2302        if actuals.len() != parameters.len() {
2303            return None;
2304        }
2305        let template = self.macro_local_binding_template(binding, parameters, replacement)?;
2306        let (type_name, type_node) = match &template.declared_type {
2307            MacroLocalBindingTypeTemplate::Parameter(index) => {
2308                let actual = *actuals.get(*index)?;
2309                if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
2310                    return None;
2311                }
2312                (node_text(actual, source).trim().to_string(), Some(actual))
2313            }
2314            MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
2315        };
2316        if type_name.is_empty() {
2317            return None;
2318        }
2319        Some(MacroLocalBinding {
2320            name: template.name.clone(),
2321            type_name,
2322            type_node,
2323            pointer_depth: template.pointer_depth,
2324            proven_unit: None,
2325        })
2326    }
2327
2328    /// Recover the typed receiver established by a C container macro
2329    /// assignment, such as `value = container_of(ptr, struct item, link)`.
2330    ///
2331    /// The macro definition's parsed replacement identifies the one formal
2332    /// parameter used in type position.  The invocation supplies the actual
2333    /// type node, which is then resolved through the ordinary visibility
2334    /// index.  Calls with no unique type-position parameter, an unresolved
2335    /// type, or an uncertain macro environment remain unproven.
2336    pub fn function_macro_container_binding<'tree>(
2337        &self,
2338        analyzer: &CppGraphSource<'_>,
2339        file: &ProjectFile,
2340        assignment: Node<'tree>,
2341        source: &str,
2342    ) -> Option<MacroLocalBinding<'tree>> {
2343        if !is_c_source_file(file) || assignment.kind() != "assignment_expression" {
2344            return None;
2345        }
2346        let name_node = assignment.child_by_field_name("left")?;
2347        if name_node.kind() != "identifier" {
2348            return None;
2349        }
2350        let call = assignment.child_by_field_name("right")?;
2351        if call.kind() != "call_expression" {
2352            return None;
2353        }
2354        let function = call.child_by_field_name("function")?;
2355        if function.kind() != "identifier" {
2356            return None;
2357        }
2358        let arguments = call.child_by_field_name("arguments")?;
2359        let actuals = argument_children(arguments).collect::<Vec<_>>();
2360        let function_name = node_text(function, source);
2361        let environment = self.macro_environment(file, call.start_byte());
2362        let binding = environment.binding(function_name)?;
2363        if !binding.is_exact() {
2364            return None;
2365        }
2366        let MacroDefinition::Function {
2367            parameters,
2368            replacement,
2369        } = &binding.definition
2370        else {
2371            return None;
2372        };
2373        if actuals.len() != parameters.len() {
2374            return None;
2375        }
2376        let body = self.parsed_macro_replacement_body(
2377            &(binding.source.clone(), binding.declaration_byte),
2378            parameters,
2379            replacement,
2380        )?;
2381        let type_parameter = macro_replacement_type_parameter(&body, parameters)?;
2382        let type_argument = *actuals.get(type_parameter)?;
2383        let type_node = macro_type_argument_node(type_argument, source)?;
2384        let type_name = node_text(type_node, source).trim().to_string();
2385        if type_name.is_empty() {
2386            return None;
2387        }
2388        let explicit_tag = match node_text(type_argument, source) {
2389            "struct" => Some(CppCTagKind::Struct),
2390            "union" => Some(CppCTagKind::Union),
2391            _ => None,
2392        };
2393        let resolved_type = if let Some(tag) = explicit_tag {
2394            let candidates = self
2395                .visible_identifier_candidates(file, &type_name)
2396                .filter(|candidate| self.cached_c_tag_kind(analyzer, candidate) == Some(tag))
2397                .collect::<Vec<_>>();
2398            self.resolve_type_candidates(
2399                analyzer,
2400                file,
2401                &candidates,
2402                TypeCandidateResolution::Canonical,
2403            )
2404            .ok()
2405        } else {
2406            self.resolve_type_node_result(file, type_node, source)
2407                .ok()
2408                .flatten()
2409        };
2410        let proven_unit = resolved_type?;
2411        Some(MacroLocalBinding {
2412            name: node_text(name_node, source).to_string(),
2413            type_name,
2414            type_node: Some(type_node),
2415            pointer_depth: 1,
2416            proven_unit: Some(proven_unit),
2417        })
2418    }
2419
2420    fn macro_local_binding_template(
2421        &self,
2422        binding: &MacroBinding,
2423        parameters: &[String],
2424        replacement: &str,
2425    ) -> Option<Arc<MacroLocalBindingTemplate>> {
2426        let key = (binding.source.clone(), binding.declaration_byte);
2427        if let Some(template) = self
2428            .macro_local_binding_templates
2429            .lock()
2430            .expect("C++ macro local-binding cache poisoned")
2431            .get(&key)
2432        {
2433            return template.clone();
2434        }
2435        let template = (|| {
2436            let body = self.parsed_macro_replacement_body(&key, parameters, replacement)?;
2437            let sentinel = body.source.as_str();
2438            let statements = body.statements()?;
2439            if statements.named_child_count() != 1 {
2440                return None;
2441            }
2442            let declaration = statements.named_child(0)?;
2443            if declaration.kind() != "declaration" {
2444                return None;
2445            }
2446            let type_node = declaration
2447                .child_by_field_name("type")
2448                .or_else(|| first_type_child(declaration))?;
2449            let declarator = declaration.child_by_field_name("declarator").or_else(|| {
2450                let mut cursor = declaration.walk();
2451                declaration.named_children(&mut cursor).find_map(|child| {
2452                    if child.kind() == "init_declarator" {
2453                        child.child_by_field_name("declarator")
2454                    } else {
2455                        is_declarator_node(child).then_some(child)
2456                    }
2457                })
2458            })?;
2459            let name = extract_variable_name(declarator, sentinel)?;
2460            let pointer_depth = declared_name_indirection(declaration, type_node, &name, sentinel)?;
2461            let type_text = node_text(type_node, sentinel).trim();
2462            let declared_type = parameters
2463                .iter()
2464                .position(|parameter| parameter == type_text)
2465                .map(MacroLocalBindingTypeTemplate::Parameter)
2466                .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
2467            Some(Arc::new(MacroLocalBindingTemplate {
2468                name,
2469                declared_type,
2470                pointer_depth,
2471            }))
2472        })();
2473        self.macro_local_binding_templates
2474            .lock()
2475            .expect("C++ macro local-binding cache poisoned")
2476            .insert(key, template.clone());
2477        template
2478    }
2479
2480    /// The parsed replacement body of the function-like macro `definition`
2481    /// defines, or `None` when the replacement cannot be recovered exactly.
2482    ///
2483    /// `definition` is the defining `preproc_function_def` node in `file`, so
2484    /// the result describes that definition rather than whichever same-named
2485    /// macro a later reference resolves to.
2486    pub fn function_macro_replacement_body(
2487        &self,
2488        file: &ProjectFile,
2489        definition: Node<'_>,
2490        source: &str,
2491    ) -> Option<Arc<ParsedReplacementBody>> {
2492        debug_assert_eq!(definition.kind(), "preproc_function_def");
2493        let MacroDefinition::Function {
2494            parameters,
2495            replacement,
2496        } = Self::decode_macro_definition(definition, source)
2497        else {
2498            return None;
2499        };
2500        self.parsed_macro_replacement_body(
2501            &(file.clone(), definition.start_byte()),
2502            &parameters,
2503            &replacement,
2504        )
2505    }
2506
2507    /// Parse one function-like macro replacement inside the shared sentinel.
2508    ///
2509    /// The parse fails closed, and the failure is cached, whenever the
2510    /// sentinel tree carries an error or the replacement uses preprocessor
2511    /// syntax that has no C++ meaning. Token pasting and stringizing produce
2512    /// `ERROR` nodes; `__VA_ARGS__` parses as an ordinary identifier and is
2513    /// therefore rejected from the parsed tree instead of the source text.
2514    fn parsed_macro_replacement_body(
2515        &self,
2516        key: &(ProjectFile, usize),
2517        parameters: &[String],
2518        replacement: &str,
2519    ) -> Option<Arc<ParsedReplacementBody>> {
2520        if let Some(body) = self
2521            .macro_replacement_bodies
2522            .lock()
2523            .expect("C++ macro replacement body cache poisoned")
2524            .get(key)
2525        {
2526            return body.clone();
2527        }
2528        let body = (|| {
2529            if replacement.trim().is_empty() {
2530                return None;
2531            }
2532            let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
2533            let mut parser = Parser::new();
2534            parser
2535                .set_language(&tree_sitter_cpp::LANGUAGE.into())
2536                .ok()?;
2537            let tree = parser.parse(&source, None)?;
2538            if tree.root_node().has_error() {
2539                return None;
2540            }
2541            let body = ParsedReplacementBody {
2542                source,
2543                tree,
2544                body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
2545                parameters: parameters.to_vec(),
2546            };
2547            body.statements()?;
2548            if body.expands_variadic_arguments() {
2549                return None;
2550            }
2551            Some(Arc::new(body))
2552        })();
2553        self.macro_replacement_bodies
2554            .lock()
2555            .expect("C++ macro replacement body cache poisoned")
2556            .insert(key.clone(), body.clone());
2557        body
2558    }
2559
2560    fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
2561        let replacement = node
2562            .child_by_field_name("value")
2563            .map(|value| node_text(value, source).to_string())
2564            .unwrap_or_default();
2565        if node.kind() == "preproc_def" {
2566            return MacroDefinition::Object { replacement };
2567        }
2568        let Some(parameters) = node.child_by_field_name("parameters") else {
2569            return MacroDefinition::Unsupported;
2570        };
2571        if (0..parameters.child_count()).any(|index| {
2572            parameters
2573                .child(index)
2574                .is_some_and(|child| child.kind() == "...")
2575        }) {
2576            return MacroDefinition::Unsupported;
2577        }
2578        let parameters = (0..parameters.named_child_count())
2579            .filter_map(|index| parameters.named_child(index))
2580            .map(|parameter| node_text(parameter, source).to_string())
2581            .collect();
2582        MacroDefinition::Function {
2583            parameters,
2584            replacement,
2585        }
2586    }
2587
2588    pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
2589        self.macro_event_cells
2590            .lock()
2591            .expect("C++ macro event cache poisoned")
2592            .entry(file.clone())
2593            .or_default()
2594            .clone()
2595    }
2596
2597    fn macro_environment_checkpoint_cell(
2598        &self,
2599        file: &ProjectFile,
2600    ) -> MacroEnvironmentCheckpointCell {
2601        self.macro_environment_checkpoints
2602            .lock()
2603            .expect("C++ macro environment checkpoint cache poisoned")
2604            .entry(file.clone())
2605            .or_default()
2606            .clone()
2607    }
2608
2609    /// The environment of `file`'s macro events applied up to `before_byte`.
2610    pub fn macro_environment(
2611        &self,
2612        file: &ProjectFile,
2613        before_byte: usize,
2614    ) -> Arc<MacroEnvironment> {
2615        #[cfg(any(test, feature = "test-support"))]
2616        self.macro_environment_request_count
2617            .fetch_add(1, Ordering::Relaxed);
2618        let cell = self.macro_event_cell(file);
2619        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2620        let frontier = events.partition_point(|event| event.byte() < before_byte);
2621        let checkpoint_cell = self.macro_environment_checkpoint_cell(file);
2622        let checkpoints =
2623            checkpoint_cell.get_or_init(|| self.build_macro_environment_checkpoints(file, events));
2624        let checkpoint = checkpoints.at_or_before(frontier);
2625        if checkpoint.frontier == frontier {
2626            return Arc::clone(&checkpoint.environment);
2627        }
2628        #[cfg(any(test, feature = "test-support"))]
2629        self.macro_environment_copy_count
2630            .fetch_add(1, Ordering::Relaxed);
2631        let mut environment = checkpoint.environment.as_ref().clone();
2632        let mut include_stack = HashSet::from_iter([file.clone()]);
2633        for event in &events[checkpoint.frontier..frontier] {
2634            self.apply_macro_event(file, event, &mut environment, &mut include_stack);
2635        }
2636        Arc::new(environment)
2637    }
2638
2639    /// Apply `file`'s events once, keeping the environment at the prefixes
2640    /// [`MacroEnvironmentCheckpoints`] describes.
2641    fn build_macro_environment_checkpoints(
2642        &self,
2643        file: &ProjectFile,
2644        events: &[MacroEvent],
2645    ) -> MacroEnvironmentCheckpoints {
2646        #[cfg(any(test, feature = "test-support"))]
2647        self.macro_environment_checkpoint_build_count
2648            .fetch_add(1, Ordering::Relaxed);
2649        // The TU's build-proven defines hold from the first byte (#2011): they
2650        // are facts of the whole compile, so they seed the frontier-zero
2651        // checkpoint. A later explicit #undef event still overrides them
2652        // through `known_undefined_names`.
2653        let mut environment = MacroEnvironment {
2654            build_proven_defines: self
2655                .compile_proven_guards(file)
2656                .iter()
2657                .filter_map(|guard| match guard {
2658                    PreprocessorGuard::Defined(name) => Some(name.clone()),
2659                    _ => None,
2660                })
2661                .collect(),
2662            ..MacroEnvironment::default()
2663        };
2664        let mut checkpoints = vec![MacroEnvironmentCheckpoint {
2665            frontier: 0,
2666            environment: Arc::new(environment.clone()),
2667        }];
2668        // Keep roughly one fixed stride's worth of checkpoints, while taking
2669        // a checkpoint at every event for event sets no larger than the fixed
2670        // stride. Generated C tables commonly have thousands of uses between
2671        // one #define and a trailing #undef; with a fixed stride those
2672        // identical frontier requests would all copy and replay the same
2673        // prefix.
2674        let checkpoint_stride = events
2675            .len()
2676            .div_ceil(MACRO_ENVIRONMENT_CHECKPOINT_STRIDE)
2677            .clamp(1, MACRO_ENVIRONMENT_CHECKPOINT_STRIDE);
2678        let mut include_stack = HashSet::from_iter([file.clone()]);
2679        for (index, event) in events.iter().enumerate() {
2680            self.apply_macro_event(file, event, &mut environment, &mut include_stack);
2681            let frontier = index + 1;
2682            if frontier % checkpoint_stride == 0 || matches!(event, MacroEvent::Include { .. }) {
2683                checkpoints.push(MacroEnvironmentCheckpoint {
2684                    frontier,
2685                    environment: Arc::new(environment.clone()),
2686                });
2687            }
2688        }
2689        MacroEnvironmentCheckpoints { checkpoints }
2690    }
2691
2692    /// Whether `name` is bound as a macro at `before_byte` in `file`,
2693    /// including a binding this environment cannot pin to one replacement
2694    /// (a conditional `#define`, or a function-like macro).
2695    ///
2696    /// [`Self::object_macro_replacement_at`] collapses every such binding to
2697    /// `None`, which is indistinguishable from "not a macro at all". A caller
2698    /// that must not read a macro token as an ordinary type name needs the two
2699    /// apart: an unexpandable macro is an unknown, a plain identifier is not.
2700    pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
2701        self.macro_environment(file, before_byte)
2702            .binding(name)
2703            .is_some()
2704    }
2705
2706    pub fn macro_name_may_be_bound_at(
2707        &self,
2708        file: &ProjectFile,
2709        name: &str,
2710        before_byte: usize,
2711    ) -> bool {
2712        self.macro_environment(file, before_byte).may_bind(name)
2713    }
2714
2715    /// Whether the active macro binding at this reference is the requested
2716    /// indexed definition. Name equality alone is not enough because two
2717    /// headers can define the same macro for different translation units.
2718    pub fn macro_binding_matches_target_at(
2719        &self,
2720        analyzer: &CppGraphSource<'_>,
2721        file: &ProjectFile,
2722        name: &str,
2723        before_byte: usize,
2724        target: &CodeUnit,
2725    ) -> bool {
2726        let ranges = analyzer.ranges(target);
2727        let declaration_bytes = self.macro_declaration_bytes(target, &ranges);
2728        self.macro_binding_matches_target_declaration_at(
2729            file,
2730            name,
2731            before_byte,
2732            target.source(),
2733            &declaration_bytes,
2734        )
2735    }
2736
2737    /// Resolve declaration ranges once for inverse scans that test many call
2738    /// sites against one macro target. Walking a large generated syntax tree
2739    /// back to the same `#define` for every call site is otherwise quadratic
2740    /// in the number of top-level declarations.
2741    pub(crate) fn macro_declaration_bytes(
2742        &self,
2743        target: &CodeUnit,
2744        ranges: &[Range],
2745    ) -> Vec<usize> {
2746        let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
2747            return Vec::new();
2748        };
2749        ranges
2750            .iter()
2751            .filter_map(|range| {
2752                let mut node = node_for_exact_range(prepared.tree().root_node(), range)?;
2753                while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
2754                    node = node.parent()?;
2755                }
2756                Some(node.start_byte())
2757            })
2758            .collect()
2759    }
2760
2761    pub(crate) fn macro_binding_matches_target_declaration_at(
2762        &self,
2763        file: &ProjectFile,
2764        name: &str,
2765        before_byte: usize,
2766        target_source: &ProjectFile,
2767        target_declaration_bytes: &[usize],
2768    ) -> bool {
2769        let environment = self.macro_environment(file, before_byte);
2770        let Some(binding) = environment.binding(name) else {
2771            return false;
2772        };
2773        if binding.definition == MacroDefinition::Unsupported {
2774            return false;
2775        }
2776        // A normal header guard makes the replacement text conditional, but
2777        // it does not erase the definition site's source and byte identity.
2778        // Keep that identity even when expansion details are not exact.
2779        if binding.source != *target_source {
2780            return false;
2781        }
2782        target_declaration_bytes.contains(&binding.declaration_byte)
2783    }
2784
2785    /// Resolve an ordinary expression-position macro token at its exact byte.
2786    ///
2787    /// Calls and preprocessor-condition tokens have separate resolution
2788    /// surfaces. Declaration names, macro parameters, and labels are not
2789    /// references. Keeping that role policy here makes forward and both
2790    /// inverse graph builders consume the same activation verdict (#2093).
2791    pub fn resolve_ordinary_macro_reference(
2792        &self,
2793        analyzer: &CppGraphSource<'_>,
2794        file: &ProjectFile,
2795        node: Node<'_>,
2796        source: &str,
2797    ) -> OrdinaryMacroReferenceResolution {
2798        if !is_ordinary_macro_reference_node(node) {
2799            return OrdinaryMacroReferenceResolution::Missing;
2800        }
2801        let name = node_text(node, source);
2802        if name.is_empty() {
2803            return OrdinaryMacroReferenceResolution::Missing;
2804        }
2805        let visible = self
2806            .visible_identifier_candidates(file, name)
2807            .filter(|candidate| candidate.is_macro())
2808            .cloned()
2809            .collect::<Vec<_>>();
2810        let mut exact = Vec::new();
2811        for candidate in &visible {
2812            if self.macro_binding_matches_target_at(
2813                analyzer,
2814                file,
2815                name,
2816                node.start_byte(),
2817                candidate,
2818            ) && !exact
2819                .iter()
2820                .any(|existing| same_visible_symbol(existing, candidate))
2821            {
2822                exact.push(candidate.clone());
2823            }
2824        }
2825        match exact.len() {
2826            1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
2827            2.. => OrdinaryMacroReferenceResolution::Ambiguous,
2828            0 if !visible.is_empty()
2829                && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
2830            {
2831                OrdinaryMacroReferenceResolution::Ambiguous
2832            }
2833            0 => OrdinaryMacroReferenceResolution::Missing,
2834        }
2835    }
2836
2837    /// Collect reference-capable C tokens beneath tree-sitter recovery nodes.
2838    ///
2839    /// The ordinary census deliberately skips every `ERROR` subtree. This
2840    /// separate, precision-only frontier admits only roles that retain enough
2841    /// structure for the C usage graph to interpret independently (#2089).
2842    /// Macro evidence comes from this visibility index at the exact byte; no
2843    /// source-text parsing or terminal-name fallback is used.
2844    pub fn recovered_c_reference_ranges(
2845        &self,
2846        file: &ProjectFile,
2847        root: Node<'_>,
2848        source: &str,
2849        limit: usize,
2850    ) -> RecoveredCReferenceRanges {
2851        if !is_c_source_file(file) {
2852            return RecoveredCReferenceRanges::Complete(Vec::new());
2853        }
2854        let mut ranges = Vec::new();
2855        let mut seen = HashSet::default();
2856        let mut stack = vec![(root, root.is_error())];
2857        while let Some((node, inside_error)) = stack.pop() {
2858            let inside_error = inside_error || node.is_error();
2859            if node.kind() == "preproc_arg" {
2860                // Tree-sitter keeps an object-like replacement opaque. The
2861                // inverse extractor uses the same parsed replacement helper;
2862                // retain its exact macro/type leaves for precision membership
2863                // even when the preprocessor node is outside ERROR recovery.
2864                let macro_value_kind = node.parent().and_then(|parent| {
2865                    (parent.child_by_field_name("value") == Some(node)).then_some(parent.kind())
2866                });
2867                if matches!(
2868                    macro_value_kind,
2869                    Some("preproc_def" | "preproc_function_def")
2870                ) {
2871                    let name = node_text(node, source);
2872                    if !name.is_empty()
2873                        && self.macro_name_may_be_bound_at(file, name, node.start_byte())
2874                        && !push_recovered_c_range(
2875                            &mut ranges,
2876                            &mut seen,
2877                            node.start_byte(),
2878                            node.end_byte(),
2879                            node,
2880                            limit,
2881                        )
2882                    {
2883                        return RecoveredCReferenceRanges::LimitExceeded;
2884                    }
2885                }
2886                if macro_value_kind == Some("preproc_def") {
2887                    for reference in object_macro_replacement_type_references(node, source) {
2888                        for range in reference.component_ranges {
2889                            let visible = self
2890                                .visible_identifier_candidates(file, &source[range.clone()])
2891                                .any(|candidate| {
2892                                    candidate.is_class()
2893                                        || candidate.is_module()
2894                                        || is_type_alias(candidate)
2895                                });
2896                            if visible
2897                                && !push_recovered_c_range(
2898                                    &mut ranges,
2899                                    &mut seen,
2900                                    range.start,
2901                                    range.end,
2902                                    node,
2903                                    limit,
2904                                )
2905                            {
2906                                return RecoveredCReferenceRanges::LimitExceeded;
2907                            }
2908                        }
2909                    }
2910                }
2911            }
2912            if inside_error
2913                && recovered_c_reference_node(self, file, node, source)
2914                && !push_recovered_c_range(
2915                    &mut ranges,
2916                    &mut seen,
2917                    node.start_byte(),
2918                    node.end_byte(),
2919                    node,
2920                    limit,
2921                )
2922            {
2923                return RecoveredCReferenceRanges::LimitExceeded;
2924            }
2925            let mut cursor = node.walk();
2926            for child in node.named_children(&mut cursor) {
2927                stack.push((child, inside_error));
2928            }
2929        }
2930        ranges.sort_unstable();
2931        RecoveredCReferenceRanges::Complete(ranges)
2932    }
2933
2934    /// Whether this target is an indexed macro visible from this file.
2935    ///
2936    /// An unresolved conditional can make more than one same-name macro a
2937    /// possible active binding. Each possible target can keep the site as an
2938    /// unproven hit. A macro in an unrelated translation unit stays excluded.
2939    pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2940        self.visible_identifier_candidates(file, target.identifier())
2941            .filter(|candidate| candidate.is_macro())
2942            .any(|candidate| {
2943                candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
2944            })
2945    }
2946
2947    pub fn object_macro_replacement_at(
2948        &self,
2949        file: &ProjectFile,
2950        name: &str,
2951        before_byte: usize,
2952    ) -> Option<String> {
2953        let environment = self.macro_environment(file, before_byte);
2954        let binding = environment.binding(name)?;
2955        if !binding.exact {
2956            return None;
2957        }
2958        match &binding.definition {
2959            MacroDefinition::Object { replacement } => Some(replacement.clone()),
2960            MacroDefinition::Function { .. } | MacroDefinition::Unsupported => None,
2961        }
2962    }
2963
2964    fn apply_macro_events(
2965        &self,
2966        file: &ProjectFile,
2967        before_byte: Option<usize>,
2968        environment: &mut MacroEnvironment,
2969        include_stack: &mut HashSet<ProjectFile>,
2970    ) {
2971        if !include_stack.insert(file.clone()) {
2972            return;
2973        }
2974        if self.cpp.prepared_syntax(self.token, file).is_none() {
2975            environment.mark_unknown_names(file, before_byte.unwrap_or_default());
2976            include_stack.remove(file);
2977            return;
2978        }
2979        match self.macro_include_protection(file) {
2980            MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
2981                Some(binding) if binding.is_exact() => {
2982                    include_stack.remove(file);
2983                    return;
2984                }
2985                Some(_) | None if environment.unknown_names => {
2986                    let mut ambiguous_seen = HashSet::default();
2987                    self.mark_macro_events_ambiguous(
2988                        file,
2989                        environment,
2990                        &mut ambiguous_seen,
2991                        file,
2992                        before_byte.unwrap_or_default(),
2993                    );
2994                    include_stack.remove(file);
2995                    return;
2996                }
2997                Some(_) => {
2998                    let mut ambiguous_seen = HashSet::default();
2999                    self.mark_macro_events_ambiguous(
3000                        file,
3001                        environment,
3002                        &mut ambiguous_seen,
3003                        file,
3004                        before_byte.unwrap_or_default(),
3005                    );
3006                    include_stack.remove(file);
3007                    return;
3008                }
3009                None => {}
3010            },
3011            MacroIncludeProtection::PragmaOnce => {
3012                if !environment.applied_pragma_once_files.insert(file.clone()) {
3013                    include_stack.remove(file);
3014                    return;
3015                }
3016                if environment.maybe_applied_pragma_once_files.remove(file) {
3017                    // A prior conditional include may already have consumed the pragma-once
3018                    // header. This unconditional include guarantees it is consumed now, but
3019                    // cannot prove whether its events occur before or after intervening local
3020                    // macro changes, so preserve the union as ambiguous.
3021                    let mut ambiguous_seen = HashSet::default();
3022                    environment.applied_pragma_once_files.remove(file);
3023                    self.mark_macro_events_ambiguous(
3024                        file,
3025                        environment,
3026                        &mut ambiguous_seen,
3027                        file,
3028                        before_byte.unwrap_or_default(),
3029                    );
3030                    environment.maybe_applied_pragma_once_files.remove(file);
3031                    environment.applied_pragma_once_files.insert(file.clone());
3032                    include_stack.remove(file);
3033                    return;
3034                }
3035            }
3036            MacroIncludeProtection::None => {}
3037        }
3038        let cell = self.macro_event_cell(file);
3039        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3040        for event in events {
3041            if before_byte.is_some_and(|limit| event.byte() >= limit) {
3042                break;
3043            }
3044            self.apply_macro_event(file, event, environment, include_stack);
3045        }
3046        include_stack.remove(file);
3047    }
3048
3049    fn apply_macro_event(
3050        &self,
3051        file: &ProjectFile,
3052        event: &MacroEvent,
3053        environment: &mut MacroEnvironment,
3054        include_stack: &mut HashSet<ProjectFile>,
3055    ) {
3056        #[cfg(any(test, feature = "test-support"))]
3057        self.macro_event_application_count
3058            .fetch_add(1, Ordering::Relaxed);
3059        match event {
3060            MacroEvent::Define {
3061                name,
3062                binding,
3063                conditionals,
3064                byte,
3065            } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3066                Some(true) => environment.insert(name.clone(), binding.clone()),
3067                Some(false) => {}
3068                None => Self::merge_conditional_macro_definition(
3069                    environment,
3070                    name,
3071                    binding,
3072                    file,
3073                    *byte,
3074                ),
3075            },
3076            MacroEvent::Undef {
3077                name,
3078                conditionals,
3079                byte,
3080            } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3081                Some(true) => environment.remove(name),
3082                Some(false) => {}
3083                None => {
3084                    if environment.binding(name).is_some() {
3085                        environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
3086                    }
3087                }
3088            },
3089            MacroEvent::Include {
3090                targets,
3091                conditionals,
3092                byte,
3093            } => {
3094                let condition =
3095                    self.macro_event_condition_value(file, *byte, environment, conditionals);
3096                if condition == Some(false) {
3097                    return;
3098                }
3099                if targets.is_empty() {
3100                    environment.mark_unknown_names(file, *byte);
3101                    return;
3102                }
3103                if condition.is_none() || targets.len() > 1 {
3104                    let mut ambiguous_seen = HashSet::default();
3105                    for target in targets {
3106                        self.mark_macro_events_ambiguous(
3107                            target,
3108                            environment,
3109                            &mut ambiguous_seen,
3110                            file,
3111                            *byte,
3112                        );
3113                    }
3114                } else if let Some(target) = targets.first() {
3115                    self.apply_macro_events(target, None, environment, include_stack);
3116                }
3117            }
3118            MacroEvent::Invalidate { byte } => {
3119                for binding in environment.bindings.values_mut() {
3120                    *binding = MacroBinding::uncertain_from(binding, file, *byte);
3121                }
3122            }
3123        }
3124    }
3125
3126    /// Evaluate the structured conditional path that owns one macro event.
3127    ///
3128    /// `Some(true)` and `Some(false)` are proofs from exact macro bindings at
3129    /// this source byte. `None` preserves the old conditional merge when a
3130    /// build/configuration input or an unsupported expression is involved.
3131    ///
3132    /// `conditionals` is the event's own [`OwningPreprocessorConditionals`]:
3133    /// which ancestors structurally own the event was decided when the events
3134    /// were collected, so all this walk does is find those nodes again and ask
3135    /// the environment what their conditions are worth here.
3136    fn macro_event_condition_value(
3137        &self,
3138        file: &ProjectFile,
3139        event_byte: usize,
3140        environment: &MacroEnvironment,
3141        conditionals: &OwningPreprocessorConditionals,
3142    ) -> Option<bool> {
3143        if conditionals.is_empty() {
3144            return Some(true);
3145        }
3146        let prepared = self.cpp.prepared_syntax(self.token, file)?;
3147        let source = prepared.source();
3148        let root = prepared.tree().root_node();
3149        let descendant = root.descendant_for_byte_range(
3150            event_byte,
3151            event_byte.saturating_add(1).min(source.len()),
3152        )?;
3153        let mut unknown = false;
3154        let mut current = descendant.parent();
3155        while let Some(conditional) = current {
3156            if matches!(
3157                conditional.kind(),
3158                "preproc_if" | "preproc_ifdef" | "preproc_elif"
3159            ) && conditionals.contains(&conditional.start_byte())
3160            {
3161                let mut value = match conditional.kind() {
3162                    "preproc_ifdef" => {
3163                        let name = conditional.child_by_field_name("name")?;
3164                        let defined =
3165                            self.macro_name_defined_value(environment, node_text(name, source));
3166                        match conditional.child(0)?.kind() {
3167                            "#ifdef" => defined,
3168                            "#ifndef" => defined.map(|defined| !defined),
3169                            _ => None,
3170                        }
3171                    }
3172                    "preproc_if" | "preproc_elif" => conditional
3173                        .child_by_field_name("condition")
3174                        .and_then(|condition| {
3175                            self.preprocessor_integer_value(
3176                                condition,
3177                                source,
3178                                environment,
3179                                &mut Vec::new(),
3180                                0,
3181                            )
3182                        })
3183                        .map(|value| value != 0),
3184                    _ => unreachable!(),
3185                };
3186                if conditional
3187                    .child_by_field_name("alternative")
3188                    .is_some_and(|alternative| {
3189                        alternative.start_byte() <= descendant.start_byte()
3190                            && descendant.end_byte() <= alternative.end_byte()
3191                    })
3192                {
3193                    value = value.map(|value| !value);
3194                }
3195                match value {
3196                    Some(true) => {}
3197                    Some(false) => return Some(false),
3198                    None => unknown = true,
3199                }
3200            }
3201            current = conditional.parent();
3202        }
3203        (!unknown).then_some(true)
3204    }
3205
3206    fn macro_name_defined_value(&self, environment: &MacroEnvironment, name: &str) -> Option<bool> {
3207        if environment.known_undefined_names.contains(name) {
3208            return Some(false);
3209        }
3210        if let Some(binding) = environment.binding(name) {
3211            return binding.is_exact().then_some(true);
3212        }
3213        environment
3214            .build_proven_defines
3215            .contains(name)
3216            .then_some(true)
3217    }
3218
3219    fn preprocessor_integer_value(
3220        &self,
3221        expression: Node<'_>,
3222        source: &str,
3223        environment: &MacroEnvironment,
3224        expansion_stack: &mut Vec<(ProjectFile, usize)>,
3225        depth: usize,
3226    ) -> Option<i128> {
3227        // Macro replacement graphs can cycle. This explicit bound makes the
3228        // otherwise recursive AST evaluation stack-safe for hostile input.
3229        if depth >= 64 {
3230            return None;
3231        }
3232        match expression.kind() {
3233            "number_literal" => parse_cpp_integer_literal(node_text(expression, source)),
3234            "identifier" | "type_identifier" => {
3235                let binding = environment.binding(node_text(expression, source))?;
3236                if !binding.is_exact() {
3237                    return None;
3238                }
3239                let MacroDefinition::Object { replacement } = &binding.definition else {
3240                    return None;
3241                };
3242                let identity = (binding.source.clone(), binding.declaration_byte);
3243                if expansion_stack.contains(&identity) {
3244                    return None;
3245                }
3246                expansion_stack.push(identity);
3247                let parsed = self.parsed_macro_replacement(binding, replacement);
3248                let value = match parsed.as_ref() {
3249                    ParsedMacroReplacement::Parsed {
3250                        source: replacement_source,
3251                        tree,
3252                    } => first_descendant_of_kind(tree.root_node(), "call_expression")
3253                        .and_then(|call| call.child_by_field_name("arguments"))
3254                        .and_then(|arguments| argument_children(arguments).next())
3255                        .and_then(|argument| {
3256                            self.preprocessor_integer_value(
3257                                argument,
3258                                replacement_source,
3259                                environment,
3260                                expansion_stack,
3261                                depth + 1,
3262                            )
3263                        }),
3264                    ParsedMacroReplacement::Unsupported => None,
3265                };
3266                expansion_stack.pop();
3267                value
3268            }
3269            "preproc_defined" => {
3270                let mut cursor = expression.walk();
3271                let name = expression
3272                    .named_children(&mut cursor)
3273                    .find(|child| child.kind() == "identifier")?;
3274                self.macro_name_defined_value(environment, node_text(name, source))
3275                    .map(i128::from)
3276            }
3277            "parenthesized_expression" => expression.named_child(0).and_then(|child| {
3278                self.preprocessor_integer_value(
3279                    child,
3280                    source,
3281                    environment,
3282                    expansion_stack,
3283                    depth + 1,
3284                )
3285            }),
3286            "unary_expression" => {
3287                let operator = expression.child_by_field_name("operator")?.kind();
3288                let argument = expression.child_by_field_name("argument")?;
3289                let value = self.preprocessor_integer_value(
3290                    argument,
3291                    source,
3292                    environment,
3293                    expansion_stack,
3294                    depth + 1,
3295                )?;
3296                match operator {
3297                    "+" => Some(value),
3298                    "-" => value.checked_neg(),
3299                    "!" => Some(i128::from(value == 0)),
3300                    "~" => Some(!value),
3301                    _ => None,
3302                }
3303            }
3304            "binary_expression" => {
3305                let left = self.preprocessor_integer_value(
3306                    expression.child_by_field_name("left")?,
3307                    source,
3308                    environment,
3309                    expansion_stack,
3310                    depth + 1,
3311                )?;
3312                let right = self.preprocessor_integer_value(
3313                    expression.child_by_field_name("right")?,
3314                    source,
3315                    environment,
3316                    expansion_stack,
3317                    depth + 1,
3318                )?;
3319                match expression.child_by_field_name("operator")?.kind() {
3320                    "+" => left.checked_add(right),
3321                    "-" => left.checked_sub(right),
3322                    "*" => left.checked_mul(right),
3323                    "/" => left.checked_div(right),
3324                    "%" => left.checked_rem(right),
3325                    "<<" => u32::try_from(right)
3326                        .ok()
3327                        .and_then(|shift| left.checked_shl(shift)),
3328                    ">>" => u32::try_from(right)
3329                        .ok()
3330                        .and_then(|shift| left.checked_shr(shift)),
3331                    "<" => Some(i128::from(left < right)),
3332                    "<=" => Some(i128::from(left <= right)),
3333                    ">" => Some(i128::from(left > right)),
3334                    ">=" => Some(i128::from(left >= right)),
3335                    "==" => Some(i128::from(left == right)),
3336                    "!=" => Some(i128::from(left != right)),
3337                    "&" => Some(left & right),
3338                    "|" => Some(left | right),
3339                    "^" => Some(left ^ right),
3340                    "&&" => Some(i128::from(left != 0 && right != 0)),
3341                    "||" => Some(i128::from(left != 0 || right != 0)),
3342                    _ => None,
3343                }
3344            }
3345            _ => None,
3346        }
3347    }
3348
3349    fn mark_macro_events_ambiguous(
3350        &self,
3351        file: &ProjectFile,
3352        environment: &mut MacroEnvironment,
3353        include_stack: &mut HashSet<ProjectFile>,
3354        conditional_file: &ProjectFile,
3355        conditional_byte: usize,
3356    ) {
3357        if !include_stack.insert(file.clone()) {
3358            return;
3359        }
3360        if self.cpp.prepared_syntax(self.token, file).is_none() {
3361            environment.mark_unknown_names(conditional_file, conditional_byte);
3362            return;
3363        }
3364        match self.macro_include_protection(file) {
3365            MacroIncludeProtection::MacroGuard(guard) => {
3366                if environment
3367                    .binding(&guard)
3368                    .is_some_and(MacroBinding::is_exact)
3369                {
3370                    return;
3371                }
3372            }
3373            MacroIncludeProtection::PragmaOnce => {
3374                if environment.applied_pragma_once_files.contains(file) {
3375                    return;
3376                }
3377                environment
3378                    .maybe_applied_pragma_once_files
3379                    .insert(file.clone());
3380            }
3381            MacroIncludeProtection::None => {}
3382        }
3383        let cell = self.macro_event_cell(file);
3384        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3385        for event in events {
3386            #[cfg(any(test, feature = "test-support"))]
3387            self.macro_event_application_count
3388                .fetch_add(1, Ordering::Relaxed);
3389            match event {
3390                MacroEvent::Define { name, binding, .. } => {
3391                    Self::merge_conditional_macro_definition(
3392                        environment,
3393                        name,
3394                        binding,
3395                        conditional_file,
3396                        conditional_byte,
3397                    );
3398                }
3399                MacroEvent::Undef { name, .. } => {
3400                    if environment.binding(name).is_some() {
3401                        environment.insert(
3402                            name.clone(),
3403                            MacroBinding::ambiguous(conditional_file, conditional_byte),
3404                        );
3405                    } else {
3406                        environment.remove_known_undefined(name);
3407                    }
3408                }
3409                MacroEvent::Include { targets, .. } => {
3410                    if targets.is_empty() {
3411                        environment.mark_unknown_names(conditional_file, conditional_byte);
3412                        continue;
3413                    }
3414                    for target in targets {
3415                        self.mark_macro_events_ambiguous(
3416                            target,
3417                            environment,
3418                            include_stack,
3419                            conditional_file,
3420                            conditional_byte,
3421                        );
3422                    }
3423                }
3424                MacroEvent::Invalidate { .. } => {
3425                    for binding in environment.bindings.values_mut() {
3426                        *binding = MacroBinding::uncertain_from(
3427                            binding,
3428                            conditional_file,
3429                            conditional_byte,
3430                        );
3431                    }
3432                }
3433            }
3434        }
3435    }
3436
3437    fn merge_conditional_macro_definition(
3438        environment: &mut MacroEnvironment,
3439        name: &str,
3440        possible_binding: &MacroBinding,
3441        conditional_file: &ProjectFile,
3442        conditional_byte: usize,
3443    ) {
3444        // A conditional include can revisit an already-active guarded header.
3445        // If the possible branch defines the exact same macro, both outcomes
3446        // leave the binding unchanged; degrading it to Unknown would discard
3447        // proof because of an unrelated unresolved macro name (#2092).
3448        if environment.binding(name).is_some_and(|current| {
3449            current.definition != MacroDefinition::Unsupported
3450                && current.definition == possible_binding.definition
3451        }) {
3452            return;
3453        }
3454        environment.insert(
3455            name.to_string(),
3456            MacroBinding::ambiguous(conditional_file, conditional_byte),
3457        );
3458    }
3459
3460    pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
3461        let cell = self
3462            .macro_include_protection_cells
3463            .lock()
3464            .expect("C++ include protection cache poisoned")
3465            .entry(file.clone())
3466            .or_default()
3467            .clone();
3468        cell.get_or_init(|| {
3469            self.cpp.prepared_syntax(self.token, file).map_or(
3470                MacroIncludeProtection::None,
3471                |prepared| {
3472                    top_level_macro_include_protection(
3473                        prepared.tree().root_node(),
3474                        prepared.source(),
3475                    )
3476                },
3477            )
3478        })
3479        .clone()
3480    }
3481
3482    fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
3483        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3484            return Vec::new();
3485        };
3486        let source = prepared.source();
3487        let mut events = Vec::new();
3488        let root = prepared.tree().root_node();
3489        let mut stack = vec![root];
3490        while let Some(node) = stack.pop() {
3491            match node.kind() {
3492                "preproc_def" | "preproc_function_def" => {
3493                    let Some(name) = node.child_by_field_name("name") else {
3494                        continue;
3495                    };
3496                    let name = node_text(name, source).to_string();
3497                    events.push(MacroEvent::Define {
3498                        name,
3499                        binding: MacroBinding {
3500                            source: file.clone(),
3501                            declaration_byte: node.start_byte(),
3502                            definition: Self::decode_macro_definition(node, source),
3503                            exact: true,
3504                        },
3505                        byte: node.start_byte(),
3506                        conditionals: owning_preprocessor_conditionals(root, node, source),
3507                    });
3508                    continue;
3509                }
3510                "preproc_include" => {
3511                    let Some(path) = node.child_by_field_name("path") else {
3512                        events.push(MacroEvent::Include {
3513                            targets: Vec::new(),
3514                            byte: node.start_byte(),
3515                            conditionals: owning_preprocessor_conditionals(root, node, source),
3516                        });
3517                        continue;
3518                    };
3519                    let targets =
3520                        structured_include_path(path, source).map_or_else(Vec::new, |path| {
3521                            resolve_include_targets_with_index(
3522                                file,
3523                                path,
3524                                self.cpp.include_target_index(),
3525                            )
3526                        });
3527                    // An unresolved angle-bracket include crosses into an external system
3528                    // boundary that is absent from the source index. It must not poison all
3529                    // later local macro evidence. Quoted/project-local and computed includes,
3530                    // by contrast, may hide indexed macro state and therefore fail closed.
3531                    if targets.is_empty() && path.kind() == "system_lib_string" {
3532                        continue;
3533                    }
3534                    events.push(MacroEvent::Include {
3535                        targets,
3536                        byte: node.start_byte(),
3537                        conditionals: owning_preprocessor_conditionals(root, node, source),
3538                    });
3539                    continue;
3540                }
3541                "preproc_call" => {
3542                    let Some(directive) = node.child_by_field_name("directive") else {
3543                        continue;
3544                    };
3545                    if node_text(directive, source) != "#undef" {
3546                        continue;
3547                    }
3548                    let name = node
3549                        .child_by_field_name("argument")
3550                        .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
3551                    if let Some(name) = name {
3552                        events.push(MacroEvent::Undef {
3553                            name,
3554                            byte: node.start_byte(),
3555                            conditionals: owning_preprocessor_conditionals(root, node, source),
3556                        });
3557                    } else {
3558                        events.push(MacroEvent::Invalidate {
3559                            byte: node.start_byte(),
3560                        });
3561                    }
3562                    continue;
3563                }
3564                _ => {}
3565            }
3566            for index in (0..node.named_child_count()).rev() {
3567                if let Some(child) = node.named_child(index) {
3568                    stack.push(child);
3569                }
3570            }
3571        }
3572        events.sort_by_key(MacroEvent::byte);
3573        events
3574    }
3575
3576    pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
3577        self.ordinary_type_import_cells
3578            .lock()
3579            .expect("C++ ordinary type import cache poisoned")
3580            .entry(file.clone())
3581            .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
3582            .clone()
3583    }
3584
3585    pub fn project_using_index(
3586        &self,
3587        build: impl FnOnce() -> ProjectUsingIndex,
3588    ) -> &ProjectUsingIndex {
3589        self.project_using_index.get_or_init(build)
3590    }
3591
3592    pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
3593        let mut files = self
3594            .visible_source_files_by_root
3595            .values()
3596            .flatten()
3597            .cloned()
3598            .collect::<HashSet<_>>()
3599            .into_iter()
3600            .collect::<Vec<_>>();
3601        files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
3602        files
3603    }
3604
3605    pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
3606        self.visible_source_files_by_root
3607            .get(root)
3608            .is_some_and(|files| files.contains(source))
3609    }
3610
3611    fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
3612        let cached = self
3613            .visible_parser_alias_name_sets
3614            .read()
3615            .expect("visible parser alias-name cache poisoned")
3616            .get(file)
3617            .cloned();
3618        let cell = if let Some(cached) = cached {
3619            cached
3620        } else {
3621            let mut cells = self
3622                .visible_parser_alias_name_sets
3623                .write()
3624                .expect("visible parser alias-name cache poisoned");
3625            Arc::clone(
3626                cells
3627                    .entry(file.clone())
3628                    .or_insert_with(|| Arc::new(OnceLock::new())),
3629            )
3630        };
3631        cell.get_or_init(|| {
3632            #[cfg(any(test, feature = "test-support"))]
3633            self.visible_parser_alias_name_set_build_count
3634                .fetch_add(1, Ordering::Relaxed);
3635            let mut names = HashSet::default();
3636            let visible_files = self
3637                .visible_source_files_by_root
3638                .get(file)
3639                .cloned()
3640                .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
3641            for visible_file in visible_files {
3642                let aliases = {
3643                    let mut cells = self.alias_cells.lock().expect("alias cell map lock");
3644                    Arc::clone(
3645                        cells
3646                            .entry(visible_file.clone())
3647                            .or_insert_with(|| Arc::new(OnceLock::new())),
3648                    )
3649                };
3650                for alias in aliases
3651                    .get_or_init(|| {
3652                        self.parser_alias_source_parses
3653                            .fetch_add(1, Ordering::Relaxed);
3654                        #[cfg(any(test, feature = "test-support"))]
3655                        {
3656                            *self
3657                                .alias_source_parse_counts
3658                                .lock()
3659                                .expect("alias source parse count lock")
3660                                .entry(visible_file.clone())
3661                                .or_default() += 1;
3662                        }
3663                        aliases_from_prepared_source(self.cpp, self.token, &visible_file)
3664                            .into_boxed_slice()
3665                    })
3666                    .iter()
3667                {
3668                    names.insert(alias.name.clone());
3669                }
3670            }
3671            names
3672        })
3673        .contains(name)
3674    }
3675
3676    pub fn parser_alias_name_may_resolve_to_target(
3677        &self,
3678        file: &ProjectFile,
3679        alias_name: &str,
3680        target: &CodeUnit,
3681    ) -> bool {
3682        let started = std::time::Instant::now();
3683        self.parser_alias_fallback_calls
3684            .fetch_add(1, Ordering::Relaxed);
3685        let mut files = 0usize;
3686        let matched = match self.visible_source_files_by_root.get(file) {
3687            None => {
3688                files = 1;
3689                self.file_alias_matches(self.cpp, file, alias_name, target)
3690            }
3691            Some(visible_files) => visible_files.iter().any(|visible_file| {
3692                files += 1;
3693                self.file_alias_matches(self.cpp, visible_file, alias_name, target)
3694            }),
3695        };
3696        self.parser_alias_fallback_files
3697            .fetch_add(files, Ordering::Relaxed);
3698        self.parser_alias_fallback_elapsed_micros.fetch_add(
3699            started.elapsed().as_micros().min(usize::MAX as u128) as usize,
3700            Ordering::Relaxed,
3701        );
3702        matched
3703    }
3704
3705    fn callable_arities_for_target(
3706        &self,
3707        analyzer: &CppGraphSource<'_>,
3708        cpp: &dyn CppSource,
3709        file: &ProjectFile,
3710        prepared: &PreparedSyntaxTree,
3711        spec: &TargetSpec,
3712    ) -> Vec<ActivatedCallableArity> {
3713        let Some(signature) = spec.target.signature() else {
3714            return Vec::new();
3715        };
3716        let Some(candidates) = self
3717            .visible_by_identifier
3718            .get(file)
3719            .and_then(|by_name| by_name.get(&spec.member_name))
3720        else {
3721            return Vec::new();
3722        };
3723        let differing_candidates = candidates
3724            .iter()
3725            .filter(|candidate| {
3726                candidate.is_function()
3727                    && candidate.fq_name() == spec.target.fq_name()
3728                    && candidate.signature() == Some(signature)
3729            })
3730            .filter_map(|candidate| {
3731                analyzer
3732                    .signature_metadata(candidate)
3733                    .into_iter()
3734                    .find_map(|metadata| metadata.callable_arity())
3735                    .filter(|arity| Some(*arity) != spec.callable_arity)
3736                    .map(|arity| (candidate, arity))
3737            })
3738            .collect::<Vec<_>>();
3739        if differing_candidates.is_empty() {
3740            return Vec::new();
3741        }
3742        let mut arities = Vec::with_capacity(differing_candidates.len());
3743        // The activation ranges here describe the whole file rather than one
3744        // reference, so there is no reference guard environment to consult.
3745        let reference = CallableReferenceContext {
3746            file,
3747            position: None,
3748        };
3749        for (candidate, candidate_arity) in differing_candidates {
3750            let declaration_activation = if candidate.source() == file {
3751                callable_declaration_activation_in_file(analyzer, prepared, candidate, &reference)
3752            } else {
3753                cpp.prepared_syntax(self.token, candidate.source())
3754                    .and_then(|syntax| {
3755                        callable_declaration_activation_in_file(
3756                            analyzer,
3757                            syntax.as_ref(),
3758                            candidate,
3759                            &reference,
3760                        )
3761                    })
3762            };
3763            let Some(declaration_activation) = declaration_activation else {
3764                continue;
3765            };
3766            let activation_byte = if candidate.source() == file {
3767                Some(declaration_activation)
3768            } else {
3769                self.include_activation_for_source(cpp, file, prepared, candidate.source())
3770            };
3771            if let Some(activation_byte) = activation_byte {
3772                arities.push(ActivatedCallableArity {
3773                    activation_byte,
3774                    arity: candidate_arity,
3775                });
3776            }
3777        }
3778        arities
3779    }
3780
3781    fn callable_parameter_macro_arity(
3782        &self,
3783        target: &CodeUnit,
3784        signature: Option<&str>,
3785    ) -> Option<CallableArity> {
3786        let parameter_types = cpp_signature_param_types(signature?)?;
3787        let [macro_name] = parameter_types.as_slice() else {
3788            return None;
3789        };
3790        if macro_name.is_empty()
3791            || !macro_name
3792                .chars()
3793                .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
3794        {
3795            return None;
3796        }
3797        let cache_key = (target.source().clone(), macro_name.clone());
3798        if let Some(cached) = self
3799            .callable_parameter_macro_arities
3800            .lock()
3801            .expect("C++ callable parameter-macro arity cache poisoned")
3802            .get(&cache_key)
3803            .copied()
3804        {
3805            return cached;
3806        }
3807        let mut visible_files = HashSet::default();
3808        collect_include_closure(
3809            &self.cpp_source(),
3810            self.cpp.include_target_index(),
3811            target.source(),
3812            &mut visible_files,
3813            None,
3814        );
3815        let mut arities = Vec::new();
3816        for visible_file in visible_files {
3817            let cell = self.macro_event_cell(&visible_file);
3818            for event in
3819                cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
3820            {
3821                let MacroEvent::Define { name, binding, .. } = event else {
3822                    continue;
3823                };
3824                if name != macro_name {
3825                    continue;
3826                }
3827                let MacroDefinition::Object { replacement } = &binding.definition else {
3828                    continue;
3829                };
3830                let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
3831                    continue;
3832                };
3833                if !arities.contains(&arity) {
3834                    arities.push(arity);
3835                }
3836            }
3837        }
3838        let resolved = (|| {
3839            let required = arities
3840                .iter()
3841                .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
3842                .min()?;
3843            let total = arities.iter().map(|arity| arity.total()).max()?;
3844            let repeated = arities
3845                .iter()
3846                .any(|arity| arity.accepts(arity.total().saturating_add(1)));
3847            // Preprocessor conditions can leave more than one object-like parameter
3848            // bundle active in the target header's include closure. Preserve their
3849            // conservative callable envelope instead of choosing whichever definition
3850            // happened to be visited first.
3851            Some(CallableArity::new(required, total, repeated))
3852        })();
3853        self.callable_parameter_macro_arities
3854            .lock()
3855            .expect("C++ callable parameter-macro arity cache poisoned")
3856            .insert(cache_key, resolved);
3857        resolved
3858    }
3859
3860    pub fn include_activation_for_source(
3861        &self,
3862        cpp: &dyn CppSource,
3863        file: &ProjectFile,
3864        prepared: &PreparedSyntaxTree,
3865        donor_source: &ProjectFile,
3866    ) -> Option<usize> {
3867        let key = (file.clone(), donor_source.clone());
3868        if let Some(cached) = self
3869            .include_activation_cells
3870            .lock()
3871            .expect("C++ include activation cache poisoned")
3872            .get(&key)
3873            .copied()
3874        {
3875            return cached;
3876        }
3877        #[cfg(any(test, feature = "test-support"))]
3878        self.include_activation_build_count
3879            .fetch_add(1, Ordering::Relaxed);
3880        let activation = find_include_activation(cpp, self.token, file, prepared, donor_source);
3881        let mut cells = self
3882            .include_activation_cells
3883            .lock()
3884            .expect("C++ include activation cache poisoned");
3885        *cells.entry(key).or_insert(activation)
3886    }
3887
3888    pub fn conditional_include_projections_for_source(
3889        &self,
3890        file: &ProjectFile,
3891        prepared: &PreparedSyntaxTree,
3892        donor_source: &ProjectFile,
3893    ) -> Arc<[ConditionalIncludeProjection]> {
3894        static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
3895        let cell = self
3896            .conditional_include_projection_cells
3897            .lock()
3898            .expect("C++ conditional include projection cache poisoned")
3899            .entry(file.clone())
3900            .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
3901            .clone();
3902        let index = cell.get_or_build_pool_independent(|| {
3903            #[cfg(any(test, feature = "test-support"))]
3904            self.conditional_include_projection_index_build_count
3905                .fetch_add(1, Ordering::Relaxed);
3906            find_conditional_include_projection_index(self.cpp, self.token, file, prepared, &|| {
3907                #[cfg(any(test, feature = "test-support"))]
3908                self.conditional_include_projection_state_count
3909                    .fetch_add(1, Ordering::Relaxed);
3910            })
3911        });
3912        index
3913            .get(donor_source)
3914            .cloned()
3915            .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
3916    }
3917
3918    #[cfg(any(test, feature = "test-support"))]
3919    pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
3920        (
3921            self.conditional_include_projection_index_build_count
3922                .load(Ordering::Relaxed),
3923            self.conditional_include_projection_state_count
3924                .load(Ordering::Relaxed),
3925        )
3926    }
3927
3928    #[cfg(any(test, feature = "test-support"))]
3929    pub fn conditional_include_target_state_count_for_test(&self) -> usize {
3930        self.conditional_include_target_state_count
3931            .load(Ordering::Relaxed)
3932    }
3933
3934    #[cfg(any(test, feature = "test-support"))]
3935    pub fn include_activation_build_count_for_test(&self) -> usize {
3936        self.include_activation_build_count.load(Ordering::Relaxed)
3937    }
3938
3939    #[cfg(any(test, feature = "test-support"))]
3940    pub fn note_using_donor_activation_for_test(&self) {
3941        self.using_donor_activation_count
3942            .fetch_add(1, Ordering::Relaxed);
3943    }
3944
3945    #[cfg(not(any(test, feature = "test-support")))]
3946    pub fn note_using_donor_activation_for_test(&self) {}
3947
3948    #[cfg(any(test, feature = "test-support"))]
3949    pub fn note_using_namespace_lookup_for_test(&self) {
3950        self.using_namespace_lookup_count
3951            .fetch_add(1, Ordering::Relaxed);
3952    }
3953
3954    #[cfg(not(any(test, feature = "test-support")))]
3955    pub fn note_using_namespace_lookup_for_test(&self) {}
3956
3957    #[cfg(any(test, feature = "test-support"))]
3958    pub fn note_using_name_candidate_inspection_for_test(&self) {
3959        self.using_name_candidate_inspection_count
3960            .fetch_add(1, Ordering::Relaxed);
3961    }
3962
3963    #[cfg(not(any(test, feature = "test-support")))]
3964    pub fn note_using_name_candidate_inspection_for_test(&self) {}
3965
3966    #[cfg(any(test, feature = "test-support"))]
3967    pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
3968        (
3969            self.using_donor_activation_count.load(Ordering::Relaxed),
3970            self.using_namespace_lookup_count.load(Ordering::Relaxed),
3971            self.callable_reference_spec_build_count
3972                .load(Ordering::Relaxed),
3973            self.using_name_candidate_inspection_count
3974                .load(Ordering::Relaxed),
3975        )
3976    }
3977
3978    pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
3979        file == target.source()
3980            || self
3981                .visible_by_file
3982                .get(file)
3983                .is_some_and(|visible| visible.contains(target))
3984    }
3985
3986    /// Whether some declaration of `declaration`'s logical symbol is visible at
3987    /// `reference_byte` in `file`.
3988    ///
3989    /// The question is asked of the *logical* symbol, not of the physical unit:
3990    /// an out-of-line body in a `.cpp` nobody includes is never itself visible,
3991    /// and it does not have to be - what makes the call legal is the header
3992    /// declaration that the reference file does include. Reading that relation
3993    /// through `same_logical_callable` rather than through signature strings is
3994    /// the same #2010 correction the gates make, and it matters here because
3995    /// the body and the declaration are exactly the pair that spells one
3996    /// parameter type two ways.
3997    pub fn declaration_visible_at(
3998        &self,
3999        analyzer: &CppGraphSource<'_>,
4000        file: &ProjectFile,
4001        declaration: &CodeUnit,
4002        reference_byte: usize,
4003    ) -> bool {
4004        let reference_guards = OnceCell::new();
4005        self.visible_identifier_candidates(file, declaration.identifier())
4006            .filter(|candidate| {
4007                self.same_logical_callable(analyzer, candidate, declaration)
4008                    || flattened_macro_namespace_declaration_matches(
4009                        analyzer,
4010                        self.cpp,
4011                        file,
4012                        candidate,
4013                        declaration,
4014                        reference_byte,
4015                    )
4016            })
4017            .any(|candidate| {
4018                self.physical_declaration_visible_at(
4019                    analyzer,
4020                    file,
4021                    candidate,
4022                    reference_byte,
4023                    &reference_guards,
4024                )
4025            })
4026    }
4027
4028    /// Whether a physical declaration is visible at an exact structured
4029    /// reference node. Inverse C field references need the reference's own
4030    /// preprocessor environment before using the callable activation path:
4031    /// callable activation can establish that a field declaration is
4032    /// nameable, but it must not admit the opposite branch of that field's
4033    /// conditional family.
4034    pub fn declaration_visible_at_reference(
4035        &self,
4036        analyzer: &CppGraphSource<'_>,
4037        file: &ProjectFile,
4038        declaration: &CodeUnit,
4039        reference: Node<'_>,
4040    ) -> bool {
4041        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4042            return false;
4043        };
4044        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4045        let declaration_guards = declaration_guard_requirements(analyzer, self.cpp, declaration);
4046        if !declaration_guards.iter().any(|(_, required)| {
4047            guards_compatible_at_reference(required, reference_guards.as_ref())
4048        }) {
4049            return false;
4050        }
4051        let guards = OnceCell::new();
4052        self.physical_declaration_visible_at(
4053            analyzer,
4054            file,
4055            declaration,
4056            reference.start_byte(),
4057            &guards,
4058        )
4059    }
4060
4061    /// C forward navigation may bind a call to a later same-file definition.
4062    /// There is no earlier source declaration to activate in that legacy C
4063    /// shape, but the call's preprocessor environment must still imply the
4064    /// definition's requirements. Ordinary C++ and inverse visibility retain
4065    /// the declaration-order rule in [`Self::declaration_visible_at`].
4066    pub fn declaration_visible_for_c_forward_call(
4067        &self,
4068        analyzer: &CppGraphSource<'_>,
4069        file: &ProjectFile,
4070        declaration: &CodeUnit,
4071        reference_byte: usize,
4072    ) -> bool {
4073        if self.declaration_visible_at(analyzer, file, declaration, reference_byte) {
4074            return true;
4075        }
4076        if declaration.source() != file {
4077            return false;
4078        }
4079        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4080            return false;
4081        };
4082        let reference_guards = prepared
4083            .tree()
4084            .root_node()
4085            .descendant_for_byte_range(reference_byte, reference_byte)
4086            .and_then(|node| preprocessor_guard_environment(node, prepared.source()));
4087        declaration_guard_requirements(analyzer, self.cpp, declaration)
4088            .into_iter()
4089            .any(|(_, required)| {
4090                guard_requirements_hold_at_reference(&required, reference_guards.as_ref())
4091            })
4092    }
4093
4094    pub fn callable_arity_at_reference(
4095        &self,
4096        analyzer: &CppGraphSource<'_>,
4097        file: &ProjectFile,
4098        candidate: &CodeUnit,
4099        reference_byte: usize,
4100    ) -> Option<CallableArity> {
4101        let key = (file.clone(), logical_symbol_key(candidate));
4102        let cell = self
4103            .callable_reference_specs
4104            .lock()
4105            .expect("C++ callable reference-spec cache poisoned")
4106            .entry(key)
4107            .or_default()
4108            .clone();
4109        let spec = cell.get_or_init(|| {
4110            let prepared = self.cpp.prepared_syntax(self.token, file)?;
4111            let spec = TargetSpec::from_target(analyzer, candidate)?;
4112            let spec = spec
4113                .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
4114                .into_owned();
4115            #[cfg(any(test, feature = "test-support"))]
4116            self.callable_reference_spec_build_count
4117                .fetch_add(1, Ordering::Relaxed);
4118            Some(spec)
4119        });
4120        spec.as_ref()?.callable_arity_at(reference_byte)
4121    }
4122
4123    fn physical_declaration_visible_at(
4124        &self,
4125        analyzer: &CppGraphSource<'_>,
4126        file: &ProjectFile,
4127        declaration: &CodeUnit,
4128        reference_byte: usize,
4129        reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
4130    ) -> bool {
4131        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4132            return false;
4133        };
4134        let reference = CallableReferenceContext {
4135            file,
4136            position: Some(CallableReferencePosition {
4137                prepared: prepared.as_ref(),
4138                byte: reference_byte,
4139                guards: reference_guards,
4140            }),
4141        };
4142        if declaration.source() == file {
4143            return callable_declaration_activation_in_file(
4144                analyzer,
4145                prepared.as_ref(),
4146                declaration,
4147                &reference,
4148            )
4149            .or_else(|| {
4150                self.exhaustive_guard_family_activation(
4151                    analyzer,
4152                    prepared.as_ref(),
4153                    declaration,
4154                    &reference,
4155                )
4156            })
4157            .is_some_and(|activation| activation < reference_byte);
4158        }
4159        let Some(donor_syntax) = self.cpp.prepared_syntax(self.token, declaration.source()) else {
4160            return false;
4161        };
4162        if callable_declaration_activation_in_file(
4163            analyzer,
4164            donor_syntax.as_ref(),
4165            declaration,
4166            &reference,
4167        )
4168        .or_else(|| {
4169            self.exhaustive_guard_family_activation(
4170                analyzer,
4171                donor_syntax.as_ref(),
4172                declaration,
4173                &reference,
4174            )
4175        })
4176        .is_none()
4177        {
4178            return false;
4179        }
4180        declaration_guard_requirements(analyzer, self.cpp, declaration)
4181            .into_iter()
4182            .any(|(_, declaration_guards)| {
4183                self.foreign_declaration_reachable_at_reference(
4184                    file,
4185                    prepared.as_ref(),
4186                    declaration.source(),
4187                    &declaration_guards,
4188                    reference.guards(),
4189                    reference_byte,
4190                )
4191            })
4192    }
4193
4194    pub fn external_type_candidate_visible_at(
4195        &self,
4196        file: &ProjectFile,
4197        candidate: &CodeUnit,
4198        reference_byte: usize,
4199    ) -> bool {
4200        if candidate.source() == file {
4201            return true;
4202        }
4203        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4204            return false;
4205        };
4206        self.visible_identifier_candidates(file, candidate.identifier())
4207            .filter(|peer| same_logical_symbol(candidate, peer))
4208            .any(|peer| {
4209                peer.source() == file
4210                    || self
4211                        .include_activation_for_source(
4212                            self.cpp,
4213                            file,
4214                            prepared.as_ref(),
4215                            peer.source(),
4216                        )
4217                        .is_some_and(|activation| activation <= reference_byte)
4218            })
4219    }
4220
4221    pub fn external_type_declaration_visible_at(
4222        &self,
4223        file: &ProjectFile,
4224        candidate: &CodeUnit,
4225        reference_byte: usize,
4226    ) -> bool {
4227        if candidate.source() == file {
4228            return true;
4229        }
4230        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4231            return false;
4232        };
4233        self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
4234            .is_some_and(|activation| activation <= reference_byte)
4235    }
4236
4237    /// The preprocessor facts the build proves for a reference sited in
4238    /// `file` (#2011).
4239    ///
4240    /// Every `-D` that survives its command's `-D`/`-U` ordering is a positive
4241    /// `Defined` fact, and a fact holds only when every compile configuration
4242    /// that governs the file agrees on it (intersection). The facts are
4243    /// strictly additive to the reference's active guard set: they can prove a
4244    /// required guard, but the guard check itself is never weakened and no
4245    /// implication is ever inferred from source text.
4246    ///
4247    /// A file with its own database entry answers from that entry alone
4248    /// (phase 1). A header takes its context from the translation units whose
4249    /// include closure reaches it, intersected across all of them (phase 2):
4250    /// the header is compiled once per including TU, so a fact holds for a
4251    /// header-sited reference only when every one of those compilations
4252    /// proves it. A reaching TU the database does not cover proves nothing,
4253    /// which empties the intersection. A file nothing covers or reaches has
4254    /// no facts and every check runs on source structure alone.
4255    pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
4256        if let Some(cached) = self
4257            .compile_proven_guard_cells
4258            .lock()
4259            .expect("C++ compile-proven guard cache poisoned")
4260            .get(file)
4261        {
4262            return Arc::clone(cached);
4263        }
4264        let names = match context_fact_names(self.cpp.compile_contexts_for(file)) {
4265            Some(names) => names,
4266            None => {
4267                let mut translation_units = self.cpp.reaching_translation_units(file).into_iter();
4268                let seed = translation_units.next().and_then(|translation_unit| {
4269                    context_fact_names(self.cpp.compile_contexts_for(&translation_unit))
4270                });
4271                match seed {
4272                    None => HashSet::default(),
4273                    Some(mut names) => {
4274                        for translation_unit in translation_units {
4275                            let Some(reached) = context_fact_names(
4276                                self.cpp.compile_contexts_for(&translation_unit),
4277                            ) else {
4278                                names.clear();
4279                                break;
4280                            };
4281                            names.retain(|name| reached.contains(name));
4282                            if names.is_empty() {
4283                                break;
4284                            }
4285                        }
4286                        names
4287                    }
4288                }
4289            }
4290        };
4291        let proven = Arc::new(
4292            names
4293                .into_iter()
4294                .map(PreprocessorGuard::Defined)
4295                .collect::<HashSet<_>>(),
4296        );
4297        self.compile_proven_guard_cells
4298            .lock()
4299            .expect("C++ compile-proven guard cache poisoned")
4300            .insert(file.clone(), Arc::clone(&proven));
4301        proven
4302    }
4303
4304    /// Whether no compile data covers the compilations of `file`: it has no
4305    /// database entry of its own, and either nothing reaches it or some
4306    /// translation unit that reaches it has no entry. This is the state a
4307    /// regenerated `compile_commands.json` could decide; data that is present
4308    /// for every governing compilation but does not prove a guard is a
4309    /// decided conservative miss, not this state.
4310    fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
4311        if !self.cpp.compile_contexts_for(file).is_empty() {
4312            return false;
4313        }
4314        let translation_units = self.cpp.reaching_translation_units(file);
4315        translation_units.is_empty()
4316            || translation_units
4317                .iter()
4318                .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
4319    }
4320
4321    /// Whether a lookup miss for `identifier` in `file` is explainable by
4322    /// missing compile context (#2011): some same-name declaration is
4323    /// reachable through a conditional include whose required guards neither
4324    /// contradict the reference's active guards nor follow from them, and the
4325    /// translation unit has no compile-commands entry that could decide the
4326    /// question. Callers surface this as an explicit "requires compile
4327    /// context" incompleteness instead of an indistinguishable miss.
4328    ///
4329    /// A structurally disproven declaration (contradicting guards) and a TU
4330    /// whose compile context exists but does not prove the guard both answer
4331    /// `false`: those misses are decided, not incomplete.
4332    pub fn miss_requires_compile_context(
4333        &self,
4334        file: &ProjectFile,
4335        identifier: &str,
4336        reference: Node<'_>,
4337    ) -> bool {
4338        if !self.compile_context_is_absent(file) {
4339            return false;
4340        }
4341        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4342            return false;
4343        };
4344        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4345        let reference_byte = reference.start_byte();
4346        let mut sources = self
4347            .visible_identifier_candidates(file, identifier)
4348            .map(CodeUnit::source)
4349            .filter(|source| *source != file)
4350            .collect::<Vec<_>>();
4351        sources.sort();
4352        sources.dedup();
4353        sources.into_iter().any(|declaration_source| {
4354            self.conditional_include_projections_for_source(
4355                file,
4356                prepared.as_ref(),
4357                declaration_source,
4358            )
4359            .iter()
4360            .any(|projection| {
4361                projection.activation_byte <= reference_byte
4362                    && !guard_requirements_hold_at_reference(
4363                        &projection.required_guards,
4364                        reference_guards.as_ref(),
4365                    )
4366                    && guards_compatible_at_reference(
4367                        &projection.required_guards,
4368                        reference_guards.as_ref(),
4369                    )
4370            })
4371        })
4372    }
4373
4374    /// Decide whether a declaration that lives in another file reaches a
4375    /// reference in `file`.
4376    ///
4377    /// An external header selects its declaration branch before the reference
4378    /// file is parsed. Require compatible reference guards, but do not test
4379    /// the header's guard expression for stability in the reference file: a
4380    /// `.c` translation unit can never satisfy the `#ifdef __cplusplus` that
4381    /// wraps every declaration of a portable C header, and demanding it would
4382    /// hide the whole header. Guards that the reference file imposes on its
4383    /// own `#include` still have to hold, and still have to be stable.
4384    fn foreign_declaration_reachable_at_reference(
4385        &self,
4386        file: &ProjectFile,
4387        prepared: &PreparedSyntaxTree,
4388        declaration_source: &ProjectFile,
4389        declaration_guards: &HashSet<PreprocessorGuard>,
4390        reference_guards: Option<&HashSet<PreprocessorGuard>>,
4391        reference_byte: usize,
4392    ) -> bool {
4393        // The translation unit's build-proven defines join the reference's
4394        // active guard set (#2011): a conditional include like the nng
4395        // `NNG_PLATFORM_POSIX` chain is provable only by the compile command.
4396        // A reference whose own environment is unknown stays unknown -- the
4397        // facts extend an environment, they never invent one.
4398        let proven = self.compile_proven_guards(file);
4399        let augmented;
4400        let reference_guards = match reference_guards {
4401            Some(active) if !proven.is_empty() => {
4402                augmented = active.union(&proven).cloned().collect();
4403                Some(&augmented)
4404            }
4405            other => other,
4406        };
4407        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
4408            eprintln!(
4409                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=foreign_guard_compatibility declaration_source={} declaration_guards={declaration_guards:?} reference_guards={reference_guards:?}",
4410                declaration_source.rel_path().display(),
4411            );
4412        }
4413        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
4414            return false;
4415        }
4416        if self
4417            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
4418            .is_some_and(|activation| activation <= reference_byte)
4419        {
4420            return true;
4421        }
4422        let projections =
4423            self.conditional_include_projections_for_source(file, prepared, declaration_source);
4424        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
4425            eprintln!(
4426                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=filtered_projection source={} declaration_guards={} proven_guards={} projections={}",
4427                declaration_source.rel_path().display(),
4428                declaration_guards.len(),
4429                proven.len(),
4430                projections.len(),
4431            );
4432        }
4433        projections.iter().any(|projection| {
4434            projection.activation_byte <= reference_byte
4435                && guard_requirements_hold_at_reference(
4436                    &projection.required_guards,
4437                    reference_guards,
4438                )
4439                && self.preprocessor_guards_stable_between(
4440                    file,
4441                    projection.activation_byte,
4442                    reference_byte,
4443                    &projection.required_guards,
4444                )
4445        })
4446    }
4447
4448    fn foreign_declaration_may_be_reachable_from_raw_guards(
4449        &self,
4450        file: &ProjectFile,
4451        prepared: &PreparedSyntaxTree,
4452        declaration_source: &ProjectFile,
4453        declaration_guards: &HashSet<PreprocessorGuard>,
4454        reference_guards: Option<&HashSet<PreprocessorGuard>>,
4455        reference_byte: usize,
4456    ) -> bool {
4457        let proven = self.compile_proven_guards(file);
4458        let augmented;
4459        let reference_guards = match reference_guards {
4460            Some(active) if !proven.is_empty() => {
4461                augmented = active.union(&proven).cloned().collect();
4462                Some(&augmented)
4463            }
4464            other => other,
4465        };
4466        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
4467            return false;
4468        }
4469        if self
4470            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
4471            .is_some_and(|activation| activation <= reference_byte)
4472        {
4473            return true;
4474        }
4475        let reachable = find_conditional_include_projection_for_source(
4476            self.cpp,
4477            self.token,
4478            file,
4479            prepared,
4480            declaration_source,
4481            reference_guards,
4482            reference_byte,
4483            &|| {
4484                #[cfg(any(test, feature = "test-support"))]
4485                self.conditional_include_target_state_count
4486                    .fetch_add(1, Ordering::Relaxed);
4487            },
4488        );
4489        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
4490            eprintln!(
4491                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_projection source={} declaration_guards={} proven_guards={} raw_guards={} reachable={reachable}",
4492                declaration_source.rel_path().display(),
4493                declaration_guards.len(),
4494                proven.len(),
4495                reference_guards.map_or(0, HashSet::len),
4496            );
4497        }
4498        reachable
4499    }
4500
4501    fn foreign_declaration_reachable_from_compile_proven_guards(
4502        &self,
4503        file: &ProjectFile,
4504        prepared: &PreparedSyntaxTree,
4505        declaration_source: &ProjectFile,
4506        declaration_guards: &HashSet<PreprocessorGuard>,
4507        reference_byte: usize,
4508    ) -> bool {
4509        let proven = self.compile_proven_guards(file);
4510        if proven.is_empty()
4511            || !guards_compatible_at_reference(declaration_guards, Some(proven.as_ref()))
4512        {
4513            return false;
4514        }
4515        let projections =
4516            self.conditional_include_projections_for_source(file, prepared, declaration_source);
4517        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
4518            eprintln!(
4519                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=compile_proven_projection source={} declaration_guards={} proven_guards={} projections={}",
4520                declaration_source.rel_path().display(),
4521                declaration_guards.len(),
4522                proven.len(),
4523                projections.len(),
4524            );
4525        }
4526        projections.iter().any(|projection| {
4527            projection.activation_byte <= reference_byte
4528                    && guard_requirements_hold_at_reference(
4529                        &projection.required_guards,
4530                        Some(proven.as_ref()),
4531                    )
4532                    // Build facts hold at translation-unit entry. A source
4533                    // `#undef` or an earlier include may invalidate one before
4534                    // this conditional include is reached; mutations after the
4535                    // include cannot revoke declarations it already supplied.
4536                    && self.preprocessor_guards_stable_between(
4537                        file,
4538                        0,
4539                        projection.activation_byte,
4540                        &projection.required_guards,
4541                    )
4542        })
4543    }
4544
4545    pub fn external_type_candidate_visible_in_context(
4546        &self,
4547        analyzer: &CppGraphSource<'_>,
4548        file: &ProjectFile,
4549        candidate: &CodeUnit,
4550        reference: Node<'_>,
4551    ) -> bool {
4552        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
4553        if report_stats {
4554            eprintln!(
4555                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=started fqn={} candidate_source={} reference_file={} reference_byte={}",
4556                candidate.fq_name(),
4557                candidate.source().rel_path().display(),
4558                file.rel_path().display(),
4559                reference.start_byte(),
4560            );
4561        }
4562        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4563            return false;
4564        };
4565        let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
4566        let reference_guards = OnceCell::new();
4567        let reference_guards_at_site = || {
4568            reference_guards.get_or_init(|| {
4569                let started = Instant::now();
4570                if report_stats {
4571                    eprintln!(
4572                        "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=started file={} reference_byte={} raw_guards={}",
4573                        file.rel_path().display(),
4574                        reference.start_byte(),
4575                        raw_reference_guards.as_ref().map_or(0, HashSet::len),
4576                    );
4577                }
4578                let macro_environment = self.macro_environment(file, reference.start_byte());
4579                let filtered = raw_reference_guards
4580                    .clone()
4581                    .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
4582                if report_stats {
4583                    eprintln!(
4584                        "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=completed retained={} elapsed_ms={}",
4585                        filtered.is_some(),
4586                        started.elapsed().as_millis(),
4587                    );
4588                }
4589                filtered
4590            })
4591        };
4592
4593        let peers = self
4594            .visible_identifier_candidates(file, candidate.identifier())
4595            .filter(|peer| same_logical_symbol(candidate, peer))
4596            .collect::<Vec<_>>();
4597        if report_stats {
4598            let peer_sources = peers
4599                .iter()
4600                .map(|peer| peer.source().rel_path().display().to_string())
4601                .collect::<Vec<_>>();
4602            eprintln!(
4603                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=peers fqn={} sources={peer_sources:?}",
4604                candidate.fq_name(),
4605            );
4606        }
4607        let directly_visible_without_reference_environment = peers.iter().any(|peer| {
4608            declaration_guard_requirements(analyzer, self.cpp, peer)
4609                .into_iter()
4610                .any(|(declaration_byte, declaration_guards)| {
4611                    if peer.source() == file {
4612                        let visible = declaration_byte < reference.start_byte()
4613                            && declaration_guards.is_empty();
4614                        if report_stats {
4615                            eprintln!(
4616                                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=true visible={visible}",
4617                                peer.source().rel_path().display(),
4618                                declaration_guards.len(),
4619                            );
4620                        }
4621                        return visible;
4622                    }
4623                    let direct = declaration_guards.is_empty()
4624                        && self
4625                            .include_activation_for_source(
4626                                self.cpp,
4627                                file,
4628                                prepared.as_ref(),
4629                                peer.source(),
4630                            )
4631                            .is_some_and(|activation| activation <= reference.start_byte());
4632                    let compile_proven = !direct
4633                        && self.foreign_declaration_reachable_from_compile_proven_guards(
4634                            file,
4635                            prepared.as_ref(),
4636                            peer.source(),
4637                            &declaration_guards,
4638                            reference.start_byte(),
4639                        );
4640                    if report_stats {
4641                        eprintln!(
4642                            "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=false direct={direct} compile_proven={compile_proven}",
4643                            peer.source().rel_path().display(),
4644                            declaration_guards.len(),
4645                        );
4646                    }
4647                    direct || compile_proven
4648                })
4649        });
4650        if directly_visible_without_reference_environment {
4651            if report_stats {
4652                eprintln!(
4653                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=direct_or_compile_proven fqn={}",
4654                    candidate.fq_name(),
4655                );
4656            }
4657            return true;
4658        }
4659        let directly_visible = peers.iter().any(|peer| {
4660            declaration_guard_requirements(analyzer, self.cpp, peer)
4661                .into_iter()
4662                .any(|(declaration_byte, declaration_guards)| {
4663                    if peer.source() == file {
4664                        if declaration_byte >= reference.start_byte() {
4665                            return false;
4666                        }
4667                        if !guard_requirements_hold_at_reference(
4668                            &declaration_guards,
4669                            raw_reference_guards.as_ref(),
4670                        ) {
4671                            return false;
4672                        }
4673                        return guard_requirements_hold_at_reference(
4674                            &declaration_guards,
4675                            reference_guards_at_site().as_ref(),
4676                        ) && self.preprocessor_guards_stable_between(
4677                            file,
4678                            declaration_byte,
4679                            reference.start_byte(),
4680                            &declaration_guards,
4681                        );
4682                    }
4683                    let raw_feasible = self.foreign_declaration_may_be_reachable_from_raw_guards(
4684                        file,
4685                        prepared.as_ref(),
4686                        peer.source(),
4687                        &declaration_guards,
4688                        raw_reference_guards.as_ref(),
4689                        reference.start_byte(),
4690                    );
4691                    if report_stats {
4692                        eprintln!(
4693                            "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_feasibility source={} declaration_guards={} feasible={raw_feasible}",
4694                            peer.source().rel_path().display(),
4695                            declaration_guards.len(),
4696                        );
4697                    }
4698                    if !raw_feasible {
4699                        return false;
4700                    }
4701                    self.foreign_declaration_reachable_at_reference(
4702                        file,
4703                        prepared.as_ref(),
4704                        peer.source(),
4705                        &declaration_guards,
4706                        reference_guards_at_site().as_ref(),
4707                        reference.start_byte(),
4708                    )
4709                })
4710        });
4711        if directly_visible {
4712            if report_stats {
4713                eprintln!(
4714                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=filtered_reference fqn={}",
4715                    candidate.fq_name(),
4716                );
4717            }
4718            return true;
4719        }
4720        let complementary = self
4721            .visible_identifier_candidates(file, candidate.identifier())
4722            .filter(|peer| {
4723                peer.kind() == candidate.kind()
4724                    && peer.fq_name() == candidate.fq_name()
4725                    && peer.source() == candidate.source()
4726            })
4727            .collect::<Vec<_>>();
4728        // A completed #if/#else family declares the shared source-level name
4729        // before this reference. A later macro mutation cannot revoke that
4730        // declaration. The family gate below rejects declarations split across
4731        // separate conditional blocks, where mutation can change coverage.
4732        let complementary_family =
4733            self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate);
4734        let raw_candidate_branch_compatible = complementary_family
4735            && raw_reference_guards.as_ref().is_some_and(|active| {
4736                declaration_guard_requirements(analyzer, self.cpp, candidate)
4737                    .iter()
4738                    .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
4739            });
4740        if report_stats {
4741            eprintln!(
4742                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=complementary fqn={} candidates={} family={} raw_compatible={}",
4743                candidate.fq_name(),
4744                complementary.len(),
4745                complementary_family,
4746                raw_candidate_branch_compatible,
4747            );
4748        }
4749        let candidate_branch_compatible = raw_candidate_branch_compatible
4750            && reference_guards_at_site().as_ref().is_some_and(|active| {
4751                declaration_guard_requirements(analyzer, self.cpp, candidate)
4752                    .iter()
4753                    .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
4754            });
4755        let complementary_visible = candidate_branch_compatible
4756            && if candidate.source() == file {
4757                declaration_guard_requirements(analyzer, self.cpp, candidate)
4758                    .iter()
4759                    .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
4760            } else {
4761                self.include_activation_for_source(
4762                    self.cpp,
4763                    file,
4764                    prepared.as_ref(),
4765                    candidate.source(),
4766                )
4767                .is_some_and(|activation| activation <= reference.start_byte())
4768            };
4769        if report_stats {
4770            eprintln!(
4771                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome={} fqn={}",
4772                if complementary_visible {
4773                    "complementary"
4774                } else {
4775                    "missing"
4776                },
4777                candidate.fq_name(),
4778            );
4779        }
4780        complementary_visible
4781    }
4782
4783    pub fn is_exhaustive_same_fqn_type_declaration_family(
4784        &self,
4785        analyzer: &CppGraphSource<'_>,
4786        file: &ProjectFile,
4787        candidate: &CodeUnit,
4788    ) -> bool {
4789        let candidates = self
4790            .visible_identifier_candidates(file, candidate.identifier())
4791            .filter(|peer| {
4792                peer.kind() == candidate.kind()
4793                    && peer.fq_name() == candidate.fq_name()
4794                    && peer.source() == candidate.source()
4795            })
4796            .collect::<Vec<_>>();
4797        self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
4798    }
4799
4800    /// Prove a nested type alias used as a dependent member-pointer owner when
4801    /// its owning class has mutually-exclusive declarations.  A common C++11
4802    /// compatibility shape provides the owning class in one preprocessor
4803    /// branch and aliases it to a standard-library type in the other branch;
4804    /// the nested fallback alias is therefore not itself active in every
4805    /// branch even though the qualified owner API is.
4806    ///
4807    /// This is deliberately narrower than ordinary type visibility.  The
4808    /// caller has already recovered a member-pointer owner path from the CST;
4809    /// this helper additionally requires the target's structured parent to
4810    /// match that path, physical source visibility, and exact preprocessor
4811    /// guard agreement with the parent declaration.  Only then may the
4812    /// parent's direct/complementary same-FQN visibility stand in for the
4813    /// nested terminal's active-branch check.
4814    pub fn dependent_member_pointer_alias_visible_in_context(
4815        &self,
4816        analyzer: &CppGraphSource<'_>,
4817        file: &ProjectFile,
4818        candidate: &CodeUnit,
4819        owner_components: &[String],
4820        reference: Node<'_>,
4821    ) -> bool {
4822        if !analyzer
4823            .type_alias_provider()
4824            .is_some_and(|provider| provider.is_type_alias(candidate))
4825        {
4826            return false;
4827        }
4828        let Some((terminal, owner_prefix)) = owner_components.split_last() else {
4829            return false;
4830        };
4831        if terminal != candidate.identifier()
4832            || canonical_cpp_scope_components(candidate) != owner_components
4833        {
4834            return false;
4835        }
4836        let Some(expected_parent_fq_name) =
4837            brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
4838        else {
4839            return false;
4840        };
4841        let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
4842            return false;
4843        };
4844        if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
4845            || parent_anchor.source() != candidate.source()
4846            || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
4847        {
4848            return false;
4849        }
4850
4851        // The ordinary path already handles unguarded aliases (and preserves
4852        // same-file declaration ordering).  This fallback is only for a
4853        // physically visible declaration whose guard is the owning branch's
4854        // guard, so reject a same-file declaration that appears after the
4855        // reference before considering guard compatibility.
4856        if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
4857            || candidate.source() == file
4858                && !analyzer
4859                    .ranges(candidate)
4860                    .iter()
4861                    .any(|range| range.start_byte < reference.start_byte())
4862        {
4863            return false;
4864        }
4865
4866        let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
4867        if candidate_guards.is_empty() {
4868            return false;
4869        }
4870        let same_guard_sets =
4871            |left: &[(usize, HashSet<PreprocessorGuard>)],
4872             right: &[(usize, HashSet<PreprocessorGuard>)]| {
4873                left.iter().all(|(_, left_guards)| {
4874                    right
4875                        .iter()
4876                        .any(|(_, right_guards)| left_guards == right_guards)
4877                })
4878            };
4879        let parent_candidates = self
4880            .visible_identifier_candidates(file, parent_anchor.identifier())
4881            .filter(|peer| {
4882                peer.kind() == parent_anchor.kind()
4883                    && peer.fq_name() == expected_parent_fq_name.as_str()
4884                    && peer.source() == parent_anchor.source()
4885                    && canonical_cpp_scope_components(peer) == owner_prefix
4886            })
4887            .filter_map(|peer| {
4888                let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
4889                (candidate_guards.len() == parent_guards.len()
4890                    && same_guard_sets(&candidate_guards, &parent_guards)
4891                    && same_guard_sets(&parent_guards, &candidate_guards))
4892                .then(|| (peer.clone(), parent_guards))
4893            })
4894            .collect::<Vec<_>>();
4895        let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
4896            return false;
4897        };
4898
4899        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4900            return false;
4901        };
4902        let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
4903        else {
4904            return false;
4905        };
4906        // An external header selects its declaration branch before the
4907        // reference file is parsed. Require compatible reference guards, but
4908        // do not test the header's guard expression for stability in the
4909        // reference file. Same-file aliases still require that stability.
4910        if !candidate_guards.iter().any(|(_, target_guards)| {
4911            guards_compatible_at_reference(target_guards, Some(&reference_guards))
4912                && (candidate.source() != file
4913                    || self.preprocessor_guards_stable_between(
4914                        file,
4915                        0,
4916                        reference.start_byte(),
4917                        target_guards,
4918                    ))
4919        }) {
4920            return false;
4921        }
4922
4923        self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
4924    }
4925
4926    /// Check a type candidate's preprocessor/import context without imposing
4927    /// ordinary declaration-before-reference ordering for same-file peers.
4928    ///
4929    /// C++ class scope makes member names visible throughout the complete
4930    /// class, including a trailing return type that appears before the member
4931    /// alias declaration in source order. Callers must first prove that the
4932    /// reference is inside the candidate's indexed class owner; this helper
4933    /// only relaxes the byte-order predicate while retaining guard and include
4934    /// activation checks.
4935    pub fn external_type_candidate_guard_compatible_in_context(
4936        &self,
4937        analyzer: &CppGraphSource<'_>,
4938        file: &ProjectFile,
4939        candidate: &CodeUnit,
4940        reference: Node<'_>,
4941    ) -> bool {
4942        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4943            return false;
4944        };
4945        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4946
4947        self.visible_identifier_candidates(file, candidate.identifier())
4948            .filter(|peer| same_logical_symbol(candidate, peer))
4949            .any(|peer| {
4950                declaration_guard_requirements(analyzer, self.cpp, peer)
4951                    .into_iter()
4952                    .any(|(declaration_byte, declaration_guards)| {
4953                        if peer.source() == file {
4954                            let (start, end) = if declaration_byte <= reference.start_byte() {
4955                                (declaration_byte, reference.start_byte())
4956                            } else {
4957                                (reference.start_byte(), declaration_byte)
4958                            };
4959                            return guard_requirements_hold_at_reference(
4960                                &declaration_guards,
4961                                reference_guards.as_ref(),
4962                            ) && self.preprocessor_guards_stable_between(
4963                                file,
4964                                start,
4965                                end,
4966                                &declaration_guards,
4967                            );
4968                        }
4969                        self.foreign_declaration_reachable_at_reference(
4970                            file,
4971                            prepared.as_ref(),
4972                            peer.source(),
4973                            &declaration_guards,
4974                            reference_guards.as_ref(),
4975                            reference.start_byte(),
4976                        )
4977                    })
4978            })
4979    }
4980
4981    /// Whether a same-file callable declaration is nameable from `reference`
4982    /// after deliberately relaxing declaration-before-reference ordering.
4983    ///
4984    /// Ordinary lookup still requires an earlier declaration. Definition
4985    /// navigation for incomplete C translation units may recover a later
4986    /// definition, but only when it is at file scope and its preprocessor
4987    /// requirements hold at the call (#2404).
4988    pub fn same_file_callable_guard_compatible_ignoring_order(
4989        &self,
4990        analyzer: &CppGraphSource<'_>,
4991        file: &ProjectFile,
4992        candidate: &CodeUnit,
4993        reference: Node<'_>,
4994    ) -> bool {
4995        if candidate.source() != file || !candidate.is_callable() {
4996            return false;
4997        }
4998        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4999            return false;
5000        };
5001        let guards = OnceCell::new();
5002        let context = CallableReferenceContext {
5003            file,
5004            position: Some(CallableReferencePosition {
5005                prepared: prepared.as_ref(),
5006                byte: reference.start_byte(),
5007                guards: &guards,
5008            }),
5009        };
5010        nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
5011            .into_iter()
5012            .any(|declaration| {
5013                callable_preprocessor_context_is_visible_for_reference(
5014                    declaration,
5015                    prepared.source(),
5016                    &context,
5017                )
5018            })
5019    }
5020
5021    pub fn type_candidate_may_be_visible_before_reference(
5022        &self,
5023        analyzer: &CppGraphSource<'_>,
5024        file: &ProjectFile,
5025        candidate: &CodeUnit,
5026        reference_byte: usize,
5027    ) -> bool {
5028        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5029            return false;
5030        };
5031        let root = prepared.tree().root_node();
5032        let end_byte = reference_byte
5033            .saturating_add(1)
5034            .min(prepared.source().len());
5035        let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
5036            return false;
5037        };
5038        self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
5039    }
5040
5041    pub fn preprocessor_guards_stable_between(
5042        &self,
5043        file: &ProjectFile,
5044        start_byte: usize,
5045        end_byte: usize,
5046        guards: &HashSet<PreprocessorGuard>,
5047    ) -> bool {
5048        if guards.is_empty() || start_byte >= end_byte {
5049            return true;
5050        }
5051        let cell = self.macro_event_cell(file);
5052        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5053        let mut visited = HashSet::from_iter([file.clone()]);
5054        !events.iter().any(|event| {
5055            event.byte() >= start_byte
5056                && event.byte() < end_byte
5057                && self.macro_event_may_mutate_guards(event, guards, &mut visited)
5058        })
5059    }
5060
5061    fn macro_event_may_mutate_guards(
5062        &self,
5063        event: &MacroEvent,
5064        guards: &HashSet<PreprocessorGuard>,
5065        visited: &mut HashSet<ProjectFile>,
5066    ) -> bool {
5067        match event {
5068            MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
5069                guards.iter().any(|guard| guard.may_depend_on_macro(name))
5070            }
5071            MacroEvent::Include { targets, .. } => {
5072                targets.is_empty()
5073                    || targets
5074                        .iter()
5075                        .any(|target| self.source_may_mutate_guards(target, guards, visited))
5076            }
5077            MacroEvent::Invalidate { .. } => true,
5078        }
5079    }
5080
5081    fn source_may_mutate_guards(
5082        &self,
5083        file: &ProjectFile,
5084        guards: &HashSet<PreprocessorGuard>,
5085        visited: &mut HashSet<ProjectFile>,
5086    ) -> bool {
5087        if !visited.insert(file.clone()) {
5088            return false;
5089        }
5090        let cell = self.macro_event_cell(file);
5091        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5092        events
5093            .iter()
5094            .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
5095    }
5096
5097    pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
5098        let normalized = normalize_reference_name(raw_name)?;
5099        self.type_candidates(file, &normalized)
5100            .into_iter()
5101            .next()
5102            .cloned()
5103    }
5104
5105    /// Mirror forward navigation's visible-name fallback for a bare parameter
5106    /// type after lexical owner and inheritance lookup is exhausted.
5107    ///
5108    /// Generated or otherwise unindexed base classes can hide the alias that
5109    /// makes a parameter type valid C++. Accept the fallback only when every
5110    /// include-visible class or alias with that spelling canonicalizes to one
5111    /// logical type. A shadowing local type resolves lexically before this
5112    /// path, while distinct visible types keep the result ambiguous.
5113    pub fn unique_visible_parameter_type_fallback(
5114        &self,
5115        analyzer: &CppGraphSource<'_>,
5116        file: &ProjectFile,
5117        node: Node<'_>,
5118        source: &str,
5119    ) -> Option<CodeUnit> {
5120        if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
5121            return None;
5122        }
5123        let name = node_text(node, source);
5124        let candidates = self
5125            .visible_identifier_candidates(file, name)
5126            .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
5127            .filter(|candidate| {
5128                self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
5129            })
5130            .collect::<Vec<_>>();
5131        self.unique_canonical_type_candidate(analyzer, file, &candidates)
5132    }
5133
5134    pub fn resolve_type_node_result(
5135        &self,
5136        file: &ProjectFile,
5137        node: Node<'_>,
5138        source: &str,
5139    ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
5140        let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
5141            return Ok(None);
5142        };
5143        let Some(arguments) = cpp_template_reference_arguments(node, source) else {
5144            return Ok(Some(primary));
5145        };
5146        self.resolve_template_arguments(file, primary, &arguments)
5147            .map(Some)
5148    }
5149
5150    pub fn resolve_type_node_primary(
5151        &self,
5152        file: &ProjectFile,
5153        node: Node<'_>,
5154        source: &str,
5155    ) -> Option<CodeUnit> {
5156        let components = cpp_type_name_components(node, source)?;
5157        self.resolve_type(file, &components.join("::"))
5158    }
5159
5160    pub fn resolve_template_arguments(
5161        &self,
5162        file: &ProjectFile,
5163        primary: CodeUnit,
5164        arguments: &[CppTemplateExpression],
5165    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
5166        self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
5167    }
5168
5169    fn resolve_template_arguments_inner(
5170        &self,
5171        file: &ProjectFile,
5172        primary: CodeUnit,
5173        arguments: &[CppTemplateExpression],
5174        seen_aliases: &mut HashSet<CodeUnit>,
5175    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
5176        if let Some(metadata) = self.cpp_template_metadata.get(&primary)
5177            && let Some(alias_target) = &metadata.alias_target
5178        {
5179            if !seen_aliases.insert(primary.clone()) {
5180                return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
5181            }
5182            let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
5183                .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
5184            let target_name = alias_target.components.join("::");
5185            let target_primary = if alias_target.global {
5186                unique_logical_type_candidate(self.type_candidates(file, &target_name))
5187            } else {
5188                self.resolve_unique_type_for_declaration(file, &primary, &target_name)
5189            };
5190            let Some(target_primary) = target_primary else {
5191                // A dependent or external RHS cannot be canonicalized from the
5192                // indexed graph. Preserve the alias's direct identity instead
5193                // of inventing a target from its source spelling.
5194                return Ok(primary);
5195            };
5196            let Some(target_arguments) = &alias_target.arguments else {
5197                return Ok(target_primary);
5198            };
5199            let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
5200                .ok_or(CppTemplateResolutionError::Substitution)?;
5201            return self.resolve_template_arguments_inner(
5202                file,
5203                target_primary,
5204                &target_arguments,
5205                seen_aliases,
5206            );
5207        }
5208
5209        let primary_fq_name = self
5210            .cpp_template_metadata
5211            .get(&primary)
5212            .map(|metadata| metadata.primary_fq_name.clone())
5213            .unwrap_or_else(|| primary.fq_name());
5214        let has_specialization_metadata = self
5215            .cpp_template_families
5216            .get(&primary_fq_name)
5217            .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
5218        if !has_specialization_metadata {
5219            return Ok(primary);
5220        }
5221        self.select_template_specialization(file, &primary, arguments)
5222    }
5223
5224    fn select_template_specialization(
5225        &self,
5226        file: &ProjectFile,
5227        resolved: &CodeUnit,
5228        explicit_arguments: &[CppTemplateExpression],
5229    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
5230        let primary_fq_name = self
5231            .cpp_template_metadata
5232            .get(resolved)
5233            .map(|metadata| metadata.primary_fq_name.clone())
5234            .unwrap_or_else(|| resolved.fq_name());
5235        let family = self
5236            .cpp_template_families
5237            .get(&primary_fq_name)
5238            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
5239        let primary_candidates = family
5240            .iter()
5241            .filter_map(|unit| {
5242                let metadata = self.cpp_template_metadata.get(unit)?;
5243                (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
5244            })
5245            .collect::<Vec<_>>();
5246        let primary_unit = primary_candidates
5247            .iter()
5248            .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
5249            .or_else(|| {
5250                primary_candidates
5251                    .iter()
5252                    .map(|(unit, _)| *unit)
5253                    .min_by_key(|unit| {
5254                        (
5255                            unit.source().to_string(),
5256                            unit.signature().unwrap_or_default(),
5257                        )
5258                    })
5259            })
5260            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
5261        let primary_parameters =
5262            cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
5263                .ok_or(CppTemplateResolutionError::PrimarySelection)?;
5264        let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
5265            .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
5266
5267        let mut applicable = Vec::new();
5268        for unit in family {
5269            let Some(metadata) = self.cpp_template_metadata.get(unit) else {
5270                continue;
5271            };
5272            if metadata.is_primary() || !self.is_visible(file, unit) {
5273                continue;
5274            }
5275            if !cpp_specialization_matches(metadata, &expanded) {
5276                continue;
5277            }
5278            applicable.push((unit, metadata));
5279        }
5280        if applicable.is_empty() {
5281            return Ok(primary_unit.clone());
5282        }
5283
5284        // A scalar constraint count cannot represent C++ partial ordering:
5285        // e.g. `<T*, U>` and `<T, int>` are incomparable for `<int*, int>`.
5286        // Select only a logical candidate whose structural pattern is strictly
5287        // more specialized than every other distinct applicable candidate.
5288        let winners = applicable
5289            .iter()
5290            .filter(|(candidate, candidate_metadata)| {
5291                applicable.iter().all(|(other, other_metadata)| {
5292                    same_visible_symbol(candidate, other)
5293                        || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
5294                })
5295            })
5296            .copied()
5297            .collect::<Vec<_>>();
5298        let Some((selected, _)) = winners.first() else {
5299            // Mutually incomparable applicable candidates: every one of them
5300            // is a live contender.
5301            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
5302                candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
5303            });
5304        };
5305        if winners
5306            .iter()
5307            .any(|(unit, _)| !same_visible_symbol(unit, selected))
5308        {
5309            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
5310                candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
5311            });
5312        }
5313        Ok((*selected).clone())
5314    }
5315
5316    pub fn resolve_type_components_lexically(
5317        &self,
5318        analyzer: &CppGraphSource<'_>,
5319        file: &ProjectFile,
5320        components: &[String],
5321        global: bool,
5322        lexical_scope: &[String],
5323    ) -> LexicalTypeResolution {
5324        self.resolve_type_components_lexically_inner(
5325            analyzer,
5326            file,
5327            components,
5328            global,
5329            lexical_scope,
5330            TypeCandidateResolution::Canonical,
5331        )
5332    }
5333
5334    pub fn resolve_type_components_lexically_for_forward(
5335        &self,
5336        analyzer: &CppGraphSource<'_>,
5337        file: &ProjectFile,
5338        components: &[String],
5339        global: bool,
5340        lexical_scope: &[String],
5341    ) -> LexicalTypeResolution {
5342        self.resolve_type_components_lexically_inner(
5343            analyzer,
5344            file,
5345            components,
5346            global,
5347            lexical_scope,
5348            TypeCandidateResolution::PreserveAlias,
5349        )
5350    }
5351
5352    pub fn resolve_type_components_lexically_for_target(
5353        &self,
5354        analyzer: &CppGraphSource<'_>,
5355        file: &ProjectFile,
5356        components: &[String],
5357        global: bool,
5358        lexical_scope: &[String],
5359        target: &CodeUnit,
5360    ) -> LexicalTypeResolution {
5361        #[cfg(any(test, feature = "test-support"))]
5362        self.target_preserving_type_resolution_count
5363            .fetch_add(1, Ordering::Relaxed);
5364        self.resolve_type_components_lexically_inner(
5365            analyzer,
5366            file,
5367            components,
5368            global,
5369            lexical_scope,
5370            TypeCandidateResolution::PreserveTarget(target),
5371        )
5372    }
5373
5374    pub fn coarse_unqualified_type_reference_may_resolve(
5375        &self,
5376        file: &ProjectFile,
5377        name: &str,
5378    ) -> bool {
5379        if name.is_empty() {
5380            return true;
5381        }
5382        self.visible_identifier_candidates(file, name)
5383            .any(|candidate| candidate.kind() == CodeUnitType::Class || is_type_alias(candidate))
5384            || self.visible_parser_alias_name_is_visible(file, name)
5385    }
5386
5387    #[allow(clippy::too_many_arguments)]
5388    pub fn structured_type_reference_may_resolve_to_target(
5389        &self,
5390        analyzer: &CppGraphSource<'_>,
5391        file: &ProjectFile,
5392        components: &[String],
5393        global: bool,
5394        lexical_scope: &[String],
5395        target: &CodeUnit,
5396    ) -> bool {
5397        if components.is_empty() {
5398            return true;
5399        }
5400        let Some(terminal) = components.last() else {
5401            return true;
5402        };
5403        let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
5404            .map(|qualified| qualified.join("::"))
5405            .collect::<Vec<_>>();
5406        let target_name = cpp_name_for(target);
5407        if qualified_tiers
5408            .iter()
5409            .any(|qualified| qualified == &target_name)
5410        {
5411            return true;
5412        }
5413
5414        let mut saw_shape_candidate = false;
5415        for candidate in self.visible_identifier_candidates(file, terminal) {
5416            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
5417            {
5418                continue;
5419            }
5420            let candidate_name = cpp_name_for(candidate);
5421            let shape_matches = if global || components.len() > 1 {
5422                qualified_tiers
5423                    .iter()
5424                    .any(|qualified| qualified == &candidate_name)
5425            } else {
5426                true
5427            };
5428            if !shape_matches {
5429                continue;
5430            }
5431            saw_shape_candidate = true;
5432            if same_visible_symbol(candidate, target)
5433                || self.c_tag_declaration_family_matches_target(
5434                    analyzer,
5435                    file,
5436                    std::slice::from_ref(&candidate),
5437                    target,
5438                )
5439                || self.compatible_primary_template_redeclarations(candidate, target)
5440                || (declared_type_alias(analyzer, candidate)
5441                    && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
5442            {
5443                return true;
5444            }
5445        }
5446
5447        !saw_shape_candidate
5448    }
5449
5450    /// A C tag's forward declaration and complete definition are one logical
5451    /// type even when their indexed signatures and source files differ. Keep
5452    /// this identity narrow: the complete declaration must be a top-level C
5453    /// tag, every visible candidate must be its physical forward declaration,
5454    /// and the parsed tag kind must agree. Different FQNs, competing complete
5455    /// definitions, aliases, and struct/union mismatches remain ambiguous.
5456    fn cached_c_tag_kind(
5457        &self,
5458        analyzer: &CppGraphSource<'_>,
5459        candidate: &CodeUnit,
5460    ) -> Option<CppCTagKind> {
5461        if let Some(kind) = self
5462            .c_tag_kind_cache
5463            .lock()
5464            .expect("C tag kind cache poisoned")
5465            .get(candidate)
5466        {
5467            return *kind;
5468        }
5469        let kind = indexed_c_tag_kind(analyzer, candidate);
5470        self.c_tag_kind_cache
5471            .lock()
5472            .expect("C tag kind cache poisoned")
5473            .insert(candidate.clone(), kind);
5474        kind
5475    }
5476
5477    fn cached_unique_c_tag_complete_definition(
5478        &self,
5479        analyzer: &CppGraphSource<'_>,
5480        target: &CodeUnit,
5481        target_tag: CppCTagKind,
5482    ) -> Option<CodeUnit> {
5483        if let Some(definition) = self
5484            .c_tag_complete_definition_cache
5485            .lock()
5486            .expect("C tag complete-definition cache poisoned")
5487            .get(target)
5488        {
5489            return definition.clone();
5490        }
5491        let complete_definitions = analyzer
5492            .definitions(&target.fq_name())
5493            .filter(|candidate| {
5494                candidate.is_class()
5495                    && !declared_type_alias(analyzer, candidate)
5496                    && is_c_source_file(candidate.source())
5497                    && analyzer.parent_of(candidate).is_none()
5498                    && cpp_class_declaration_strength(analyzer, candidate)
5499                        == CppClassDeclarationStrength::Full
5500                    && self.cached_c_tag_kind(analyzer, candidate) == Some(target_tag)
5501            })
5502            .collect::<HashSet<_>>();
5503        let definition = (complete_definitions.len() == 1)
5504            .then(|| complete_definitions.into_iter().next())
5505            .flatten()
5506            .filter(|candidate| same_visible_symbol(candidate, target));
5507        self.c_tag_complete_definition_cache
5508            .lock()
5509            .expect("C tag complete-definition cache poisoned")
5510            .insert(target.clone(), definition.clone());
5511        definition
5512    }
5513
5514    pub fn c_tag_declaration_family_matches_target(
5515        &self,
5516        analyzer: &CppGraphSource<'_>,
5517        visible_from: &ProjectFile,
5518        candidates: &[&CodeUnit],
5519        target: &CodeUnit,
5520    ) -> bool {
5521        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5522            let candidate_evidence = candidates
5523                .iter()
5524                .map(|candidate| {
5525                    (
5526                        candidate.fq_name(),
5527                        candidate.source().rel_path().to_path_buf(),
5528                        cpp_class_declaration_strength(analyzer, candidate),
5529                        indexed_c_tag_kind(analyzer, candidate),
5530                        self.is_physically_visible(visible_from, candidate),
5531                    )
5532                })
5533                .collect::<Vec<_>>();
5534            eprintln!(
5535                "BIFROST_CPP_C_TAG_FAMILY_STATS visible_from={} target=({}, {}, {:?}, {:?}) candidates={candidate_evidence:?}",
5536                visible_from.rel_path().display(),
5537                target.fq_name(),
5538                target.source().rel_path().display(),
5539                cpp_class_declaration_strength(analyzer, target),
5540                indexed_c_tag_kind(analyzer, target),
5541            );
5542        }
5543        if candidates.is_empty()
5544            || !target.is_class()
5545            || declared_type_alias(analyzer, target)
5546            || !is_c_source_file(target.source())
5547            || analyzer.parent_of(target).is_some()
5548            || cpp_class_declaration_strength(analyzer, target) != CppClassDeclarationStrength::Full
5549        {
5550            return false;
5551        }
5552        let Some(target_tag) = self.cached_c_tag_kind(analyzer, target) else {
5553            return false;
5554        };
5555        if self
5556            .cached_unique_c_tag_complete_definition(analyzer, target, target_tag)
5557            .is_none()
5558        {
5559            return false;
5560        }
5561        let mut saw_visible_forward = false;
5562        for candidate in candidates.iter().copied() {
5563            if candidate == target {
5564                continue;
5565            }
5566            if !candidate.is_class()
5567                || declared_type_alias(analyzer, candidate)
5568                || candidate.fq_name() != target.fq_name()
5569                || analyzer.parent_of(candidate).is_some()
5570                || cpp_class_declaration_strength(analyzer, candidate)
5571                    != CppClassDeclarationStrength::Forward
5572                || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
5573                || !self.is_physically_visible(visible_from, candidate)
5574            {
5575                return false;
5576            }
5577            saw_visible_forward = true;
5578        }
5579        saw_visible_forward
5580    }
5581
5582    /// Collapse one visible complete C tag and its visible forward declarations
5583    /// before ordinary lexical resolution sees their different signatures as
5584    /// competing types. A second complete definition, a different FQN/tag
5585    /// kind, or an unknown declaration shape remains ambiguous.
5586    fn unique_c_tag_declaration_family(
5587        &self,
5588        analyzer: &CppGraphSource<'_>,
5589        visible_from: &ProjectFile,
5590        candidates: &[&CodeUnit],
5591    ) -> Option<CodeUnit> {
5592        let first = candidates.first()?;
5593        let target_fq_name = first.fq_name();
5594        let target_tag = self.cached_c_tag_kind(analyzer, first)?;
5595        let mut full = None;
5596        let mut saw_forward = false;
5597        for candidate in candidates.iter().copied() {
5598            if !candidate.is_class()
5599                || declared_type_alias(analyzer, candidate)
5600                || candidate.fq_name() != target_fq_name
5601                || analyzer.parent_of(candidate).is_some()
5602                || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
5603            {
5604                return None;
5605            }
5606            match cpp_class_declaration_strength(analyzer, candidate) {
5607                CppClassDeclarationStrength::Full
5608                    if is_c_source_file(candidate.source())
5609                        && full.replace(candidate.clone()).is_none() => {}
5610                CppClassDeclarationStrength::Forward
5611                    if self.is_physically_visible(visible_from, candidate) =>
5612                {
5613                    saw_forward = true
5614                }
5615                _ => return None,
5616            }
5617        }
5618        if saw_forward { full } else { None }
5619    }
5620
5621    pub fn target_preserving_reference_namespace(
5622        &self,
5623        analyzer: &CppGraphSource<'_>,
5624        file: &ProjectFile,
5625        identifier: &str,
5626        target: &CodeUnit,
5627    ) -> Option<Vec<String>> {
5628        let mut namespace = None;
5629        for candidate in self.visible_identifier_candidates(file, identifier) {
5630            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
5631            {
5632                continue;
5633            }
5634            if !(same_visible_symbol(candidate, target)
5635                || self.compatible_primary_template_redeclarations(candidate, target)
5636                || declared_type_alias(analyzer, candidate)
5637                    && self.structured_alias_primary_preserves_target(
5638                        analyzer, file, candidate, target,
5639                    ))
5640            {
5641                continue;
5642            }
5643            if namespace
5644                .as_ref()
5645                .is_some_and(|existing| existing != candidate.package_name())
5646            {
5647                return None;
5648            }
5649            namespace = Some(candidate.package_name().to_string());
5650        }
5651        let namespace = namespace?;
5652        Some(
5653            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5654                brokk_bifrost_core::analyzer::Language::Cpp,
5655                &namespace,
5656            ),
5657        )
5658    }
5659
5660    pub fn resolve_imported_type_candidate(
5661        &self,
5662        analyzer: &CppGraphSource<'_>,
5663        file: &ProjectFile,
5664        target: &CodeUnit,
5665        target_components: &[String],
5666        direct_target: Option<&CodeUnit>,
5667        preserve_alias: bool,
5668    ) -> LexicalTypeResolution {
5669        let candidates = [target];
5670        let resolution = if preserve_alias {
5671            TypeCandidateResolution::PreserveAlias
5672        } else {
5673            direct_target.map_or(
5674                TypeCandidateResolution::Canonical,
5675                TypeCandidateResolution::PreserveTarget,
5676            )
5677        };
5678        // One candidate goes in, so a failure here is never "choose one of
5679        // these": it is the alias chain leaving the index, which must answer
5680        // missing rather than ambiguous (#1828).
5681        match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
5682            Ok(unit) => LexicalTypeResolution::Resolved {
5683                unit,
5684                components: target_components.to_vec(),
5685                candidates: vec![target.clone()],
5686            },
5687            Err(failure) => failure.lexical_resolution(),
5688        }
5689    }
5690
5691    fn resolve_type_components_lexically_inner(
5692        &self,
5693        analyzer: &CppGraphSource<'_>,
5694        file: &ProjectFile,
5695        components: &[String],
5696        global: bool,
5697        lexical_scope: &[String],
5698        resolution: TypeCandidateResolution<'_>,
5699    ) -> LexicalTypeResolution {
5700        if components.is_empty() {
5701            return LexicalTypeResolution::Missing;
5702        }
5703        // A C++ class injects its own name into the class scope.  The indexed
5704        // FqName for that declaration is the class path itself (for example,
5705        // `n::raw_hash_set`), not a synthetic child named
5706        // `n::raw_hash_set::raw_hash_set`.  Ordinary lexical tiers append the
5707        // requested identifier to every scope component, so they cannot
5708        // represent that injected binding when the enclosing class is the
5709        // closest scope.  Recover the binding from the structured class path
5710        // before allowing lookup to fall through to an outer same-spelled
5711        // declaration.
5712        let mut injected = self.resolve_injected_class_name(
5713            analyzer,
5714            file,
5715            components,
5716            global,
5717            lexical_scope,
5718            resolution,
5719        );
5720        for qualified in lexical_component_tiers(components, global, lexical_scope) {
5721            let prefix_len = qualified.len().saturating_sub(components.len());
5722            if injected
5723                .as_ref()
5724                .is_some_and(|(owner_len, _)| prefix_len < *owner_len)
5725            {
5726                return injected
5727                    .take()
5728                    .expect("injected class resolution was just present")
5729                    .1;
5730            }
5731            let qualified_name = qualified.join("::");
5732            let candidates = self
5733                .type_candidates(file, &qualified_name)
5734                .into_iter()
5735                .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
5736                .collect::<Vec<_>>();
5737            if candidates.is_empty() {
5738                if !global && components.len() == 1 {
5739                    match self.resolve_inherited_type_for_lexical_scope(
5740                        analyzer,
5741                        file,
5742                        &qualified[..prefix_len],
5743                        &components[0],
5744                        resolution,
5745                    ) {
5746                        LexicalTypeResolution::Missing => {}
5747                        inherited => return inherited,
5748                    }
5749                }
5750                continue;
5751            }
5752            let candidates =
5753                self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
5754            let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
5755                Ok(unit) => unit,
5756                Err(failure) => return failure.lexical_resolution(),
5757            };
5758            return LexicalTypeResolution::Resolved {
5759                unit,
5760                components: qualified,
5761                candidates: candidates.into_iter().cloned().collect(),
5762            };
5763        }
5764        LexicalTypeResolution::Missing
5765    }
5766
5767    fn resolve_injected_class_name(
5768        &self,
5769        analyzer: &CppGraphSource<'_>,
5770        file: &ProjectFile,
5771        components: &[String],
5772        global: bool,
5773        lexical_scope: &[String],
5774        resolution: TypeCandidateResolution<'_>,
5775    ) -> Option<(usize, LexicalTypeResolution)> {
5776        if global
5777            || components.len() != 1
5778            || file.rel_path().extension().is_some_and(|ext| ext == "c")
5779            || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
5780        {
5781            return None;
5782        }
5783        let name = components.first()?;
5784        let mut matches: Vec<&CodeUnit> = Vec::new();
5785        let mut owner_len = 0;
5786        for candidate in self.visible_identifier_candidates(file, name) {
5787            if !candidate.is_class()
5788                || declared_type_alias(analyzer, candidate)
5789                || candidate.identifier() != name
5790            {
5791                continue;
5792            }
5793            let candidate_scope = canonical_cpp_scope_components(candidate);
5794            if candidate_scope.len() > lexical_scope.len()
5795                || !lexical_scope.starts_with(&candidate_scope)
5796                || candidate_scope.last().is_none_or(|last| last != name)
5797            {
5798                continue;
5799            }
5800            if candidate_scope.len() > owner_len {
5801                owner_len = candidate_scope.len();
5802                matches.clear();
5803            }
5804            if candidate_scope.len() == owner_len
5805                && !matches
5806                    .iter()
5807                    .any(|existing| same_logical_symbol(existing, candidate))
5808            {
5809                matches.push(candidate);
5810            }
5811        }
5812        if matches.is_empty() {
5813            return None;
5814        }
5815        // A same-named class at the current lexical boundary is already
5816        // represented by the ordinary namespace/class tier.  The injected
5817        // recovery is only needed when lookup is occurring inside a nested
5818        // class, where the enclosing class name is injected across that
5819        // additional class boundary.  Keeping this boundary strict avoids
5820        // treating qualified receiver/static-qualifier context as an
5821        // injected-name reference.
5822        if owner_len >= lexical_scope.len() {
5823            return None;
5824        }
5825        let owner_components = lexical_scope[..owner_len].to_vec();
5826        let matches = self.candidates_for_type_resolution(analyzer, file, &matches, resolution);
5827        let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
5828            Ok(unit) => LexicalTypeResolution::Resolved {
5829                unit,
5830                components: owner_components,
5831                candidates: matches.into_iter().cloned().collect(),
5832            },
5833            Err(failure) => failure.lexical_resolution(),
5834        };
5835        Some((owner_len, resolution))
5836    }
5837
5838    fn resolve_inherited_type_for_lexical_scope(
5839        &self,
5840        analyzer: &CppGraphSource<'_>,
5841        file: &ProjectFile,
5842        lexical_scope: &[String],
5843        name: &str,
5844        resolution: TypeCandidateResolution<'_>,
5845    ) -> LexicalTypeResolution {
5846        let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
5847            return LexicalTypeResolution::Missing;
5848        };
5849        let lexical_owner_name = lexical_scope.join("::");
5850        if lexical_owner_name.is_empty() {
5851            return LexicalTypeResolution::Missing;
5852        }
5853        let owner_candidates = self
5854            .type_candidates(file, &lexical_owner_name)
5855            .into_iter()
5856            .filter(|candidate| {
5857                canonical_cpp_name_matches(candidate, &lexical_owner_name)
5858                    && !declared_type_alias(analyzer, candidate)
5859            })
5860            .collect::<Vec<_>>();
5861        if owner_candidates.is_empty() {
5862            return LexicalTypeResolution::Missing;
5863        }
5864        // A visible forward declaration and the physical class definition share
5865        // one FQN, but only the definition owns hierarchy facts. When lookup is
5866        // physically inside that definition, do not let an earlier header
5867        // forward declaration erase its base edges (#2240).
5868        let physical_owner_candidates = owner_candidates
5869            .iter()
5870            .copied()
5871            .filter(|candidate| candidate.source() == file)
5872            .collect::<Vec<_>>();
5873        let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
5874            owner_candidates
5875        } else {
5876            physical_owner_candidates
5877        };
5878        let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
5879            return LexicalTypeResolution::Ambiguous;
5880        };
5881
5882        let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
5883        let mut visited_owners = HashSet::default();
5884        while !frontier.is_empty() {
5885            let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
5886            let mut next_frontier = Vec::new();
5887            for owner in frontier {
5888                if !visited_owners.insert(owner.fq_name()) {
5889                    continue;
5890                }
5891                let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
5892                let candidates = self
5893                    .type_candidates(file, &qualified_name)
5894                    .into_iter()
5895                    .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
5896                    .collect::<Vec<_>>();
5897                if candidates.is_empty() {
5898                    for ancestor in hierarchy.get_direct_ancestors(&owner) {
5899                        if !next_frontier
5900                            .iter()
5901                            .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
5902                        {
5903                            next_frontier.push(ancestor);
5904                        }
5905                    }
5906                    continue;
5907                }
5908                let candidates =
5909                    self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
5910                let unit =
5911                    match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
5912                        Ok(unit) => unit,
5913                        Err(failure) => return failure.lexical_resolution(),
5914                    };
5915                level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
5916            }
5917            if let Some((unit, candidates)) = level_matches.first().cloned() {
5918                let Some(first_declaration) = candidates.first() else {
5919                    return LexicalTypeResolution::Ambiguous;
5920                };
5921                if !level_matches.iter().all(|(_, declarations)| {
5922                    declarations
5923                        .iter()
5924                        .all(|declaration| same_logical_symbol(first_declaration, declaration))
5925                }) {
5926                    return LexicalTypeResolution::Ambiguous;
5927                }
5928                let mut components = lexical_scope.to_vec();
5929                components.push(name.to_string());
5930                return LexicalTypeResolution::Resolved {
5931                    unit,
5932                    components,
5933                    candidates,
5934                };
5935            }
5936            frontier = next_frontier;
5937        }
5938        LexicalTypeResolution::Missing
5939    }
5940
5941    /// Resolve a base class through its injected class name at the nearest
5942    /// inheritance tier. Distinct same-named bases at that tier are ambiguous.
5943    ///
5944    /// A base whose canonical full definition cannot be pinned from `file` -
5945    /// a forward declaration the include closure completes with two different
5946    /// full definitions, or an alias chain that leaves the index - stops the
5947    /// walk only when that base is spelled `injected_name`. The mem-initializer
5948    /// names a base by that base's own injected class name, so a base spelled
5949    /// differently can never be the one it names, whichever definition it would
5950    /// have turned out to be; aborting the level on its account instead loses
5951    /// the sibling base that *is* named (#2543). A base that is spelled
5952    /// `injected_name` still fails closed, because choosing a deeper same-named
5953    /// ancestor over it would bind the initializer to the wrong constructor.
5954    /// The skipped base carries its own ancestors out of the walk with it: with
5955    /// no canonical unit, the repeated-base accounting below cannot tell one
5956    /// inherited path through it from two.
5957    pub fn inherited_injected_class_owner(
5958        &self,
5959        analyzer: &CppGraphSource<'_>,
5960        file: &ProjectFile,
5961        enclosing_owner: &CodeUnit,
5962        injected_name: &str,
5963    ) -> Option<CodeUnit> {
5964        let hierarchy = analyzer.type_hierarchy_provider()?;
5965        let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
5966        let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
5967        while !frontier.is_empty() {
5968            let mut level_matches = Vec::new();
5969            let mut next_frontier = Vec::new();
5970            for raw_owner in frontier {
5971                let Some(owner) = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
5972                else {
5973                    if raw_owner.identifier() == injected_name {
5974                        return None;
5975                    }
5976                    continue;
5977                };
5978                let propagated = propagated_counts.entry(owner.clone()).or_default();
5979                if *propagated == 2 {
5980                    continue;
5981                }
5982                *propagated += 1;
5983                if owner.identifier() == injected_name {
5984                    level_matches.push(owner.clone());
5985                }
5986                next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
5987            }
5988            match level_matches.as_slice() {
5989                [owner] => return Some(owner.clone()),
5990                [_, ..] => return None,
5991                [] => {}
5992            }
5993            frontier = next_frontier;
5994        }
5995        None
5996    }
5997
5998    /// The one type the candidates name under `resolution`, or why they do not
5999    /// name one. The two preserving modes only ever reject candidates that
6000    /// disagree with each other, which is ambiguity; canonicalization can also
6001    /// fail because the alias chain leaves the index (#1828).
6002    fn resolve_type_candidates(
6003        &self,
6004        analyzer: &CppGraphSource<'_>,
6005        file: &ProjectFile,
6006        candidates: &[&CodeUnit],
6007        resolution: TypeCandidateResolution<'_>,
6008    ) -> Result<CodeUnit, TypeCandidateFailure> {
6009        if !matches!(resolution, TypeCandidateResolution::PreserveTarget(_))
6010            && let Some(unit) = self.unique_c_tag_declaration_family(analyzer, file, candidates)
6011        {
6012            return Ok(unit);
6013        }
6014        match resolution {
6015            TypeCandidateResolution::Canonical => {
6016                self.canonical_type_candidate_resolution(analyzer, file, candidates)
6017            }
6018            TypeCandidateResolution::PreserveAlias => {
6019                // A generated index can retain identical alias spellings from
6020                // mutually exclusive headers. When the reference file
6021                // physically reaches exactly one of those source declarations,
6022                // include closure is the structured evidence that selects it;
6023                // treating the two source spellings as an overload set makes a
6024                // reachable alias appear ambiguous (#1844).
6025                let same_fqn_alias_family = candidates.len() > 1
6026                    && candidates.iter().all(|candidate| {
6027                        declared_type_alias(analyzer, candidate)
6028                            && same_logical_symbol(candidates[0], candidate)
6029                    })
6030                    && candidates
6031                        .iter()
6032                        .any(|candidate| candidate.source() != candidates[0].source());
6033                if same_fqn_alias_family {
6034                    let physically_visible = candidates
6035                        .iter()
6036                        .copied()
6037                        .filter(|candidate| self.is_physically_visible(file, candidate))
6038                        .collect::<Vec<_>>();
6039                    // The family is one logical declaration only when the
6040                    // reachable spellings agree. Two same-FQN aliases whose
6041                    // written targets differ (`using Choice = Canonical;` in
6042                    // one header, `using Choice = ::Canonical;` in another)
6043                    // are a genuine conflict, and choosing the first indexed
6044                    // one silently binds the reference to an arbitrary owner
6045                    // (#2398). Collapse only a single reachable declaration
6046                    // or reachable declarations with one structured target;
6047                    // everything else stays ambiguous below.
6048                    let one_structured_target = physically_visible.len() > 1
6049                        && physically_visible.iter().skip(1).all(|candidate| {
6050                            let target = self.structured_alias_target(analyzer, candidate);
6051                            target.is_some()
6052                                && target
6053                                    == self.structured_alias_target(analyzer, physically_visible[0])
6054                        });
6055                    if physically_visible.len() == 1 || one_structured_target {
6056                        return Ok(physically_visible[0].clone());
6057                    }
6058                }
6059                unique_type_candidate_preserving_alias(analyzer, candidates)
6060                    .ok_or(TypeCandidateFailure::Ambiguous)
6061            }
6062            TypeCandidateResolution::PreserveTarget(target) => self
6063                .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
6064                .ok_or(TypeCandidateFailure::Ambiguous),
6065        }
6066    }
6067
6068    fn candidates_for_type_resolution<'b>(
6069        &self,
6070        analyzer: &CppGraphSource<'_>,
6071        file: &ProjectFile,
6072        candidates: &[&'b CodeUnit],
6073        resolution: TypeCandidateResolution<'_>,
6074    ) -> Vec<&'b CodeUnit> {
6075        if matches!(resolution, TypeCandidateResolution::PreserveAlias) && candidates.len() > 1 {
6076            let compile_proven = self.compile_proven_type_candidates(analyzer, file, candidates);
6077            if compile_proven.len() == 1 {
6078                return compile_proven;
6079            }
6080        }
6081        candidates.to_vec()
6082    }
6083
6084    /// Narrow a same-name forward lookup to the declaration selected by the
6085    /// translation unit's compile command. The lexical resolver intentionally
6086    /// does not receive a reference node, so this is the only compile-context
6087    /// evidence available at that stage. A single selected candidate is safe:
6088    /// the caller still checks include activation and the reference's own
6089    /// guards before reporting the result as visible.
6090    fn compile_proven_type_candidates<'b>(
6091        &self,
6092        analyzer: &CppGraphSource<'_>,
6093        file: &ProjectFile,
6094        candidates: &[&'b CodeUnit],
6095    ) -> Vec<&'b CodeUnit> {
6096        let proven = self.compile_proven_guards(file);
6097        if proven.is_empty() {
6098            return Vec::new();
6099        }
6100        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6101            return Vec::new();
6102        };
6103        candidates
6104            .iter()
6105            .copied()
6106            .filter(|candidate| {
6107                let declaration_guards =
6108                    declaration_guard_requirements(analyzer, self.cpp, candidate);
6109                if declaration_guards.is_empty() {
6110                    return false;
6111                }
6112                if candidate.source() == file {
6113                    return declaration_guards.iter().any(|(_, required)| {
6114                        guard_requirements_hold_at_reference(required, Some(proven.as_ref()))
6115                    });
6116                }
6117                declaration_guards.iter().any(|(_, required)| {
6118                    self.foreign_declaration_reachable_from_compile_proven_guards(
6119                        file,
6120                        prepared.as_ref(),
6121                        candidate.source(),
6122                        required,
6123                        usize::MAX,
6124                    )
6125                })
6126            })
6127            .collect()
6128    }
6129
6130    pub fn resolve_callable_value_components_lexically(
6131        &self,
6132        analyzer: &CppGraphSource<'_>,
6133        file: &ProjectFile,
6134        owner_components: &[String],
6135        member_name: &str,
6136        global: bool,
6137        lexical_scope: &[String],
6138    ) -> LexicalCallableValueResolution {
6139        if owner_components.is_empty() || member_name.is_empty() {
6140            return LexicalCallableValueResolution::Missing;
6141        }
6142        for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
6143            let owner_name = qualified_owner.join("::");
6144            let type_candidates = self
6145                .type_candidates(file, &owner_name)
6146                .into_iter()
6147                .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
6148                .collect::<Vec<_>>();
6149            let resolved_type = if type_candidates.is_empty() {
6150                None
6151            } else {
6152                let Some(unit) =
6153                    self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
6154                else {
6155                    return LexicalCallableValueResolution::Ambiguous;
6156                };
6157                Some(unit)
6158            };
6159
6160            let mut qualified_callable = qualified_owner;
6161            qualified_callable.push(member_name.to_string());
6162            let callable_name = qualified_callable.join("::");
6163            let free_function = self
6164                .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
6165                .into_iter()
6166                .find(|candidate| {
6167                    canonical_cpp_name_matches(candidate, &callable_name)
6168                        && type_owner_of(analyzer, candidate).is_none()
6169                })
6170                .cloned();
6171
6172            match (resolved_type, free_function) {
6173                (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
6174                (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
6175                (None, Some(function)) => {
6176                    return LexicalCallableValueResolution::FreeFunction(function);
6177                }
6178                (None, None) => {}
6179            }
6180        }
6181        LexicalCallableValueResolution::Missing
6182    }
6183
6184    fn resolve_type_for_declaration(
6185        &self,
6186        visible_from: &ProjectFile,
6187        declaration: &CodeUnit,
6188        raw_name: &str,
6189    ) -> Option<CodeUnit> {
6190        let normalized = normalize_reference_name(raw_name)?;
6191        if !normalized.contains("::")
6192            && let Some(namespace) = cpp_namespace_for(declaration)
6193        {
6194            for prefix in namespace_prefixes(&namespace) {
6195                let qualified = format!("{prefix}::{normalized}");
6196                if let Some(unit) = self
6197                    .type_candidates(visible_from, &qualified)
6198                    .into_iter()
6199                    .next()
6200                {
6201                    return Some(unit.clone());
6202                }
6203            }
6204        }
6205        self.resolve_type(visible_from, raw_name)
6206    }
6207
6208    fn resolve_unique_canonical_type_for_declaration(
6209        &self,
6210        analyzer: &CppGraphSource<'_>,
6211        visible_from: &ProjectFile,
6212        declaration: &CodeUnit,
6213        raw_name: &str,
6214    ) -> Option<CodeUnit> {
6215        let mut current =
6216            self.resolve_unique_type_for_declaration(visible_from, declaration, raw_name)?;
6217        let mut seen_aliases = HashSet::default();
6218        loop {
6219            let Some(target) = self.structured_alias_target(analyzer, &current) else {
6220                return current.is_class().then_some(current);
6221            };
6222            if matches!(target, StructuredAliasTarget::Builtin) {
6223                return current.is_class().then_some(current);
6224            }
6225            if !seen_aliases.insert(current.clone()) {
6226                return None;
6227            }
6228            current = self.resolve_structured_alias_target(visible_from, &current, &target)?;
6229        }
6230    }
6231
6232    pub fn canonical_type_unit(
6233        &self,
6234        analyzer: &CppGraphSource<'_>,
6235        visible_from: &ProjectFile,
6236        unit: &CodeUnit,
6237    ) -> Option<CodeUnit> {
6238        self.canonical_type_resolution(analyzer, visible_from, unit)
6239            .ok()
6240    }
6241
6242    /// Follow an alias only when it is visible at `reference`.
6243    ///
6244    /// The consumer need not spell the alias target. In particular, a
6245    /// conditional include can make `PublicPtr` visible at the reference while
6246    /// ordinary physical-include visibility is false. Prove the public alias
6247    /// with the reference's guard environment, then use the existing structured
6248    /// alias-chain resolver over the consumer's bounded declaration index.
6249    pub fn canonical_type_unit_in_context(
6250        &self,
6251        analyzer: &CppGraphSource<'_>,
6252        visible_from: &ProjectFile,
6253        reference: Node<'_>,
6254        unit: &CodeUnit,
6255    ) -> Option<CodeUnit> {
6256        if !self.external_type_candidate_visible_in_context(analyzer, visible_from, unit, reference)
6257        {
6258            return None;
6259        }
6260        self.canonical_type_resolution(analyzer, visible_from, unit)
6261            .ok()
6262    }
6263
6264    /// Follow `unit`'s alias chain to the class it names, or report why the
6265    /// chain does not end at one indexed class.
6266    ///
6267    /// A chain that leaves the index - an alias to a template parameter, to a
6268    /// standard-library type, or to any other declaration the workspace does
6269    /// not hold - is `Unresolvable`, not `Ambiguous` (#1828). So is a cycle:
6270    /// there is still nothing to choose between.
6271    fn canonical_type_resolution(
6272        &self,
6273        analyzer: &CppGraphSource<'_>,
6274        visible_from: &ProjectFile,
6275        unit: &CodeUnit,
6276    ) -> Result<CodeUnit, TypeCandidateFailure> {
6277        let mut current = unit.clone();
6278        let mut seen_aliases = HashSet::default();
6279        loop {
6280            let Some(target) = self.structured_alias_target(analyzer, &current) else {
6281                return current
6282                    .is_class()
6283                    .then_some(current)
6284                    .ok_or(TypeCandidateFailure::Unresolvable);
6285            };
6286            if matches!(target, StructuredAliasTarget::Builtin) {
6287                return current
6288                    .is_class()
6289                    .then_some(current)
6290                    .ok_or(TypeCandidateFailure::Unresolvable);
6291            }
6292            if !seen_aliases.insert(current.clone()) {
6293                return Err(TypeCandidateFailure::Unresolvable);
6294            }
6295            current = self.structured_alias_target_resolution(visible_from, &current, &target)?;
6296        }
6297    }
6298
6299    pub fn canonical_visible_full_type_unit(
6300        &self,
6301        analyzer: &CppGraphSource<'_>,
6302        visible_from: &ProjectFile,
6303        unit: &CodeUnit,
6304    ) -> Option<CodeUnit> {
6305        let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
6306        if cpp_class_declaration_strength(analyzer, &canonical)
6307            != CppClassDeclarationStrength::Forward
6308        {
6309            return Some(canonical);
6310        }
6311        let mut full = Vec::new();
6312        for candidate in self
6313            .visible_identifier_candidates(visible_from, canonical.identifier())
6314            .filter(|candidate| {
6315                candidate.is_class()
6316                    && candidate.fq_name() == canonical.fq_name()
6317                    && cpp_class_declaration_strength(analyzer, candidate)
6318                        == CppClassDeclarationStrength::Full
6319            })
6320        {
6321            if !full.iter().any(|existing| same_symbol(existing, candidate)) {
6322                full.push(candidate.clone());
6323            }
6324        }
6325        match full.len() {
6326            0 => Some(canonical),
6327            1 => full.pop(),
6328            _ => None,
6329        }
6330    }
6331
6332    fn resolve_structured_alias_target(
6333        &self,
6334        visible_from: &ProjectFile,
6335        declaration: &CodeUnit,
6336        target: &StructuredAliasTarget,
6337    ) -> Option<CodeUnit> {
6338        self.structured_alias_target_resolution(visible_from, declaration, target)
6339            .ok()
6340    }
6341
6342    fn structured_alias_target_resolution(
6343        &self,
6344        visible_from: &ProjectFile,
6345        declaration: &CodeUnit,
6346        target: &StructuredAliasTarget,
6347    ) -> Result<CodeUnit, TypeCandidateFailure> {
6348        let primary =
6349            self.structured_alias_primary_resolution(visible_from, declaration, target)?;
6350        let StructuredAliasTarget::Named { arguments, .. } = target else {
6351            return Err(TypeCandidateFailure::Unresolvable);
6352        };
6353        match arguments {
6354            Some(arguments) => self
6355                .resolve_template_arguments(visible_from, primary, arguments)
6356                .map_err(|error| match error {
6357                    CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
6358                        TypeCandidateFailure::Ambiguous
6359                    }
6360                    _ => TypeCandidateFailure::Unresolvable,
6361                }),
6362            None => Ok(primary),
6363        }
6364    }
6365
6366    fn resolve_structured_alias_primary(
6367        &self,
6368        visible_from: &ProjectFile,
6369        declaration: &CodeUnit,
6370        target: &StructuredAliasTarget,
6371    ) -> Option<CodeUnit> {
6372        self.structured_alias_primary_resolution(visible_from, declaration, target)
6373            .ok()
6374    }
6375
6376    fn structured_alias_primary_resolution(
6377        &self,
6378        visible_from: &ProjectFile,
6379        declaration: &CodeUnit,
6380        target: &StructuredAliasTarget,
6381    ) -> Result<CodeUnit, TypeCandidateFailure> {
6382        let StructuredAliasTarget::Named {
6383            components, global, ..
6384        } = target
6385        else {
6386            return Err(TypeCandidateFailure::Unresolvable);
6387        };
6388        let qualified = components.join("::");
6389        let candidates = if *global {
6390            // `::A::B` anchors at the root scope, so a candidate whose
6391            // canonical path merely ends with the spelled components does not
6392            // qualify. Without this filter a global `::Canonical` target also
6393            // collects `alpha::Canonical`, the lookup reports a false
6394            // ambiguity, and the alias arm silently drops out of its
6395            // conflicting family instead of proving the conflict (#2398).
6396            let mut candidates = self.type_candidates(visible_from, &qualified);
6397            candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
6398            candidates
6399        } else {
6400            self.type_candidates_for_declaration(visible_from, declaration, &qualified)
6401        };
6402        logical_type_candidate(candidates)
6403    }
6404
6405    pub fn structured_alias_primary_preserves_target(
6406        &self,
6407        analyzer: &CppGraphSource<'_>,
6408        visible_from: &ProjectFile,
6409        candidate: &CodeUnit,
6410        target: &CodeUnit,
6411    ) -> bool {
6412        let mut current = candidate.clone();
6413        let mut seen = HashSet::default();
6414        let mut matched_target = false;
6415        loop {
6416            if same_visible_symbol(&current, target)
6417                || self.compatible_primary_template_redeclarations(&current, target)
6418            {
6419                matched_target = true;
6420            }
6421            if !seen.insert(current.clone()) {
6422                return false;
6423            }
6424            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
6425                return matched_target;
6426            };
6427            if matches!(alias_target, StructuredAliasTarget::Builtin) {
6428                return matched_target;
6429            };
6430            let Some(primary) =
6431                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
6432            else {
6433                // A dependent member target such as `Detector<T>::type`
6434                // cannot be reduced to an indexed primary, but a preceding
6435                // structured alias hop may already have proven the requested
6436                // alias identity. Cycles still resolve a primary and are
6437                // rejected by `seen` above.
6438                return matched_target;
6439            };
6440            current = primary;
6441        }
6442    }
6443
6444    pub fn structured_class_alias_resolves_to_target(
6445        &self,
6446        analyzer: &CppGraphSource<'_>,
6447        visible_from: &ProjectFile,
6448        alias: &CodeUnit,
6449        target: &CodeUnit,
6450    ) -> bool {
6451        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
6452            return false;
6453        };
6454        let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
6455            return false;
6456        };
6457        let StructuredAliasTarget::Named {
6458            components, global, ..
6459        } = &alias_target
6460        else {
6461            return false;
6462        };
6463        let lexical_scope = canonical_cpp_scope_components(&owner);
6464        match self.resolve_type_components_lexically_for_target(
6465            analyzer,
6466            visible_from,
6467            components,
6468            *global,
6469            &lexical_scope,
6470            target,
6471        ) {
6472            LexicalTypeResolution::Resolved {
6473                unit, candidates, ..
6474            } => {
6475                same_visible_symbol(&unit, target)
6476                    || self.same_template_member_identity(analyzer, &unit, target)
6477                    || candidates.iter().any(|candidate| {
6478                        same_visible_symbol(candidate, target)
6479                            || self.same_template_member_identity(analyzer, candidate, target)
6480                    })
6481            }
6482            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
6483                self.structured_alias_primary_preserves_target(
6484                    analyzer,
6485                    visible_from,
6486                    alias,
6487                    target,
6488                ) || self.flattened_macro_namespace_alias_target_matches(
6489                    analyzer,
6490                    visible_from,
6491                    alias,
6492                    &alias_target,
6493                    target,
6494                )
6495            }
6496        }
6497    }
6498
6499    /// Return true when a class-owned alias names the requested type as one
6500    /// structured qualifier in its target path.
6501    ///
6502    /// A dependent target such as `Primary<T>::Type` cannot resolve to one
6503    /// indexed class. Forward lookup can still retain `Primary` as its bounded
6504    /// canonical identity. Inverse lookup needs the same evidence when later
6505    /// references use only the alias spelling.
6506    pub fn structured_class_alias_path_preserves_target(
6507        &self,
6508        analyzer: &CppGraphSource<'_>,
6509        visible_from: &ProjectFile,
6510        alias: &CodeUnit,
6511        target: &CodeUnit,
6512    ) -> bool {
6513        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
6514            return false;
6515        };
6516        let Some(StructuredAliasTarget::Named {
6517            components, global, ..
6518        }) = self.structured_alias_target(analyzer, alias)
6519        else {
6520            return false;
6521        };
6522        let lexical_scope = canonical_cpp_scope_components(&owner);
6523        (1..components.len()).rev().any(|component_count| {
6524            matches!(
6525                self.resolve_type_components_lexically_for_target(
6526                    analyzer,
6527                    visible_from,
6528                    &components[..component_count],
6529                    global,
6530                    &lexical_scope,
6531                    target,
6532                ),
6533                LexicalTypeResolution::Resolved {
6534                    ref unit,
6535                    ref candidates,
6536                    ..
6537                } if same_visible_symbol(unit, target)
6538                    || self.same_template_member_identity(analyzer, unit, target)
6539                    || candidates.iter().any(|candidate| {
6540                        same_visible_symbol(candidate, target)
6541                            || self.same_template_member_identity(analyzer, candidate, target)
6542                    })
6543            )
6544        })
6545    }
6546
6547    fn flattened_macro_namespace_alias_target_matches(
6548        &self,
6549        analyzer: &CppGraphSource<'_>,
6550        visible_from: &ProjectFile,
6551        alias: &CodeUnit,
6552        alias_target: &StructuredAliasTarget,
6553        target: &CodeUnit,
6554    ) -> bool {
6555        let StructuredAliasTarget::Named {
6556            components,
6557            global: false,
6558            arguments: None,
6559        } = alias_target
6560        else {
6561            return false;
6562        };
6563        let Some((target_name, namespace_components)) = components.split_last() else {
6564            return false;
6565        };
6566        if namespace_components.is_empty()
6567            || target_name != target.identifier()
6568            || alias.source() != target.source()
6569            || alias.source() != visible_from
6570            || !target.is_class()
6571            || declared_type_alias(analyzer, target)
6572        {
6573            return false;
6574        }
6575        if self
6576            .resolve_structured_alias_target(visible_from, alias, alias_target)
6577            .is_some()
6578        {
6579            return false;
6580        }
6581
6582        let alias_ranges = analyzer.ranges(alias);
6583        let target_ranges = analyzer.ranges(target);
6584        if alias_ranges.is_empty() || target_ranges.is_empty() {
6585            return false;
6586        }
6587        let alias_start = alias_ranges
6588            .iter()
6589            .map(|range| range.start_byte)
6590            .min()
6591            .expect("non-empty alias ranges have a minimum");
6592        let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
6593            return false;
6594        };
6595        let root = prepared.tree().root_node();
6596        let has_matching_declaration = target_ranges
6597            .iter()
6598            .filter(|range| range.end_byte <= alias_start)
6599            .filter_map(|range| node_for_exact_range(root, range))
6600            .any(|node| {
6601                flattened_macro_namespace_components(node, prepared.source())
6602                    .is_some_and(|recovered| recovered == namespace_components)
6603            });
6604        if !has_matching_declaration {
6605            return false;
6606        }
6607
6608        let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
6609        let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
6610        guard_requirement_sets_match(&alias_guards, &target_guards)
6611    }
6612
6613    pub fn template_alias_arguments_preserve_target(
6614        &self,
6615        analyzer: &CppGraphSource<'_>,
6616        visible_from: &ProjectFile,
6617        alias: &CodeUnit,
6618        arguments: &[CppTemplateExpression],
6619        target: &CodeUnit,
6620    ) -> bool {
6621        let Some(metadata) = self.cpp_template_metadata.get(alias) else {
6622            return false;
6623        };
6624        if metadata.alias_target.is_none()
6625            || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
6626        {
6627            return false;
6628        }
6629        self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
6630    }
6631
6632    pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
6633        self.cpp_template_metadata
6634            .get(unit)
6635            .is_some_and(CppTemplateMetadata::is_primary)
6636    }
6637
6638    pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
6639        self.cpp_template_metadata
6640            .get(unit)
6641            .is_some_and(CppTemplateMetadata::is_specialization)
6642    }
6643
6644    pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
6645        same_visible_symbol(left, right)
6646            || self.compatible_primary_template_redeclarations(left, right)
6647    }
6648
6649    pub fn same_template_member_identity(
6650        &self,
6651        analyzer: &CppGraphSource<'_>,
6652        left: &CodeUnit,
6653        right: &CodeUnit,
6654    ) -> bool {
6655        if same_visible_symbol(left, right) {
6656            return true;
6657        }
6658        if left.kind() != right.kind()
6659            || left.identifier() != right.identifier()
6660            || left.signature() != right.signature()
6661        {
6662            return false;
6663        }
6664        let (Some(left_owner), Some(right_owner)) =
6665            (analyzer.parent_of(left), analyzer.parent_of(right))
6666        else {
6667            return false;
6668        };
6669        left_owner.is_class()
6670            && right_owner.is_class()
6671            && self.same_template_owner_identity(&left_owner, &right_owner)
6672    }
6673
6674    fn unique_canonical_type_candidate(
6675        &self,
6676        analyzer: &CppGraphSource<'_>,
6677        visible_from: &ProjectFile,
6678        candidates: &[&CodeUnit],
6679    ) -> Option<CodeUnit> {
6680        self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
6681            .ok()
6682    }
6683
6684    fn canonical_type_candidate_resolution(
6685        &self,
6686        analyzer: &CppGraphSource<'_>,
6687        visible_from: &ProjectFile,
6688        candidates: &[&CodeUnit],
6689    ) -> Result<CodeUnit, TypeCandidateFailure> {
6690        let mut canonical = Vec::new();
6691        for candidate in candidates {
6692            let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
6693            if canonical
6694                .iter()
6695                .any(|existing| same_visible_symbol(existing, &resolved))
6696            {
6697                continue;
6698            }
6699            if let Some(existing) = canonical.iter_mut().find(|existing| {
6700                self.compatible_primary_template_redeclarations(existing, &resolved)
6701            }) {
6702                // A forward declaration and its full primary-template
6703                // definition are one C++ type even when they live in
6704                // different headers and alpha-rename their parameters. The
6705                // target-preserving path already reconciles this family; do
6706                // the same for ordinary canonical lookup so an out-of-line
6707                // member's lexical owner is not made ambiguous by its own
6708                // forward declaration. Retain the strongest physical
6709                // declaration for later owner/range queries.
6710                if matches!(
6711                    (
6712                        cpp_class_declaration_strength(analyzer, existing),
6713                        cpp_class_declaration_strength(analyzer, &resolved),
6714                    ),
6715                    (
6716                        CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
6717                        CppClassDeclarationStrength::Full,
6718                    ) | (
6719                        CppClassDeclarationStrength::Unknown,
6720                        CppClassDeclarationStrength::Forward,
6721                    )
6722                ) {
6723                    *existing = resolved;
6724                }
6725                continue;
6726            }
6727            canonical.push(resolved);
6728            if canonical.len() > 1 {
6729                return Err(TypeCandidateFailure::Ambiguous);
6730            }
6731        }
6732        canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
6733    }
6734
6735    pub fn unique_type_candidate_preserving_target(
6736        &self,
6737        analyzer: &CppGraphSource<'_>,
6738        visible_from: &ProjectFile,
6739        candidates: &[&CodeUnit],
6740        target: &CodeUnit,
6741    ) -> Option<CodeUnit> {
6742        // C++ headers often expose one logical type through mutually exclusive
6743        // physical declarations, for example a class in the fallback branch
6744        // and a `using` alias to the standard-library type in the configured
6745        // branch. The index intentionally retains both declarations so forward
6746        // lookup can report each target. Preserve the requested target when
6747        // that is the only ambiguity: every candidate has the same type kind,
6748        // exact canonical FQN, and source file, and the requested declaration
6749        // itself is one of the physical candidates. Do not merge same-named
6750        // declarations from different files or namespaces; those remain
6751        // ambiguous and fail closed below.
6752        if self.c_tag_declaration_family_matches_target(analyzer, visible_from, candidates, target)
6753            || self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
6754        {
6755            return Some(target.clone());
6756        }
6757        let mut resolved_candidates = Vec::new();
6758        for candidate in candidates {
6759            // An ifdef branch that aliases an unindexed system type (for
6760            // example `typedef pthread_mutex_t k5_os_mutex`) cannot be
6761            // canonicalized. That branch does not name `target`. Dropping it
6762            // keeps the branch that does. Failing the whole family here would
6763            // deny every usage of the reachable spelling (#2368).
6764            let Some(resolved) =
6765                self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
6766            else {
6767                continue;
6768            };
6769            if resolved_candidates
6770                .iter()
6771                .any(|existing| same_visible_symbol(existing, &resolved))
6772            {
6773                continue;
6774            }
6775            resolved_candidates.push(resolved);
6776        }
6777        match resolved_candidates.as_slice() {
6778            [] => None,
6779            [single] => Some(single.clone()),
6780            // The branches disagree about what the name aliases. When they are
6781            // spellings of one entity (#1845) that disagreement is a build
6782            // configuration, not a choice between types, so it must not deny
6783            // the requested target its reference.
6784            _ => self
6785                .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
6786                .map(|_| target.clone()),
6787        }
6788    }
6789
6790    /// The declaration a same-file same-FQN family stands for when a reference
6791    /// names `target`, or `None` when the candidates are not one family or the
6792    /// family does not name `target`.
6793    ///
6794    /// A translation unit cannot hold two different types under one qualified
6795    /// name, so several same-kind declarations of one FQN in one file are
6796    /// alternate spellings of one entity - the configuration branches of an
6797    /// `#if` family, for example log4cxx's `logchar`, which aliases `char` in
6798    /// the UTF-8 branch and `UniChar` in the unichar branch. Their alias
6799    /// targets differ; canonicalizing each branch on its own and then demanding
6800    /// agreement reports an ambiguity that denies every declaration in the
6801    /// family its usages (#1845). The family names `target` when it declares
6802    /// it, or when one branch's alias chain reaches it.
6803    ///
6804    /// Declarations in different files or namespaces are distinct entities and
6805    /// are deliberately excluded: their disagreement is a real ambiguity.
6806    pub fn same_fqn_type_spelling_for_target<'b>(
6807        &self,
6808        analyzer: &CppGraphSource<'_>,
6809        visible_from: &ProjectFile,
6810        candidates: &[&'b CodeUnit],
6811        target: &CodeUnit,
6812    ) -> Option<&'b CodeUnit> {
6813        let [first, rest @ ..] = candidates else {
6814            return None;
6815        };
6816        if rest.is_empty()
6817            || !rest.iter().all(|candidate| {
6818                candidate.kind() == first.kind()
6819                    && candidate.fq_name() == first.fq_name()
6820                    && candidate.source() == first.source()
6821            })
6822        {
6823            return None;
6824        }
6825        candidates
6826            .iter()
6827            .copied()
6828            .find(|candidate| same_symbol(candidate, target))
6829            .or_else(|| {
6830                candidates.iter().copied().find(|candidate| {
6831                    self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
6832                        .is_some_and(|resolved| same_visible_symbol(&resolved, target))
6833                })
6834            })
6835    }
6836
6837    pub fn alternate_same_fqn_type_declarations(
6838        &self,
6839        analyzer: &CppGraphSource<'_>,
6840        candidates: &[&CodeUnit],
6841        target: &CodeUnit,
6842    ) -> bool {
6843        let Some(first) = candidates.first() else {
6844            return false;
6845        };
6846        let same_api = first.kind() == target.kind()
6847            && first.fq_name() == target.fq_name()
6848            && first.source() == target.source()
6849            && candidates.iter().all(|candidate| {
6850                candidate.kind() == target.kind()
6851                    && candidate.fq_name() == target.fq_name()
6852                    && candidate.source() == target.source()
6853            })
6854            && candidates
6855                .iter()
6856                .any(|candidate| same_symbol(candidate, target))
6857            && candidates
6858                .iter()
6859                .any(|candidate| !same_logical_symbol(candidate, target));
6860        if !same_api {
6861            return false;
6862        }
6863
6864        let requirements = candidates
6865            .iter()
6866            .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
6867            .collect::<Vec<_>>();
6868        requirements.len() > 1
6869            && requirements
6870                .iter()
6871                .all(|requirement| !requirement.is_empty())
6872            && requirements.iter().enumerate().all(|(index, left)| {
6873                requirements[index + 1..].iter().all(|right| {
6874                    left.iter().all(|(_, left_guards)| {
6875                        right.iter().all(|(_, right_guards)| {
6876                            merge_preprocessor_guards(left_guards, right_guards).is_none()
6877                        })
6878                    })
6879                })
6880            })
6881    }
6882
6883    fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
6884        let mut pending = vec![terms.to_vec()];
6885        while let Some(branch_terms) = pending.pop() {
6886            let mut normalized = Vec::new();
6887            let mut covers_branch = false;
6888            for term in branch_terms {
6889                if term.iter().any(|guard| term.contains(&guard.negated())) {
6890                    continue;
6891                }
6892                if term.is_empty() {
6893                    covers_branch = true;
6894                    break;
6895                }
6896                if !normalized.iter().any(|existing| existing == &term) {
6897                    normalized.push(term);
6898                }
6899            }
6900            if covers_branch {
6901                continue;
6902            }
6903            let Some(split_guard) = normalized
6904                .iter()
6905                .flat_map(|term| term.iter())
6906                .next()
6907                .cloned()
6908            else {
6909                return false;
6910            };
6911            let negated_guard = split_guard.negated();
6912            let mut when_defined = Vec::new();
6913            let mut when_undefined = Vec::new();
6914            for term in normalized {
6915                if term.contains(&negated_guard) {
6916                    // This term cannot hold when `split_guard` is true.
6917                } else if term.contains(&split_guard) {
6918                    let mut reduced = term.clone();
6919                    reduced.remove(&split_guard);
6920                    when_defined.push(reduced);
6921                } else {
6922                    when_defined.push(term.clone());
6923                }
6924                if term.contains(&split_guard) {
6925                    // This term cannot hold when `split_guard` is false.
6926                } else if term.contains(&negated_guard) {
6927                    let mut reduced = term;
6928                    reduced.remove(&negated_guard);
6929                    when_undefined.push(reduced);
6930                } else {
6931                    when_undefined.push(term);
6932                }
6933            }
6934            pending.push(when_defined);
6935            pending.push(when_undefined);
6936        }
6937        true
6938    }
6939
6940    /// The byte range of the one `#if` family with a terminal `#else` that holds
6941    /// every physical declaration of every candidate, or `None` when they do not
6942    /// share one such family.
6943    ///
6944    /// Guard terms alone cannot distinguish one `#if` family from separate blocks
6945    /// whose macros changed between declarations. Require every physical range to
6946    /// belong to one syntax-tree family with a terminal `#else` before the terms
6947    /// can prove branch coverage.
6948    fn declarations_share_exhaustive_conditional_family(
6949        &self,
6950        analyzer: &CppGraphSource<'_>,
6951        candidates: &[&CodeUnit],
6952    ) -> Option<(usize, usize)> {
6953        let mut family_range = None;
6954        for candidate in candidates {
6955            let prepared = self.cpp.prepared_syntax(self.token, candidate.source())?;
6956            let root = prepared.tree().root_node();
6957            let mut candidate_family = None;
6958            for range in analyzer.ranges(candidate) {
6959                let node = root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
6960                let family = preprocessor_conditional_family_for_declaration(node)?;
6961                let key = (family.start_byte(), family.end_byte());
6962                if candidate_family.is_some_and(|existing| existing != key) {
6963                    return None;
6964                }
6965                candidate_family = Some(key);
6966            }
6967            let candidate_family = candidate_family?;
6968            if family_range.is_some_and(|existing| existing != candidate_family) {
6969                return None;
6970            }
6971            family_range = Some(candidate_family);
6972        }
6973        family_range
6974    }
6975
6976    pub fn complementary_same_fqn_type_declarations(
6977        &self,
6978        analyzer: &CppGraphSource<'_>,
6979        candidates: &[&CodeUnit],
6980        target: &CodeUnit,
6981    ) -> bool {
6982        if candidates.len() < 2
6983            || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
6984            || self
6985                .declarations_share_exhaustive_conditional_family(analyzer, candidates)
6986                .is_none()
6987        {
6988            return false;
6989        }
6990        Self::preprocessor_guard_terms_cover_all_paths(
6991            &self.declaration_family_guard_terms(analyzer, candidates),
6992        )
6993    }
6994
6995    fn declaration_family_guard_terms(
6996        &self,
6997        analyzer: &CppGraphSource<'_>,
6998        candidates: &[&CodeUnit],
6999    ) -> Vec<HashSet<PreprocessorGuard>> {
7000        candidates
7001            .iter()
7002            .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7003            .map(|(_, guards)| guards)
7004            .collect()
7005    }
7006
7007    /// A callable name declared on every branch of one completed `#if`/`#else`
7008    /// family is declared on every configuration path, so a reference below the
7009    /// whole family sees one of the branches whatever the preprocessor decides.
7010    /// Answer the family's end byte: only past `#endif` is every branch's
7011    /// declaration behind the reference.
7012    ///
7013    /// This is the callable analogue of `complementary_same_fqn_type_declarations`
7014    /// and shares both of its primitives. It does not require two distinct
7015    /// `CodeUnit`s: branches that declare the same signature can collapse into
7016    /// one unit carrying one physical range per branch.
7017    ///
7018    /// The branches are alternate spellings of one declaration, never competing
7019    /// declarations, so only the first branch stands for the family. Reporting
7020    /// every branch as visible would turn a name the source declares exactly
7021    /// once into an ambiguity between build configurations.
7022    fn exhaustive_guard_family_activation(
7023        &self,
7024        analyzer: &CppGraphSource<'_>,
7025        prepared: &PreparedSyntaxTree,
7026        candidate: &CodeUnit,
7027        reference: &CallableReferenceContext<'_>,
7028    ) -> Option<usize> {
7029        // Branch coverage says nothing about scope: a block-local declaration
7030        // stays invisible however many branches declare it.
7031        if nameable_callable_declaration_nodes(analyzer, prepared, candidate).is_empty() {
7032            return None;
7033        }
7034        let family = self
7035            .visible_identifier_candidates(candidate.source(), candidate.identifier())
7036            .filter(|peer| {
7037                peer.kind() == candidate.kind()
7038                    && peer.fq_name() == candidate.fq_name()
7039                    && peer.source() == candidate.source()
7040            })
7041            .collect::<Vec<_>>();
7042        let (_, family_end) =
7043            self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
7044        if !Self::preprocessor_guard_terms_cover_all_paths(
7045            &self.declaration_family_guard_terms(analyzer, &family),
7046        ) {
7047            return None;
7048        }
7049        // A reference whose own guards pick one branch already reaches that
7050        // branch through the ordinary same-guard path; the family must not
7051        // resurrect the branch the reference contradicts.
7052        if !declaration_guard_requirements(analyzer, self.cpp, candidate)
7053            .iter()
7054            .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
7055        {
7056            return None;
7057        }
7058        (first_declaration_byte(analyzer, candidate)?
7059            == family
7060                .iter()
7061                .filter_map(|peer| first_declaration_byte(analyzer, peer))
7062                .min()?)
7063        .then_some(family_end)
7064    }
7065
7066    fn type_candidate_preserving_target(
7067        &self,
7068        analyzer: &CppGraphSource<'_>,
7069        visible_from: &ProjectFile,
7070        candidate: &CodeUnit,
7071        target: &CodeUnit,
7072    ) -> Option<CodeUnit> {
7073        let mut current = candidate.clone();
7074        let mut matched_target = same_visible_symbol(&current, target)
7075            || self.compatible_primary_template_redeclarations(&current, target);
7076        let mut seen = HashSet::default();
7077        loop {
7078            if !seen.insert(current.clone()) {
7079                return None;
7080            }
7081            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
7082                return matched_target
7083                    .then(|| target.clone())
7084                    .or_else(|| current.is_class().then_some(current));
7085            };
7086            if self.flattened_macro_namespace_alias_target_matches(
7087                analyzer,
7088                visible_from,
7089                &current,
7090                &alias_target,
7091                target,
7092            ) {
7093                return Some(target.clone());
7094            }
7095            if matches!(alias_target, StructuredAliasTarget::Builtin) {
7096                return matched_target
7097                    .then(|| target.clone())
7098                    .or_else(|| current.is_class().then_some(current));
7099            }
7100            // A non-template alias can name a template alias with explicit
7101            // arguments (for example, `using Result = Expected<int>`).  When
7102            // the requested target is that alias's primary declaration, keep
7103            // the primary identity before expanding the RHS arguments.  The
7104            // expansion would otherwise canonicalize through the underlying
7105            // implementation type and lose the target spelling used by the
7106            // forward resolver.
7107            if !self.cpp_template_metadata.contains_key(&current)
7108                && let Some(primary) =
7109                    self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
7110                && (same_visible_symbol(&primary, target)
7111                    || self.compatible_primary_template_redeclarations(&primary, target))
7112            {
7113                return Some(target.clone());
7114            }
7115            if same_visible_symbol(&current, target) {
7116                return Some(target.clone());
7117            }
7118            if self.cpp_template_metadata.contains_key(&current) {
7119                return None;
7120            }
7121            let Some(next) =
7122                self.resolve_structured_alias_target(visible_from, &current, &alias_target)
7123            else {
7124                return matched_target.then(|| target.clone());
7125            };
7126            current = next;
7127            matched_target |= same_visible_symbol(&current, target)
7128                || self.compatible_primary_template_redeclarations(&current, target);
7129        }
7130    }
7131
7132    fn compatible_primary_template_redeclarations(
7133        &self,
7134        left: &CodeUnit,
7135        right: &CodeUnit,
7136    ) -> bool {
7137        let (Some(left_metadata), Some(right_metadata)) = (
7138            self.cpp_template_metadata.get(left),
7139            self.cpp_template_metadata.get(right),
7140        ) else {
7141            return false;
7142        };
7143        left_metadata.primary_fq_name == right_metadata.primary_fq_name
7144            && left_metadata.is_primary()
7145            && right_metadata.is_primary()
7146            && cpp_reconcile_primary_template_parameters(
7147                &[(left, left_metadata), (right, right_metadata)],
7148                right,
7149            )
7150            .is_some()
7151    }
7152
7153    fn alias_candidate_may_preserve_target(
7154        &self,
7155        analyzer: &CppGraphSource<'_>,
7156        visible_from: &ProjectFile,
7157        candidate: &CodeUnit,
7158        target: &CodeUnit,
7159    ) -> bool {
7160        let mut current = candidate.clone();
7161        let mut seen = HashSet::default();
7162        loop {
7163            if same_visible_symbol(&current, target)
7164                || self.compatible_primary_template_redeclarations(&current, target)
7165            {
7166                return true;
7167            }
7168            if self.cpp_template_metadata.contains_key(&current) {
7169                return true;
7170            }
7171            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
7172                return false;
7173            };
7174            let StructuredAliasTarget::Named {
7175                components,
7176                global,
7177                arguments,
7178            } = alias_target
7179            else {
7180                return false;
7181            };
7182            if arguments.is_some() || !seen.insert(current.clone()) {
7183                return true;
7184            }
7185            let qualified = components.join("::");
7186            let next = if global {
7187                unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
7188            } else {
7189                self.resolve_unique_type_for_declaration(visible_from, &current, &qualified)
7190            };
7191            let Some(next) = next else {
7192                return true;
7193            };
7194            current = next;
7195        }
7196    }
7197
7198    /// Every indexed type declaration `raw_name` names when it is written in
7199    /// `declaration`'s namespace: the innermost enclosing namespace that holds
7200    /// the name wins, otherwise the name is looked up unqualified.
7201    fn type_candidates_for_declaration<'b>(
7202        &'b self,
7203        visible_from: &ProjectFile,
7204        declaration: &CodeUnit,
7205        raw_name: &str,
7206    ) -> Vec<&'b CodeUnit> {
7207        let Some(normalized) = normalize_reference_name(raw_name) else {
7208            return Vec::new();
7209        };
7210        if let Some(namespace) = cpp_namespace_for(declaration) {
7211            for prefix in namespace_prefixes(&namespace) {
7212                let qualified = format!("{prefix}::{normalized}");
7213                let candidates = self.type_candidates(visible_from, &qualified);
7214                if !candidates.is_empty() {
7215                    return candidates;
7216                }
7217            }
7218        }
7219        self.type_candidates(visible_from, &normalized)
7220    }
7221
7222    fn resolve_unique_type_for_declaration(
7223        &self,
7224        visible_from: &ProjectFile,
7225        declaration: &CodeUnit,
7226        raw_name: &str,
7227    ) -> Option<CodeUnit> {
7228        unique_logical_type_candidate(self.type_candidates_for_declaration(
7229            visible_from,
7230            declaration,
7231            raw_name,
7232        ))
7233    }
7234
7235    pub fn resolves_to_type(
7236        &self,
7237        analyzer: &CppGraphSource<'_>,
7238        file: &ProjectFile,
7239        raw_name: &str,
7240        target: &CodeUnit,
7241    ) -> bool {
7242        let Some(normalized) = normalize_reference_name(raw_name) else {
7243            return false;
7244        };
7245        let candidates = self.type_candidates(file, &normalized);
7246        if candidates.is_empty() {
7247            return self.parser_alias_resolves_to_type(analyzer, file, raw_name, target);
7248        }
7249        let Some(resolved) =
7250            self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
7251        else {
7252            return false;
7253        };
7254        same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
7255    }
7256
7257    pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
7258        let raw_target = cpp_alias_declaration_target_text(alias.signature()?)?;
7259        let resolved = self.resolve_type_for_declaration(alias.source(), alias, &raw_target)?;
7260        match resolved.kind() {
7261            CodeUnitType::Class => Some(resolved),
7262            _ if is_type_alias(&resolved) => self.alias_target(&resolved),
7263            _ => None,
7264        }
7265    }
7266
7267    /// Whether two callable declarations declare one function.
7268    ///
7269    /// [`same_logical_symbol`] compares the persisted signature strings, which
7270    /// embed each parameter type exactly as it was spelled. A header
7271    /// declaration written inside `namespace zmq { class dist_t { ... } }` says
7272    /// `send_to_matching(msg_t *)` while its out-of-line body at file scope
7273    /// says `zmq::msg_t *`, so the string comparison reports two symbols where
7274    /// C++ ([basic.def], [dcl.fct]) sees one declaration and one definition.
7275    /// This resolves the written parameter names before comparing them and
7276    /// reports the same answer the language does for the cases it can prove.
7277    ///
7278    /// Everything it cannot prove stays two symbols: a template declaration, a
7279    /// parameter with no comparable shape, a name that resolves on one side
7280    /// only, and an alias chain it cannot follow safely (#2010).
7281    pub fn same_logical_callable(
7282        &self,
7283        analyzer: &CppGraphSource<'_>,
7284        left: &CodeUnit,
7285        right: &CodeUnit,
7286    ) -> bool {
7287        if same_logical_symbol(left, right) {
7288            return true;
7289        }
7290        if left.kind() != right.kind()
7291            || !left.is_callable()
7292            || !right.is_callable()
7293            || left.fq_name() != right.fq_name()
7294        {
7295            return false;
7296        }
7297        // A template declaration and its out-of-line body can also diverge
7298        // outside the parameter list - `template <class T>` against
7299        // `template <typename T>` - and the template head is part of the
7300        // persisted signature. Deciding template-head equivalence is a
7301        // separate question, so templates keep string identity.
7302        if self.callable_is_template_declaration(analyzer, left)
7303            || self.callable_is_template_declaration(analyzer, right)
7304        {
7305            return false;
7306        }
7307        let (Some(left_comparable), Some(right_comparable)) = (
7308            self.callable_comparable(analyzer, left),
7309            self.callable_comparable(analyzer, right),
7310        ) else {
7311            return false;
7312        };
7313        // The trailing member `const`, ref-qualifier, `noexcept`, trailing
7314        // return type and requires-clause are part of C++ callable identity and
7315        // an out-of-line definition repeats them verbatim, so they must agree
7316        // as written.
7317        if left_comparable.suffix != right_comparable.suffix
7318            || left_comparable.shapes.len() != right_comparable.shapes.len()
7319        {
7320            return false;
7321        }
7322        left_comparable
7323            .shapes
7324            .iter()
7325            .zip(right_comparable.shapes.iter())
7326            .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
7327                (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
7328                (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
7329                    self.comparable_shapes_agree(analyzer, left_shape, right_shape)
7330                }
7331                // An unstructured parameter records that the reduction failed,
7332                // not that the two spellings mean the same type, so it agrees
7333                // with nothing - including another unstructured parameter.
7334                _ => false,
7335            })
7336    }
7337
7338    /// Compare two parameter shapes node by node with an explicit paired stack.
7339    ///
7340    /// Shape variants and cv-qualifiers must agree exactly at every level; only
7341    /// the named leaves may be spelled differently, and they agree when they
7342    /// resolve to one type declaration.
7343    fn comparable_shapes_agree(
7344        &self,
7345        analyzer: &CppGraphSource<'_>,
7346        left: &CppComparableParameter,
7347        right: &CppComparableParameter,
7348    ) -> bool {
7349        let mut stack = vec![(left.root(), right.root())];
7350        while let Some((left_index, right_index)) = stack.pop() {
7351            match (left.node(left_index), right.node(right_index)) {
7352                (
7353                    CppComparableNode::Named {
7354                        name: left_name,
7355                        primitive: left_primitive,
7356                        konst: left_konst,
7357                        volatil: left_volatil,
7358                    },
7359                    CppComparableNode::Named {
7360                        name: right_name,
7361                        primitive: right_primitive,
7362                        konst: right_konst,
7363                        volatil: right_volatil,
7364                    },
7365                ) => {
7366                    if left_konst != right_konst
7367                        || left_volatil != right_volatil
7368                        || left_primitive != right_primitive
7369                        || !self.comparable_names_agree(
7370                            analyzer,
7371                            left_name,
7372                            right_name,
7373                            *left_primitive,
7374                        )
7375                    {
7376                        return false;
7377                    }
7378                }
7379                (
7380                    CppComparableNode::Pointer {
7381                        inner: left_inner,
7382                        konst: left_konst,
7383                        volatil: left_volatil,
7384                    },
7385                    CppComparableNode::Pointer {
7386                        inner: right_inner,
7387                        konst: right_konst,
7388                        volatil: right_volatil,
7389                    },
7390                ) => {
7391                    if left_konst != right_konst || left_volatil != right_volatil {
7392                        return false;
7393                    }
7394                    stack.push((*left_inner, *right_inner));
7395                }
7396                (
7397                    CppComparableNode::Reference { inner: left_inner },
7398                    CppComparableNode::Reference { inner: right_inner },
7399                )
7400                | (
7401                    CppComparableNode::Array { inner: left_inner },
7402                    CppComparableNode::Array { inner: right_inner },
7403                ) => stack.push((*left_inner, *right_inner)),
7404                (
7405                    CppComparableNode::Generic {
7406                        base: left_base,
7407                        arguments: left_arguments,
7408                    },
7409                    CppComparableNode::Generic {
7410                        base: right_base,
7411                        arguments: right_arguments,
7412                    },
7413                ) => {
7414                    if left_arguments.len() != right_arguments.len() {
7415                        return false;
7416                    }
7417                    stack.push((*left_base, *right_base));
7418                    stack.extend(
7419                        left_arguments.iter().zip(right_arguments.iter()).map(
7420                            |(left_argument, right_argument)| (*left_argument, *right_argument),
7421                        ),
7422                    );
7423                }
7424                _ => return false,
7425            }
7426        }
7427        true
7428    }
7429
7430    /// Whether two written type names denote one type.
7431    ///
7432    /// A primitive denotes the same type in every scope, so its recorded
7433    /// lexical scope is noise and its spelling decides. A nominal name is
7434    /// resolved on each side independently: two resolved names agree when they
7435    /// reach one type declaration, and two unresolved names agree only on
7436    /// exact agreement of what was written, which is no weaker than the
7437    /// whole-signature string equality this comparison replaces. Resolution on
7438    /// one side only is evidence of difference, never of agreement.
7439    fn comparable_names_agree(
7440        &self,
7441        analyzer: &CppGraphSource<'_>,
7442        left: &StructuredTypeName,
7443        right: &StructuredTypeName,
7444        primitive: bool,
7445    ) -> bool {
7446        if primitive {
7447            return left.path() == right.path();
7448        }
7449        match (
7450            self.comparable_name_terminal(analyzer, left),
7451            self.comparable_name_terminal(analyzer, right),
7452        ) {
7453            (Some(left_terminal), Some(right_terminal)) => {
7454                same_logical_symbol(&left_terminal, &right_terminal)
7455            }
7456            (None, None) => {
7457                left.path() == right.path() && left.is_absolute() == right.is_absolute()
7458            }
7459            _ => false,
7460        }
7461    }
7462
7463    /// The class declaration a written type name denotes, or `None` when the
7464    /// workspace cannot prove one.
7465    ///
7466    /// The lookup is a closure-independent lexical-scope prefix walk over the
7467    /// workspace definition index rather than a visibility lookup: the index
7468    /// handed to a definition query is rooted at the reference file, and a
7469    /// body's `.cpp` is almost never in that file's include closure. Any name
7470    /// this walk resolves is one an enclosing-scope lookup could resolve, so it
7471    /// cannot invent a type the compiler could not see; `using`-directives are
7472    /// not modelled, and a name that needs one stays unresolved.
7473    fn comparable_name_terminal(
7474        &self,
7475        analyzer: &CppGraphSource<'_>,
7476        name: &StructuredTypeName,
7477    ) -> Option<CodeUnit> {
7478        let mut current = self.comparable_name_declaration(analyzer, name)?;
7479        let mut visited = HashSet::default();
7480        for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
7481            // The alias question is asked before the class question, and
7482            // through `declared_type_alias` rather than `is_type_alias`,
7483            // because extraction records `using A8 = A7;` as a *Class* unit
7484            // whose signature is the alias declaration. Reading the kind first
7485            // would end the chase on the alias itself and report an alias
7486            // spelling and its underlying class as two types (#2010).
7487            if !declared_type_alias(analyzer, &current) {
7488                return current.is_class().then_some(current);
7489            }
7490            if !visited.insert(current.clone()) {
7491                return None;
7492            }
7493            let signature = current.signature()?;
7494            // `cpp_alias_declaration_target_text` reads the declaration's
7495            // `type` field only, so `typedef Foo *Bar` reports `Foo` and the
7496            // pointer is silently dropped. Substituting such an alias would
7497            // fuse `f(Bar)` and `f(Foo)`, which are two functions.
7498            if cpp_alias_declaration_adds_indirection(signature) {
7499                return None;
7500            }
7501            let raw_target = cpp_alias_declaration_target_text(signature)?;
7502            current = self.comparable_alias_target(analyzer, &current, &raw_target)?;
7503        }
7504        None
7505    }
7506
7507    /// The declaration one alias hop lands on: the type `raw_target` names,
7508    /// looked up from the alias declaration's own enclosing namespace.
7509    ///
7510    /// The hop takes the same closure-independent prefix walk the first lookup
7511    /// took, and deliberately not `resolve_type_for_declaration`: that one
7512    /// answers out of the `VisibilityIndex`, which is rooted at the reference
7513    /// file, while the alias declaration this hop starts from is reached
7514    /// through the workspace definition index and its file need not be in that
7515    /// root's include closure - where the visibility lookup answers nothing and
7516    /// the chase would stop on the alias itself (#2010).
7517    fn comparable_alias_target(
7518        &self,
7519        analyzer: &CppGraphSource<'_>,
7520        alias: &CodeUnit,
7521        raw_target: &str,
7522    ) -> Option<CodeUnit> {
7523        // `raw_target` is the alias declaration's written type text, so it is a
7524        // plain `::`-joined qualified-id: the same domain the shared symbol-path
7525        // parser reads, and the same leading `::` that marks an absolute name
7526        // everywhere else this crate normalizes a reference.
7527        let absolute = raw_target.trim_start().starts_with("::");
7528        let normalized = normalize_reference_name(raw_target)?;
7529        let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
7530            brokk_bifrost_core::analyzer::Language::Cpp,
7531            &normalized,
7532        );
7533        let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
7534            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
7535                brokk_bifrost_core::analyzer::Language::Cpp,
7536                &namespace,
7537            )
7538        });
7539        let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
7540        self.comparable_name_declaration(analyzer, &name)
7541    }
7542
7543    /// The one type declaration `name` names, by enclosing scope, innermost
7544    /// first.
7545    ///
7546    /// The first prefix depth that names anything decides: an inner scope hides
7547    /// an outer one, so a match there is the answer even when an outer scope
7548    /// also declares the name. Several logically distinct declarations at that
7549    /// depth are an ambiguity this comparison must not guess at.
7550    fn comparable_name_declaration(
7551        &self,
7552        analyzer: &CppGraphSource<'_>,
7553        name: &StructuredTypeName,
7554    ) -> Option<CodeUnit> {
7555        let definitions = analyzer.workspace_definitions();
7556        let interner = segment_interner();
7557        let first_depth = if name.is_absolute() {
7558            0
7559        } else {
7560            name.lexical_scope().len()
7561        };
7562        for depth in (0..=first_depth).rev() {
7563            let mut structured = FqName::new();
7564            for component in name.lexical_scope()[..depth].iter().chain(name.path()) {
7565                structured.push(interner.intern(component, SegmentKind::Unknown));
7566            }
7567            let mut candidates = definitions
7568                .identifier(&structured)
7569                .into_iter()
7570                .filter(|unit| unit.fq().same_segment_texts(&structured))
7571                .filter(|unit| {
7572                    unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
7573                });
7574            let Some(first) = candidates.next() else {
7575                continue;
7576            };
7577            return candidates
7578                .all(|unit| same_logical_symbol(&unit, &first))
7579                .then_some(first);
7580        }
7581        None
7582    }
7583
7584    /// The comparison inputs of one callable declaration, extracted once.
7585    ///
7586    /// The comparison itself runs only when two candidates share kind and fully
7587    /// qualified name but not signature, which is rare; re-reading the same
7588    /// declaration for every pair in a candidate set is not.
7589    fn callable_comparable(
7590        &self,
7591        analyzer: &CppGraphSource<'_>,
7592        unit: &CodeUnit,
7593    ) -> Option<Arc<ExtractedComparable>> {
7594        if let Some(cached) = self
7595            .callable_comparables
7596            .lock()
7597            .expect("C++ callable comparable cache poisoned")
7598            .get(unit)
7599            .cloned()
7600        {
7601            return cached;
7602        }
7603        let extracted = self
7604            .extract_callable_comparable(analyzer, unit)
7605            .map(Arc::new);
7606        self.callable_comparables
7607            .lock()
7608            .expect("C++ callable comparable cache poisoned")
7609            .insert(unit.clone(), extracted.clone());
7610        extracted
7611    }
7612
7613    fn extract_callable_comparable(
7614        &self,
7615        analyzer: &CppGraphSource<'_>,
7616        unit: &CodeUnit,
7617    ) -> Option<ExtractedComparable> {
7618        let prepared = self.cpp.prepared_syntax(self.token, unit.source())?;
7619        let root = prepared.tree().root_node();
7620        let declarator = analyzer
7621            .ranges(unit)
7622            .into_iter()
7623            .find_map(|range| cpp_function_declarator_at(root, range.start_byte))?;
7624        Some(ExtractedComparable {
7625            // One question about one declarator: indexing the file's tree would
7626            // cost more than the walk it saves.
7627            shapes: cpp_comparable_parameter_shapes(
7628                declarator,
7629                prepared.source(),
7630                &ParentIndex::unindexed(),
7631            ),
7632            suffix: cpp_callable_identity_suffix(declarator, prepared.source())?,
7633        })
7634    }
7635
7636    pub fn canonical_type_for_reference(
7637        &self,
7638        file: &ProjectFile,
7639        raw_name: &str,
7640    ) -> Option<CodeUnit> {
7641        let resolved = self.resolve_type(file, raw_name)?;
7642        self.alias_target(&resolved).or(Some(resolved))
7643    }
7644
7645    pub fn parser_alias_resolves_to_type(
7646        &self,
7647        analyzer: &CppGraphSource<'_>,
7648        file: &ProjectFile,
7649        raw_name: &str,
7650        target: &CodeUnit,
7651    ) -> bool {
7652        let Some(alias_name) = normalize_reference_name(raw_name) else {
7653            return false;
7654        };
7655        let Some(cpp) = analyzer.cpp else {
7656            return false;
7657        };
7658        let matches_file = |source_file: &ProjectFile| {
7659            self.file_alias_matches(cpp, source_file, &alias_name, target)
7660        };
7661        self.visible_source_files_by_root.get(file).map_or_else(
7662            || matches_file(file),
7663            |files| files.iter().any(matches_file),
7664        )
7665    }
7666
7667    fn file_alias_matches(
7668        &self,
7669        cpp: &dyn CppSource,
7670        file: &ProjectFile,
7671        alias_name: &str,
7672        target: &CodeUnit,
7673    ) -> bool {
7674        let cell = {
7675            let mut cells = self.alias_cells.lock().expect("alias cell map lock");
7676            Arc::clone(
7677                cells
7678                    .entry(file.clone())
7679                    .or_insert_with(|| Arc::new(OnceLock::new())),
7680            )
7681        };
7682        cell.get_or_init(|| {
7683            self.parser_alias_source_parses
7684                .fetch_add(1, Ordering::Relaxed);
7685            #[cfg(any(test, feature = "test-support"))]
7686            {
7687                *self
7688                    .alias_source_parse_counts
7689                    .lock()
7690                    .expect("alias source parse count lock")
7691                    .entry(file.clone())
7692                    .or_default() += 1;
7693            }
7694            aliases_from_prepared_source(cpp, self.token, file).into_boxed_slice()
7695        })
7696        .iter()
7697        .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
7698    }
7699
7700    #[cfg(any(test, feature = "test-support"))]
7701    pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
7702        self.visible_source_files_by_root
7703            .get(file)
7704            .cloned()
7705            .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
7706    }
7707
7708    #[cfg(any(test, feature = "test-support"))]
7709    pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
7710        self.alias_source_parse_counts
7711            .lock()
7712            .expect("alias source parse count lock")
7713            .get(file)
7714            .copied()
7715            .unwrap_or(0)
7716    }
7717
7718    pub fn resolve_named(
7719        &self,
7720        file: &ProjectFile,
7721        raw_name: &str,
7722        kind: TargetKind,
7723    ) -> Option<CodeUnit> {
7724        let normalized = normalize_reference_name(raw_name)?;
7725        self.named_candidates_for_normalized(file, &normalized, kind)
7726            .into_iter()
7727            .next()
7728            .cloned()
7729    }
7730
7731    pub fn contains_named_symbol(
7732        &self,
7733        file: &ProjectFile,
7734        raw_name: &str,
7735        kind: TargetKind,
7736        target: &CodeUnit,
7737    ) -> bool {
7738        let Some(normalized) = normalize_reference_name(raw_name) else {
7739            return false;
7740        };
7741        self.named_candidates_for_normalized(file, &normalized, kind)
7742            .into_iter()
7743            .any(|unit| {
7744                matches_kind_for_lookup(unit, kind)
7745                    && reference_matches_unit(&normalized, unit)
7746                    && same_visible_symbol(unit, target)
7747            })
7748    }
7749
7750    pub fn named_candidates(
7751        &self,
7752        file: &ProjectFile,
7753        raw_name: &str,
7754        kind: TargetKind,
7755    ) -> Vec<CodeUnit> {
7756        let Some(normalized) = normalize_reference_name(raw_name) else {
7757            return Vec::new();
7758        };
7759        self.named_candidates_for_normalized(file, &normalized, kind)
7760            .into_iter()
7761            .cloned()
7762            .collect()
7763    }
7764
7765    pub fn resolve_known_non_target(
7766        &self,
7767        file: &ProjectFile,
7768        raw_name: &str,
7769        kind: TargetKind,
7770        target: &CodeUnit,
7771    ) -> bool {
7772        let Some(normalized) = normalize_reference_name(raw_name) else {
7773            return false;
7774        };
7775        normalized.contains("::")
7776            && self
7777                .named_candidates_for_normalized(file, &normalized, kind)
7778                .into_iter()
7779                .any(|unit| {
7780                    matches_kind_for_lookup(unit, kind)
7781                        && reference_matches_unit(&normalized, unit)
7782                        && !same_visible_symbol(unit, target)
7783                })
7784    }
7785
7786    pub fn resolve_call_return_binding(
7787        &self,
7788        analyzer: &CppGraphSource<'_>,
7789        file: &ProjectFile,
7790        raw_name: &str,
7791        arity: usize,
7792        lexical_namespace: Option<&str>,
7793        direct_type: Option<&CodeUnit>,
7794    ) -> Option<CppScanBinding> {
7795        let normalized = normalize_reference_name(raw_name)?;
7796        let mut candidates = Vec::new();
7797        for function in
7798            self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
7799        {
7800            if cpp_callable_arity(analyzer, function).accepts(arity)
7801                && !direct_type.is_some_and(|direct_type| {
7802                    self.callable_is_constructor_declaration(analyzer, function)
7803                        && type_owner_of(analyzer, function)
7804                            .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
7805                })
7806            {
7807                candidates.push(function.clone());
7808            }
7809        }
7810        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
7811        unanimous_return_binding(analyzer, self, file, &candidates)
7812    }
7813
7814    pub fn resolve_call_return_binding_without_arity(
7815        &self,
7816        analyzer: &CppGraphSource<'_>,
7817        file: &ProjectFile,
7818        raw_name: &str,
7819        lexical_namespace: Option<&str>,
7820        direct_type: Option<&CodeUnit>,
7821    ) -> (bool, Option<CppScanBinding>) {
7822        let Some(normalized) = normalize_reference_name(raw_name) else {
7823            return (false, None);
7824        };
7825        let mut candidates = self
7826            .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
7827            .into_iter()
7828            .filter(|function| {
7829                function.is_function()
7830                    && !direct_type.is_some_and(|direct_type| {
7831                        self.callable_is_constructor_declaration(analyzer, function)
7832                            && type_owner_of(analyzer, function)
7833                                .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
7834                    })
7835            })
7836            .cloned()
7837            .collect::<Vec<_>>();
7838        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
7839        let has_candidates = !candidates.is_empty();
7840        (
7841            has_candidates,
7842            unanimous_return_binding(analyzer, self, file, &candidates),
7843        )
7844    }
7845
7846    pub fn visible_identifier_candidates<'b>(
7847        &'b self,
7848        file: &ProjectFile,
7849        identifier: &str,
7850    ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
7851        self.visible_by_identifier
7852            .get(file)
7853            .and_then(|by_name| by_name.get(identifier))
7854            .into_iter()
7855            .flatten()
7856    }
7857
7858    /// Return terminal reference names that can denote `target` from `file`.
7859    ///
7860    /// The indexed candidate table covers ordinary declarations and aliases;
7861    /// Parser-only aliases are tested lazily when their spelling is actually
7862    /// encountered in a scanned type node. Enumerating them here would parse
7863    /// every source in the include closure even when the target's direct name
7864    /// is the only spelling present in the file.
7865    pub fn visible_type_reference_component_names_for_target(
7866        &self,
7867        analyzer: &CppGraphSource<'_>,
7868        file: &ProjectFile,
7869        target: &CodeUnit,
7870    ) -> HashSet<String> {
7871        let mut names = HashSet::from_iter([target.identifier().to_string()]);
7872        if let Some(metadata) = self.cpp_template_metadata.get(target) {
7873            names.insert(metadata.primary_name.clone());
7874        }
7875
7876        if let Some(by_identifier) = self.visible_by_identifier.get(file) {
7877            for (identifier, candidates) in by_identifier {
7878                if candidates.iter().any(|candidate| {
7879                    (candidate.is_class()
7880                        && (same_visible_symbol(candidate, target)
7881                            || self.compatible_primary_template_redeclarations(candidate, target)))
7882                        || (declared_type_alias(analyzer, candidate)
7883                            && self.alias_candidate_may_preserve_target(
7884                                analyzer, file, candidate, target,
7885                            ))
7886                }) {
7887                    names.insert(identifier.clone());
7888                }
7889            }
7890        }
7891
7892        names
7893    }
7894
7895    pub fn indexed_structural_class_scope(
7896        &self,
7897        file: &ProjectFile,
7898        class: Node<'_>,
7899        source: &str,
7900    ) -> Option<Vec<String>> {
7901        let key = (file.clone(), class.start_byte(), class.end_byte());
7902        if let Some(cached) = self
7903            .indexed_structural_class_scopes
7904            .lock()
7905            .expect("C++ indexed structural-class scope cache poisoned")
7906            .get(&key)
7907            .cloned()
7908        {
7909            return cached;
7910        }
7911        let resolved = (|| {
7912            let name = class.child_by_field_name("name")?;
7913            let identifier = if name.kind() == "template_type" {
7914                node_text(name.child_by_field_name("name")?, source).to_string()
7915            } else {
7916                let mut components = Vec::new();
7917                append_cpp_name_components(name, source, &mut components)?;
7918                components.last()?.clone()
7919            };
7920            let visible = self
7921                .visible_identifier_candidates(file, &identifier)
7922                .cloned()
7923                .collect::<Vec<_>>();
7924            let mut visible = visible;
7925            for candidate in
7926                self.visible_by_file
7927                    .get(file)
7928                    .into_iter()
7929                    .flatten()
7930                    .filter(|candidate| {
7931                        self.cpp_template_metadata
7932                            .get(candidate)
7933                            .is_some_and(|metadata| metadata.primary_name == identifier)
7934                    })
7935            {
7936                if !visible
7937                    .iter()
7938                    .any(|existing| same_logical_symbol(existing, candidate))
7939                {
7940                    visible.push(candidate.clone());
7941                }
7942            }
7943            // Built once per call rather than per candidate; `cpp_source` rebuilds
7944            // the five-field source from the same `self.cpp` on every call.
7945            let cpp_source = self.cpp_source();
7946            let candidates = visible
7947                .iter()
7948                .filter(|candidate| {
7949                    candidate.source() == file
7950                        && candidate.is_class()
7951                        && !declared_type_alias(&cpp_source, candidate)
7952                        && self.cpp.ranges(candidate).iter().any(|range| {
7953                            range.start_byte <= class.start_byte()
7954                                && class.end_byte() <= range.end_byte
7955                        })
7956                })
7957                .collect::<Vec<_>>();
7958            let owner = if name.kind() == "template_type" {
7959                let expected = normalize_cpp_whitespace(node_text(name, source));
7960                let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
7961                let exact = candidates
7962                    .iter()
7963                    .copied()
7964                    .filter(|candidate| {
7965                        candidate
7966                            .fq()
7967                            .segments()
7968                            .iter()
7969                            .rev()
7970                            .find_map(|&segment| {
7971                                let (text, kind) = interner.resolve(segment);
7972                                matches!(
7973                                    kind,
7974                                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
7975                                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
7976                                )
7977                                .then_some(text)
7978                            })
7979                            .is_some_and(|text| text == expected)
7980                    })
7981                    .collect::<Vec<_>>();
7982                unique_logical_type_candidate(exact)
7983                    .or_else(|| unique_logical_type_candidate(candidates.clone()))?
7984            } else {
7985                unique_logical_type_candidate(candidates)?
7986            };
7987            Some(canonical_cpp_scope_components(&owner))
7988        })();
7989        self.indexed_structural_class_scopes
7990            .lock()
7991            .expect("C++ indexed structural-class scope cache poisoned")
7992            .insert(key, resolved.clone());
7993        resolved
7994    }
7995
7996    pub fn indexed_enclosing_owner_scope(
7997        &self,
7998        analyzer: &CppGraphSource<'_>,
7999        file: &ProjectFile,
8000        node: Node<'_>,
8001    ) -> Option<Vec<String>> {
8002        let anchor = std::iter::successors(Some(node), |current| current.parent())
8003            .find(|current| {
8004                matches!(
8005                    current.kind(),
8006                    "function_definition"
8007                        | "class_specifier"
8008                        | "struct_specifier"
8009                        | "union_specifier"
8010                )
8011            })
8012            .unwrap_or(node);
8013        let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
8014        if let Some(cached) = self
8015            .indexed_enclosing_owner_scopes
8016            .lock()
8017            .expect("C++ indexed enclosing-owner scope cache poisoned")
8018            .get(&key)
8019            .cloned()
8020        {
8021            return cached;
8022        }
8023        let resolved = (|| {
8024            let range = Range {
8025                start_byte: node.start_byte(),
8026                end_byte: node.end_byte(),
8027                start_line: node.start_position().row,
8028                end_line: node.end_position().row,
8029            };
8030            let start = analyzer.enclosing_code_unit(file, &range)?;
8031            let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
8032                start,
8033                |unit| self.cached_precise_parent_of(analyzer, unit),
8034            )
8035            .find(|unit| {
8036                unit.is_class()
8037                    && !analyzer
8038                        .type_alias_provider()
8039                        .is_some_and(|provider| provider.is_type_alias(unit))
8040            })?;
8041            Some(canonical_cpp_scope_components(&owner))
8042        })();
8043        self.indexed_enclosing_owner_scopes
8044            .lock()
8045            .expect("C++ indexed enclosing-owner scope cache poisoned")
8046            .insert(key, resolved.clone());
8047        resolved
8048    }
8049
8050    fn cached_precise_parent_of(
8051        &self,
8052        analyzer: &CppGraphSource<'_>,
8053        code_unit: &CodeUnit,
8054    ) -> Option<CodeUnit> {
8055        if let Some(cached) = self
8056            .precise_parent_cache
8057            .lock()
8058            .expect("C++ precise-parent cache poisoned")
8059            .get(code_unit)
8060            .cloned()
8061        {
8062            return cached;
8063        }
8064        let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
8065        self.precise_parent_cache
8066            .lock()
8067            .expect("C++ precise-parent cache poisoned")
8068            .insert(code_unit.clone(), resolved.clone());
8069        resolved
8070    }
8071
8072    pub fn callable_is_constructor_declaration(
8073        &self,
8074        analyzer: &CppGraphSource<'_>,
8075        candidate: &CodeUnit,
8076    ) -> bool {
8077        if !candidate.is_function() {
8078            return false;
8079        }
8080        let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
8081            return false;
8082        };
8083        let root = prepared.tree().root_node();
8084        let candidate_ranges = analyzer.ranges(candidate);
8085        let enclosed_by_matching_type = candidate_ranges.iter().any(|range| {
8086            let mut current = root
8087                .descendant_for_byte_range(range.start_byte, range.end_byte)
8088                .and_then(|node| node.parent());
8089            while let Some(node) = current {
8090                if matches!(
8091                    node.kind(),
8092                    "class_specifier" | "struct_specifier" | "union_specifier"
8093                ) {
8094                    return node
8095                        .child_by_field_name("name")
8096                        .map(|name| terminal_name(node_text(name, prepared.source())))
8097                        .is_some_and(|name| name == candidate.identifier());
8098                }
8099                current = node.parent();
8100            }
8101            false
8102        });
8103        if enclosed_by_matching_type {
8104            return true;
8105        }
8106        let indexed_containment = analyzer
8107            .declarations(candidate.source())
8108            .into_iter()
8109            .filter(|unit| unit.is_class() && unit.identifier() == candidate.identifier())
8110            .any(|owner| {
8111                analyzer.ranges(&owner).iter().any(|owner_range| {
8112                    candidate_ranges.iter().any(|candidate_range| {
8113                        owner_range.start_byte <= candidate_range.start_byte
8114                            && candidate_range.end_byte <= owner_range.end_byte
8115                    })
8116                })
8117            });
8118        if indexed_containment {
8119            return true;
8120        }
8121        let metadata = analyzer.signature_metadata(candidate);
8122        !metadata.is_empty()
8123            && metadata
8124                .iter()
8125                .all(|signature| signature.return_type_text().is_none())
8126    }
8127
8128    /// Whether a callable declaration is a class-template deduction guide.
8129    ///
8130    /// Tree-sitter represents `Box(T) -> Box<T>;` as a declaration with no
8131    /// type field whose function declarator owns a trailing return type. This
8132    /// structured shape distinguishes a guide from both a constructor (no
8133    /// trailing return) and an ordinary trailing-return function (an `auto`
8134    /// type field).
8135    pub fn callable_is_deduction_guide_declaration(
8136        &self,
8137        analyzer: &CppGraphSource<'_>,
8138        candidate: &CodeUnit,
8139    ) -> bool {
8140        if !candidate.is_function() {
8141            return false;
8142        }
8143        let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
8144            return false;
8145        };
8146        nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
8147            .into_iter()
8148            .any(|declaration| {
8149                if declaration.kind() != "declaration"
8150                    || declaration.child_by_field_name("type").is_some()
8151                {
8152                    return false;
8153                }
8154                let Some(declarator) = declaration.child_by_field_name("declarator") else {
8155                    return false;
8156                };
8157                if declarator.kind() != "function_declarator" {
8158                    return false;
8159                }
8160                let mut cursor = declarator.walk();
8161                let has_trailing_return = declarator
8162                    .named_children(&mut cursor)
8163                    .any(|child| child.kind() == "trailing_return_type");
8164                has_trailing_return
8165                    && declarator_name_node(declarator).is_some_and(|name| {
8166                        node_text(name, prepared.source()) == candidate.identifier()
8167                    })
8168            })
8169    }
8170
8171    /// Whether a callable occurrence is directly wrapped by a C++ template
8172    /// declaration. This deliberately inspects declaration syntax instead of
8173    /// inferring template status from the rendered signature.
8174    pub fn callable_is_template_declaration(
8175        &self,
8176        analyzer: &CppGraphSource<'_>,
8177        candidate: &CodeUnit,
8178    ) -> bool {
8179        if !candidate.is_function() {
8180            return false;
8181        }
8182        let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
8183            return false;
8184        };
8185        let root = prepared.tree().root_node();
8186        analyzer.ranges(candidate).iter().any(|range| {
8187            let Some(node) = node_for_exact_range(root, range)
8188                .or_else(|| root.descendant_for_byte_range(range.start_byte, range.end_byte))
8189            else {
8190                return false;
8191            };
8192            node.parent().is_some_and(|parent| {
8193                parent.kind() == "template_declaration"
8194                    && parent
8195                        .named_child(parent.named_child_count().saturating_sub(1))
8196                        .is_some_and(|declaration| same_node(declaration, node))
8197            })
8198        })
8199    }
8200
8201    pub fn type_name_candidates<'b>(
8202        &'b self,
8203        file: &ProjectFile,
8204        normalized: &str,
8205    ) -> Vec<&'b CodeUnit> {
8206        self.candidate_units(file, normalized, TargetKind::Type)
8207    }
8208
8209    pub fn visible_members_for_owner_name<'b>(
8210        &'b self,
8211        file: &ProjectFile,
8212        owner: &CodeUnit,
8213        name: &str,
8214    ) -> Vec<&'b CodeUnit> {
8215        self.visible_identifier_candidates(file, name)
8216            .filter(|unit| {
8217                // Structured owner pop on the unit's own `fq()` (shared with
8218                // `CodeUnitIndex::parent_of`), not a re-split of its rendered fqn
8219                // string.
8220                brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
8221                    .is_some_and(|parent| parent == owner.fq_name())
8222            })
8223            .collect()
8224    }
8225
8226    pub fn visible_member_for_owner_name(
8227        &self,
8228        file: &ProjectFile,
8229        owner: &CodeUnit,
8230        name: &str,
8231    ) -> VisibleMemberResolution {
8232        let candidates = self.visible_members_for_owner_name(file, owner, name);
8233        let mut callables = Vec::new();
8234        let mut non_callable = None;
8235        for candidate in candidates {
8236            if candidate.is_function() {
8237                callables.push(candidate.clone());
8238            } else if non_callable.is_none() {
8239                non_callable = Some(candidate.clone());
8240            }
8241        }
8242        match (callables.is_empty(), non_callable) {
8243            (false, None) => VisibleMemberResolution::Callable(callables),
8244            (true, Some(_)) => VisibleMemberResolution::NonCallable,
8245            (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
8246            (true, None) => VisibleMemberResolution::Missing,
8247        }
8248    }
8249
8250    fn field_declared_type_fact(
8251        &self,
8252        analyzer: &CppGraphSource<'_>,
8253        field: &CodeUnit,
8254    ) -> Option<DeclaredFieldTypeFact> {
8255        if let Some(cached) = self
8256            .field_type_facts
8257            .lock()
8258            .expect("C++ field type fact cache poisoned")
8259            .get(field)
8260            .cloned()
8261        {
8262            return cached;
8263        }
8264        let decoded = decode_field_declared_type_fact(analyzer, field);
8265        self.field_type_facts
8266            .lock()
8267            .expect("C++ field type fact cache poisoned")
8268            .insert(field.clone(), decoded.clone());
8269        decoded
8270    }
8271
8272    fn structured_alias_target(
8273        &self,
8274        analyzer: &CppGraphSource<'_>,
8275        unit: &CodeUnit,
8276    ) -> Option<StructuredAliasTarget> {
8277        if let Some(cached) = self
8278            .structured_alias_targets
8279            .lock()
8280            .expect("C++ structured alias target cache poisoned")
8281            .get(unit)
8282            .cloned()
8283        {
8284            return cached;
8285        }
8286        let decoded = decode_structured_alias_target(analyzer, unit);
8287        self.structured_alias_targets
8288            .lock()
8289            .expect("C++ structured alias target cache poisoned")
8290            .insert(unit.clone(), decoded.clone());
8291        decoded
8292    }
8293
8294    pub fn type_candidates<'b>(
8295        &'b self,
8296        file: &ProjectFile,
8297        normalized: &str,
8298    ) -> Vec<&'b CodeUnit> {
8299        let mut candidates = self
8300            .candidate_units(file, normalized, TargetKind::Type)
8301            .into_iter()
8302            .filter(|unit| unit.kind() == CodeUnitType::Class || is_type_alias(unit))
8303            .collect::<Vec<_>>();
8304        dedup_unit_refs(&mut candidates);
8305        candidates
8306    }
8307
8308    pub fn named_candidates_for_normalized<'b>(
8309        &'b self,
8310        file: &ProjectFile,
8311        normalized: &str,
8312        kind: TargetKind,
8313    ) -> Vec<&'b CodeUnit> {
8314        let mut candidates = self
8315            .candidate_units(file, normalized, kind)
8316            .into_iter()
8317            .filter(|unit| {
8318                matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
8319            })
8320            .collect::<Vec<_>>();
8321        dedup_unit_refs(&mut candidates);
8322        candidates
8323    }
8324
8325    pub fn candidate_units<'b>(
8326        &'b self,
8327        file: &ProjectFile,
8328        normalized: &str,
8329        kind: TargetKind,
8330    ) -> Vec<&'b CodeUnit> {
8331        if normalized.contains("::") {
8332            // `normalized` comes from `normalize_cpp_reference_text`, which
8333            // truncates at the first `(`/`{`/`<`, leaving a plain `::`-joined
8334            // qualified-id with no embedded `.`/`/`/`\` and operator tokens
8335            // kept intact by the shared splitter's operator merge — the same
8336            // domain `cpp_reference_fqn_candidates` below already parses with
8337            // the shared splitter. Re-tokenizing and taking the last segment
8338            // reproduces `rsplit("::").find(non-empty)`'s terminal-component
8339            // scan exactly.
8340            let Some(identifier) = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8341                brokk_bifrost_core::analyzer::Language::Cpp,
8342                normalized,
8343            )
8344            .pop() else {
8345                return Vec::new();
8346            };
8347            let fqns = cpp_reference_fqn_candidates(normalized, kind);
8348            return self
8349                .visible_identifier_candidates(file, &identifier)
8350                .filter(|unit| {
8351                    #[cfg(any(test, feature = "test-support"))]
8352                    self.qualified_candidate_inspections
8353                        .fetch_add(1, Ordering::Relaxed);
8354                    fqns.iter().any(|fqn| unit.fq_name() == *fqn)
8355                        || canonical_cpp_name_matches(unit, normalized)
8356                })
8357                .collect();
8358        }
8359        self.visible_identifier_candidates(file, normalized)
8360            .collect()
8361    }
8362
8363    #[cfg(any(test, feature = "test-support"))]
8364    pub fn reset_qualified_candidate_inspections(&self) {
8365        self.qualified_candidate_inspections
8366            .store(0, Ordering::Relaxed);
8367    }
8368
8369    #[cfg(any(test, feature = "test-support"))]
8370    pub fn qualified_candidate_inspections(&self) -> usize {
8371        self.qualified_candidate_inspections.load(Ordering::Relaxed)
8372    }
8373
8374    #[cfg(any(test, feature = "test-support"))]
8375    pub fn reset_target_preserving_type_resolution_count(&self) {
8376        self.target_preserving_type_resolution_count
8377            .store(0, Ordering::Relaxed);
8378    }
8379
8380    #[cfg(any(test, feature = "test-support"))]
8381    pub fn target_preserving_type_resolution_count(&self) -> usize {
8382        self.target_preserving_type_resolution_count
8383            .load(Ordering::Relaxed)
8384    }
8385
8386    #[cfg(any(test, feature = "test-support"))]
8387    pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
8388        self.visible_parser_alias_name_set_build_count
8389            .load(Ordering::Relaxed)
8390    }
8391}
8392
8393#[derive(Default)]
8394struct IncludeGraph {
8395    targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
8396}
8397
8398impl IncludeGraph {
8399    fn extend_with<F>(
8400        &mut self,
8401        root: &ProjectFile,
8402        cancellation: Option<&CancellationToken>,
8403        targets_for: &mut F,
8404    ) where
8405        F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
8406    {
8407        let mut stack = vec![root.clone()];
8408        while let Some(file) = stack.pop() {
8409            if cancellation.is_some_and(CancellationToken::is_cancelled) {
8410                break;
8411            }
8412            if self.targets_by_file.contains_key(&file) {
8413                continue;
8414            }
8415            let targets = targets_for(&file);
8416            stack.extend(targets.iter().cloned());
8417            self.targets_by_file.insert(file, targets);
8418        }
8419    }
8420
8421    fn files(&self) -> impl Iterator<Item = &ProjectFile> {
8422        self.targets_by_file.keys()
8423    }
8424
8425    fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
8426        self.targets_by_file
8427            .get(file)
8428            .map(Vec::as_slice)
8429            .unwrap_or_default()
8430    }
8431
8432    fn reachable_files(
8433        &self,
8434        root: &ProjectFile,
8435        cancellation: Option<&CancellationToken>,
8436    ) -> HashSet<ProjectFile> {
8437        let mut pending = vec![root.clone()];
8438        let mut visited = HashSet::default();
8439        while let Some(file) = pending.pop() {
8440            if cancellation.is_some_and(CancellationToken::is_cancelled) {
8441                break;
8442            }
8443            if visited.insert(file.clone()) {
8444                pending.extend(self.targets(&file).iter().cloned());
8445            }
8446        }
8447        visited
8448    }
8449}
8450
8451fn build_bounded_visible_declarations(
8452    cpp: &dyn CppSource,
8453    token: QueryToken<'_>,
8454    analyzer: &CppGraphSource<'_>,
8455    roots: &HashSet<ProjectFile>,
8456    visible_sources: &HashMap<ProjectFile, HashSet<ProjectFile>>,
8457    cancellation: Option<&CancellationToken>,
8458    stats: &mut BoundedVisibilityStats,
8459) -> HashMap<ProjectFile, HashSet<CodeUnit>> {
8460    roots
8461        .iter()
8462        .map(|root| {
8463            let reading_is_c = analyzer.reference_uses_c_semantics(root);
8464            let declarations_started = Instant::now();
8465            let root_declarations =
8466                bounded_visibility_declarations_in_reading(analyzer, root, reading_is_c);
8467            stats.declaration_elapsed += declarations_started.elapsed();
8468            stats.declaration_reads += 1;
8469            stats.declaration_units += root_declarations.len();
8470            let mut visible = root_declarations.into_iter().collect::<HashSet<_>>();
8471            let mut pending_names = HashSet::default();
8472            if let Some(prepared) = cpp.prepared_syntax(token, root) {
8473                let mut pending_nodes = vec![prepared.tree().root_node()];
8474                while let Some(node) = pending_nodes.pop() {
8475                    if matches!(
8476                        node.kind(),
8477                        "identifier"
8478                            | "type_identifier"
8479                            | "field_identifier"
8480                            | "namespace_identifier"
8481                    ) {
8482                        pending_names.insert(node_text(node, prepared.source()).to_string());
8483                    }
8484                    if node.kind() == "preproc_arg" {
8485                        for reference in
8486                            object_macro_replacement_type_references(node, prepared.source())
8487                        {
8488                            pending_names.extend(reference.components);
8489                        }
8490                    }
8491                    for index in 0..node.named_child_count() {
8492                        if let Some(child) = node.named_child(index) {
8493                            pending_nodes.push(child);
8494                        }
8495                    }
8496                }
8497            }
8498            stats.root_names += pending_names.len();
8499            let mut completed_names = HashSet::default();
8500            while !pending_names.is_empty() {
8501                stats.rounds += 1;
8502                let round_names = std::mem::take(&mut pending_names);
8503                let mut requested_names_by_source: HashMap<ProjectFile, HashSet<String>> =
8504                    HashMap::default();
8505                for identifier in round_names {
8506                    if !completed_names.insert(identifier.clone())
8507                        || cancellation.is_some_and(CancellationToken::is_cancelled)
8508                    {
8509                        continue;
8510                    }
8511                    let lookup_started = Instant::now();
8512                    let candidates = cpp.visibility_identifier_candidates(&identifier);
8513                    stats.lookup_elapsed += lookup_started.elapsed();
8514                    stats.identifier_lookups += 1;
8515                    stats.candidate_units += candidates.len();
8516                    for source in candidates
8517                        .into_iter()
8518                        .map(|unit| unit.source().clone())
8519                        .collect::<HashSet<_>>()
8520                    {
8521                        if source != *root
8522                            && visible_sources
8523                                .get(root)
8524                                .is_some_and(|files| files.contains(&source))
8525                        {
8526                            requested_names_by_source
8527                                .entry(source)
8528                                .or_default()
8529                                .insert(identifier.clone());
8530                        }
8531                    }
8532                }
8533                stats.candidate_sources += requested_names_by_source.len();
8534                for (source, requested_names) in requested_names_by_source {
8535                    let declarations_started = Instant::now();
8536                    let declarations =
8537                        bounded_visibility_declarations_in_reading(analyzer, &source, reading_is_c);
8538                    stats.declaration_elapsed += declarations_started.elapsed();
8539                    stats.declaration_reads += 1;
8540                    stats.declaration_units += declarations.len();
8541                    for unit in declarations {
8542                        let template_metadata = unit
8543                            .is_class()
8544                            .then(|| cpp.template_metadata(&unit))
8545                            .flatten();
8546                        if !requested_names.contains(unit.identifier())
8547                            && !template_metadata.as_ref().is_some_and(|metadata| {
8548                                requested_names.contains(&metadata.primary_name)
8549                            })
8550                        {
8551                            continue;
8552                        }
8553                        stats.selected_units += 1;
8554                        if let Some(prepared) = cpp.prepared_syntax(token, &source) {
8555                            let ast_started = Instant::now();
8556                            for range in analyzer.ranges(&unit) {
8557                                let Some(declaration) =
8558                                    node_for_exact_range(prepared.tree().root_node(), &range)
8559                                else {
8560                                    continue;
8561                                };
8562                                let mut pending_nodes = vec![declaration];
8563                                while let Some(node) = pending_nodes.pop() {
8564                                    stats.dependency_ast_nodes += 1;
8565                                    if matches!(
8566                                        node.kind(),
8567                                        "type_identifier" | "namespace_identifier"
8568                                    ) {
8569                                        let name = node_text(node, prepared.source());
8570                                        if !completed_names.contains(name)
8571                                            && pending_names.insert(name.to_string())
8572                                        {
8573                                            stats.dependency_names += 1;
8574                                        }
8575                                    }
8576                                    for index in 0..node.named_child_count() {
8577                                        if let Some(child) = node.named_child(index) {
8578                                            pending_nodes.push(child);
8579                                        }
8580                                    }
8581                                }
8582                            }
8583                            stats.dependency_ast_elapsed += ast_started.elapsed();
8584                        }
8585                        if let Some(metadata) = template_metadata
8586                            && !completed_names.contains(&metadata.primary_name)
8587                        {
8588                            pending_names.insert(metadata.primary_name);
8589                        }
8590                        visible.insert(unit);
8591                    }
8592                }
8593            }
8594            (root.clone(), visible)
8595        })
8596        .collect()
8597}
8598
8599#[derive(Default)]
8600struct BoundedVisibilityStats {
8601    rounds: usize,
8602    root_names: usize,
8603    identifier_lookups: usize,
8604    candidate_units: usize,
8605    candidate_sources: usize,
8606    declaration_reads: usize,
8607    declaration_units: usize,
8608    selected_units: usize,
8609    dependency_ast_nodes: usize,
8610    dependency_names: usize,
8611    lookup_elapsed: Duration,
8612    declaration_elapsed: Duration,
8613    dependency_ast_elapsed: Duration,
8614}
8615
8616fn bounded_visibility_declarations_in_reading(
8617    analyzer: &CppGraphSource<'_>,
8618    file: &ProjectFile,
8619    c_semantics: bool,
8620) -> BTreeSet<CodeUnit> {
8621    #[cfg(any(test, feature = "test-support"))]
8622    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(count.get() + 1));
8623    analyzer.declarations_in_reading(file, c_semantics)
8624}
8625
8626#[cfg(any(test, feature = "test-support"))]
8627pub fn reset_bounded_visibility_declaration_read_count_for_test() {
8628    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(0));
8629}
8630
8631#[cfg(any(test, feature = "test-support"))]
8632pub fn bounded_visibility_declaration_read_count_for_test() -> usize {
8633    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(Cell::get)
8634}
8635
8636pub struct VisibilityData {
8637    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
8638    pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
8639}
8640
8641/// Build the per-root include closure and the declarations each root can see
8642/// through it.
8643///
8644/// `declarations_for` takes the reading to answer in (issue #1970): a root
8645/// compiled as C sees the C reading of every file in its closure, a root
8646/// compiled as C++ sees the C++ reading, and `reading_is_c_for` decides which
8647/// per root. The two readings agree for all but a handful of headers, so the
8648/// C map is built only when some root actually asks for it, and only over the
8649/// files that root reaches.
8650pub fn build_visibility_data<F, R, D>(
8651    roots: &HashSet<ProjectFile>,
8652    cancellation: Option<&CancellationToken>,
8653    mut targets_for: F,
8654    mut reading_is_c_for: R,
8655    mut declarations_for: D,
8656) -> VisibilityData
8657where
8658    F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
8659    R: FnMut(&ProjectFile) -> bool,
8660    D: FnMut(&ProjectFile, bool) -> BTreeSet<CodeUnit>,
8661{
8662    let mut include_graph = IncludeGraph::default();
8663    for file in roots {
8664        if cancellation.is_some_and(CancellationToken::is_cancelled) {
8665            break;
8666        }
8667        include_graph.extend_with(file, cancellation, &mut targets_for);
8668    }
8669    let cpp_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
8670        .files()
8671        .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
8672        .map(|file| (file.clone(), declarations_for(file, false)))
8673        .collect();
8674    let mut c_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = HashMap::default();
8675    let mut visible_by_file = HashMap::default();
8676    let mut visible_source_files_by_root = HashMap::default();
8677    for file in roots {
8678        if cancellation.is_some_and(CancellationToken::is_cancelled) {
8679            break;
8680        }
8681        let mut visited = HashSet::default();
8682        let mut visible = HashSet::default();
8683        let declarations_by_file = if reading_is_c_for(file) {
8684            for reached in cpp_declarations_by_file.keys() {
8685                if !c_declarations_by_file.contains_key(reached) {
8686                    let declarations = declarations_for(reached, true);
8687                    c_declarations_by_file.insert(reached.clone(), declarations);
8688                }
8689            }
8690            &c_declarations_by_file
8691        } else {
8692            &cpp_declarations_by_file
8693        };
8694        collect_visible_declarations(
8695            &include_graph,
8696            declarations_by_file,
8697            file,
8698            &mut visited,
8699            &mut visible,
8700            cancellation,
8701        );
8702        visible_by_file.insert(file.clone(), visible);
8703        visible_source_files_by_root.insert(file.clone(), visited);
8704    }
8705    VisibilityData {
8706        visible_by_file,
8707        visible_source_files_by_root,
8708    }
8709}
8710
8711/// Admit the class that an out-of-line definition proves is in scope.
8712///
8713/// `Owner::member(...) { ... }` in a file is structured proof that `Owner`
8714/// names a class-like entity in that file's scope: a member declaration can
8715/// live in a file other than its class's only when it is written out of line.
8716/// A file a build concatenates rather than compiles carries no `#include` edge
8717/// to the header declaring `Owner` -- google/wuffs
8718/// `internal/cgen/auxiliary/image.cc` defines
8719/// `DecodeImageResult::DecodeImageResult` and never includes `image.hh` -- so
8720/// every unqualified member and constructor reference in it had no candidate at
8721/// all (#1832).
8722///
8723/// The evidence is the indexed declaration's own owner name, taken from its
8724/// `FqName`, so this stays a structured answer rather than a text fallback.
8725/// Only an owner the file cannot already see is admitted: that is what keeps a
8726/// header declaring its own class from additionally seeing every same-named
8727/// class in the workspace, and it makes the pass free for the ordinary file
8728/// whose owners are all visible.
8729#[derive(Default)]
8730struct OutOfLineOwnerBindingStats {
8731    unseen_owners: usize,
8732    definition_lookups: usize,
8733    admitted: usize,
8734}
8735
8736fn extend_with_out_of_line_owner_bindings(
8737    cpp: &dyn CppSource,
8738    visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
8739) -> OutOfLineOwnerBindingStats {
8740    let mut stats = OutOfLineOwnerBindingStats::default();
8741    for (file, visible) in visible_by_file.iter_mut() {
8742        // The include-closure walk seeds every root with its own declarations,
8743        // so the file's members are already here; re-reading them from the
8744        // analyzer would pay for the same declaration set twice.
8745        let mut unseen_owners: HashSet<String> = visible
8746            .iter()
8747            .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
8748            .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
8749            .collect();
8750        if unseen_owners.is_empty() {
8751            continue;
8752        }
8753        for unit in visible.iter().filter(|unit| unit.is_class()) {
8754            unseen_owners.remove(&unit.fq_name());
8755        }
8756        stats.unseen_owners += unseen_owners.len();
8757        stats.definition_lookups += unseen_owners.len();
8758        let admitted = unseen_owners
8759            .iter()
8760            .flat_map(|owner| cpp.definitions(owner))
8761            .filter(CodeUnit::is_class)
8762            .collect::<Vec<_>>();
8763        stats.admitted += admitted.len();
8764        visible.extend(admitted);
8765    }
8766    stats
8767}
8768
8769pub enum VisibleMemberResolution {
8770    Callable(Vec<CodeUnit>),
8771    NonCallable,
8772    AmbiguousKind,
8773    Missing,
8774}
8775
8776#[derive(Clone)]
8777pub enum EnclosingMemberOwnerResolution {
8778    Owner(CodeUnit),
8779    Ambiguous,
8780    Missing,
8781}
8782
8783pub fn resolve_declaring_member_owner(
8784    analyzer: &CppGraphSource<'_>,
8785    visibility: &VisibilityIndex<'_>,
8786    file: &ProjectFile,
8787    receiver_owner: &CodeUnit,
8788    member_name: &str,
8789) -> EnclosingMemberOwnerResolution {
8790    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
8791        return EnclosingMemberOwnerResolution::Missing;
8792    };
8793    let Some(receiver_owner) =
8794        visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
8795    else {
8796        return EnclosingMemberOwnerResolution::Ambiguous;
8797    };
8798    let resolve_level = |frontier: &[CodeUnit]| {
8799        let mut member_owners = Vec::new();
8800        for raw_owner in frontier {
8801            let Some(owner) =
8802                visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
8803            else {
8804                return EnclosingMemberOwnerResolution::Ambiguous;
8805            };
8806            for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
8807                // A type nested in an owner shares the owner's member-name
8808                // index, but it cannot be the value receiver of `owner.name`.
8809                // Keep value members here so an anonymous aggregate field
8810                // does not become ambiguous with its promoted receiver type.
8811                if !member.is_field() && !member.is_function() {
8812                    continue;
8813                }
8814                let Some(member_owner) = type_owner_of(analyzer, member) else {
8815                    return EnclosingMemberOwnerResolution::Ambiguous;
8816                };
8817                if !member_owners
8818                    .iter()
8819                    .any(|existing| same_visible_symbol(existing, &member_owner))
8820                {
8821                    member_owners.push(member_owner);
8822                }
8823            }
8824        }
8825        match member_owners.len() {
8826            0 => EnclosingMemberOwnerResolution::Missing,
8827            1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
8828            _ => EnclosingMemberOwnerResolution::Ambiguous,
8829        }
8830    };
8831    // The first declaration on each structured base path hides deeper names,
8832    // regardless of whether its callable overload is applicable at a particular
8833    // call site. Applicability is checked only after this owner is established.
8834    let direct = resolve_level(std::slice::from_ref(&receiver_owner));
8835    if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
8836        return direct;
8837    }
8838    let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
8839    let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
8840    let mut path_matches = Vec::new();
8841    while let Some(raw_owner) = stack.pop() {
8842        let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
8843        else {
8844            return EnclosingMemberOwnerResolution::Ambiguous;
8845        };
8846        // Persisted hierarchy edges do not encode virtual-base or base-subobject paths.
8847        // Propagate at most two occurrences of each owner: that preserves the distinction
8848        // between one and multiple resolving base paths without exponential diamond walks.
8849        let propagated = propagated_counts.entry(owner.clone()).or_default();
8850        if *propagated == 2 {
8851            continue;
8852        }
8853        *propagated += 1;
8854        match resolve_level(std::slice::from_ref(&owner)) {
8855            EnclosingMemberOwnerResolution::Owner(owner) => {
8856                path_matches.push(owner);
8857                if path_matches.len() == 2 {
8858                    return EnclosingMemberOwnerResolution::Ambiguous;
8859                }
8860            }
8861            EnclosingMemberOwnerResolution::Ambiguous => {
8862                return EnclosingMemberOwnerResolution::Ambiguous;
8863            }
8864            EnclosingMemberOwnerResolution::Missing => {
8865                stack.extend(hierarchy.get_direct_ancestors(&owner));
8866            }
8867        }
8868    }
8869    match path_matches.len() {
8870        0 => EnclosingMemberOwnerResolution::Missing,
8871        1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
8872        _ => unreachable!("base-path matches are capped at one before returning"),
8873    }
8874}
8875
8876/// Resolve the declaring owner of a callable after applying a member
8877/// `using <Base>::<member>;` declaration to one exact call arity.
8878///
8879/// Ordinary member lookup is intentionally name-based: the first class that
8880/// declares a name hides the same name on deeper bases. A member
8881/// using-declaration is the one exception. When none of the declarations on
8882/// that first owner accepts the call arity, it can reintroduce an applicable
8883/// overload from the named base. If a declaration on the first owner does
8884/// accept the arity, argument types would be needed to choose between it and
8885/// a same-arity introduced overload, so this resolver conservatively keeps the
8886/// ordinary owner (#1835/#1843).
8887///
8888/// The caller supplies ordinary name-based owner resolution so a file scan can
8889/// reuse its existing owner cache before applying this callable-only exception.
8890pub fn resolve_declaring_callable_owner(
8891    analyzer: &CppGraphSource<'_>,
8892    visibility: &VisibilityIndex<'_>,
8893    file: &ProjectFile,
8894    ordinary: EnclosingMemberOwnerResolution,
8895    member_name: &str,
8896    call_arity: usize,
8897) -> EnclosingMemberOwnerResolution {
8898    let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
8899        return ordinary;
8900    };
8901    if visibility
8902        .visible_members_for_owner_name(file, ordinary_owner, member_name)
8903        .into_iter()
8904        .any(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity))
8905    {
8906        return ordinary;
8907    }
8908
8909    let mut pending = match member_using_declaration_bases(
8910        analyzer,
8911        visibility,
8912        file,
8913        ordinary_owner,
8914        member_name,
8915    ) {
8916        Ok(bases) => bases,
8917        Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
8918    };
8919    let mut visited = HashSet::default();
8920    let mut introduced_owners = Vec::new();
8921    while let Some(owner) = pending.pop() {
8922        if !visited.insert(owner.clone()) {
8923            continue;
8924        }
8925        let accepts_arity = visibility
8926            .visible_members_for_owner_name(file, &owner, member_name)
8927            .into_iter()
8928            .any(|unit| {
8929                unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity)
8930            });
8931        if accepts_arity {
8932            if !introduced_owners
8933                .iter()
8934                .any(|existing| same_visible_symbol(existing, &owner))
8935            {
8936                introduced_owners.push(owner);
8937            }
8938            continue;
8939        }
8940        match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
8941            Ok(bases) => pending.extend(bases),
8942            Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
8943        }
8944    }
8945    match introduced_owners.as_slice() {
8946        [] => ordinary,
8947        [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
8948        _ => EnclosingMemberOwnerResolution::Ambiguous,
8949    }
8950}
8951
8952fn member_using_declaration_bases(
8953    analyzer: &CppGraphSource<'_>,
8954    visibility: &VisibilityIndex<'_>,
8955    file: &ProjectFile,
8956    owner: &CodeUnit,
8957    member_name: &str,
8958) -> Result<Vec<CodeUnit>, ()> {
8959    let Some(source) = analyzer.get_source(owner, false) else {
8960        return Ok(Vec::new());
8961    };
8962    let scopes = cpp_member_using_declaration_scopes(&source, member_name);
8963    if scopes.is_empty() {
8964        return Ok(Vec::new());
8965    }
8966    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
8967        return Ok(Vec::new());
8968    };
8969    let mut bases = Vec::new();
8970    for raw_ancestor in hierarchy.get_ancestors(owner) {
8971        let Some(ancestor) =
8972            visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
8973        else {
8974            return Err(());
8975        };
8976        let qualified = cpp_name_for(&ancestor);
8977        if scopes
8978            .iter()
8979            .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
8980            && !bases
8981                .iter()
8982                .any(|existing| same_visible_symbol(existing, &ancestor))
8983        {
8984            bases.push(ancestor);
8985        }
8986    }
8987    Ok(bases)
8988}
8989
8990pub fn lexical_component_tiers<'a>(
8991    components: &'a [String],
8992    global: bool,
8993    lexical_scope: &'a [String],
8994) -> impl Iterator<Item = Vec<String>> + 'a {
8995    let first_prefix_len = if global { 0 } else { lexical_scope.len() };
8996    (0..=first_prefix_len).rev().map(move |prefix_len| {
8997        let mut qualified = Vec::with_capacity(prefix_len + components.len());
8998        qualified.extend_from_slice(&lexical_scope[..prefix_len]);
8999        qualified.extend_from_slice(components);
9000        qualified
9001    })
9002}
9003
9004pub fn build_visible_identifier_index(
9005    analyzer: &CppGraphSource<'_>,
9006    visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
9007    visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
9008    global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
9009) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
9010    let mut out = HashMap::default();
9011    for (file, visible) in visible_by_file {
9012        let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
9013        for unit in visible {
9014            if unit.is_field()
9015                && !visible_source_files_by_root
9016                    .get(file)
9017                    .is_some_and(|sources| sources.contains(unit.source()))
9018                && cpp_global_field_has_internal_linkage_cached(
9019                    analyzer,
9020                    global_field_internal_linkage,
9021                    unit,
9022                )
9023            {
9024                continue;
9025            }
9026            by_identifier
9027                .entry(unit.identifier().to_string())
9028                .or_default()
9029                .push(unit.clone());
9030        }
9031        for units in by_identifier.values_mut() {
9032            sort_lookup_units(units);
9033            units.dedup();
9034        }
9035        out.insert(file.clone(), by_identifier);
9036    }
9037    out
9038}
9039
9040fn sort_lookup_units(units: &mut [CodeUnit]) {
9041    units.sort_by(|left, right| {
9042        left.fq_name()
9043            .cmp(&right.fq_name())
9044            .then_with(|| left.signature().cmp(&right.signature()))
9045            .then_with(|| left.source().cmp(right.source()))
9046            .then_with(|| left.kind().cmp(&right.kind()))
9047            .then_with(|| {
9048                left.package_segment_count()
9049                    .cmp(&right.package_segment_count())
9050            })
9051            .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
9052            .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
9053    });
9054}
9055
9056fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
9057    let interner = segment_interner();
9058    for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
9059        let (left_text, left_kind) = interner.resolve(left_id);
9060        let (right_text, right_kind) = interner.resolve(right_id);
9061        let order = left_text
9062            .cmp(right_text)
9063            .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
9064        if order != CmpOrdering::Equal {
9065            return order;
9066        }
9067    }
9068    left.len().cmp(&right.len())
9069}
9070
9071const fn segment_kind_order(kind: SegmentKind) -> u8 {
9072    match kind {
9073        SegmentKind::Path => 0,
9074        SegmentKind::Package => 1,
9075        SegmentKind::Type => 2,
9076        SegmentKind::Companion => 3,
9077        SegmentKind::Nested => 4,
9078        SegmentKind::Member => 5,
9079        SegmentKind::Unknown => 6,
9080    }
9081}
9082
9083fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
9084    let mut deduped = Vec::with_capacity(units.len());
9085    for unit in units.drain(..) {
9086        if !deduped.contains(&unit) {
9087            deduped.push(unit);
9088        }
9089    }
9090    *units = deduped;
9091}
9092
9093pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
9094    // Same domain as `candidate_units` above: `reference` is a plain
9095    // `::`-joined qualified-id with operator tokens kept intact by the shared
9096    // splitter's operator merge.
9097    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9098        brokk_bifrost_core::analyzer::Language::Cpp,
9099        reference,
9100    );
9101    if parts.is_empty() {
9102        return Vec::new();
9103    }
9104
9105    let mut candidates = Vec::new();
9106    for package_len in 0..parts.len() {
9107        let package = parts[..package_len].join("::");
9108        let rest = &parts[package_len..];
9109        if rest.is_empty() {
9110            continue;
9111        }
9112        match kind {
9113            TargetKind::Type | TargetKind::Constructor => {
9114                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
9115                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
9116            }
9117            TargetKind::FreeFunction
9118            | TargetKind::Method
9119            | TargetKind::GlobalField
9120            | TargetKind::MemberField
9121            | TargetKind::Macro => {
9122                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
9123                if rest.len() > 1 {
9124                    let owner = rest[..rest.len() - 1].join("$");
9125                    let short = format!("{}.{}", owner, rest[rest.len() - 1]);
9126                    push_cpp_fqn_candidate(&mut candidates, &package, &short);
9127                }
9128            }
9129        }
9130    }
9131    candidates
9132}
9133
9134fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
9135    let fqn = if package.is_empty() {
9136        short.to_string()
9137    } else {
9138        format!("{package}.{short}")
9139    };
9140    if !out.contains(&fqn) {
9141        out.push(fqn);
9142    }
9143}
9144
9145pub fn infer_cpp_initializer_type(
9146    analyzer: &CppGraphSource<'_>,
9147    visibility: &VisibilityIndex<'_>,
9148    file: &ProjectFile,
9149    source: &str,
9150    node: Node<'_>,
9151) -> Option<CodeUnit> {
9152    infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
9153        .and_then(|binding| binding.unit)
9154}
9155
9156pub fn infer_cpp_initializer_binding(
9157    analyzer: &CppGraphSource<'_>,
9158    visibility: &VisibilityIndex<'_>,
9159    file: &ProjectFile,
9160    source: &str,
9161    node: Node<'_>,
9162    receiver_resolver: Option<&ReceiverResolver<'_>>,
9163) -> Option<CppScanBinding> {
9164    match node.kind() {
9165        "new_expression" => {
9166            let text = normalize_cpp_whitespace(node_text(node, source));
9167            let rest = text.strip_prefix("new ").unwrap_or(text.as_str());
9168            let type_text = rest.split(['(', '{']).next().unwrap_or(rest);
9169            let name = normalize_cpp_type_name(type_text);
9170            Some(CppScanBinding::from_type_name(
9171                name.clone(),
9172                visibility.resolve_type(file, &name),
9173                1,
9174            ))
9175        }
9176        "call_expression" => node.child_by_field_name("function").and_then(|function| {
9177            // `a().b()` and `p->b()` invoke a member on a receiver *value*. The
9178            // callee's source text is an expression, not a name, and every name
9179            // lookup below normalizes a reference by truncating at the first
9180            // `(`: `first().second` would read as `first`, so the chained call
9181            // would take the type of `first()` instead of the type of
9182            // `first().second()` (#2178). Only the member path can answer for
9183            // this shape, so route to it from the callee's node kind.
9184            if function.kind() == "field_expression" {
9185                let arity = visibility.call_arity_evidence(file, node, source).exact()?;
9186                return resolve_field_method_call_return_binding(
9187                    analyzer,
9188                    visibility,
9189                    file,
9190                    source,
9191                    function,
9192                    arity,
9193                    receiver_resolver,
9194                );
9195            }
9196            let function_text = node_text(function, source);
9197            let direct_type_binding = visibility
9198                .resolve_type(file, function_text)
9199                .map(|unit| CppScanBinding::from_unit(unit, 0));
9200            if function.kind() == "template_function" && direct_type_binding.is_some() {
9201                let lexical_namespace = enclosing_namespace_context(node, source);
9202                let arity = visibility.call_arity_evidence(file, node, source).exact();
9203                if let Some(arity) = arity
9204                    && let Some(binding) = visibility.resolve_call_return_binding(
9205                        analyzer,
9206                        file,
9207                        function_text,
9208                        arity,
9209                        lexical_namespace.as_deref(),
9210                        direct_type_binding
9211                            .as_ref()
9212                            .and_then(|binding| binding.unit.as_ref()),
9213                    )
9214                {
9215                    return Some(binding);
9216                }
9217                let (has_callable, callable_binding) = visibility
9218                    .resolve_call_return_binding_without_arity(
9219                        analyzer,
9220                        file,
9221                        function_text,
9222                        lexical_namespace.as_deref(),
9223                        direct_type_binding
9224                            .as_ref()
9225                            .and_then(|binding| binding.unit.as_ref()),
9226                    );
9227                if let Some(binding) = callable_binding {
9228                    return Some(binding);
9229                }
9230                if has_callable {
9231                    return None;
9232                }
9233                return direct_type_binding;
9234            }
9235            // Only the return-typed branches need the argument count. An
9236            // unknown arity leaves them out, exactly as in the template arm
9237            // above, and still constructs the direct type: `File(getPath())`
9238            // names `File` whether or not `getPath()`'s expansion is provable.
9239            let arity = visibility.call_arity_evidence(file, node, source).exact();
9240            if let Some(arity) = arity {
9241                let direct_type_binding_for_call = direct_type_binding.clone();
9242                if let Some(binding) = resolve_static_method_call_return_binding(
9243                    analyzer, visibility, file, source, function, arity,
9244                )
9245                .or_else(|| {
9246                    // An applicable free function supplies the receiver value
9247                    // before an unrelated visible type with the same terminal
9248                    // name. The direct type still excludes its own constructor
9249                    // declaration below and remains the construction fallback.
9250                    visibility.resolve_call_return_binding(
9251                        analyzer,
9252                        file,
9253                        function_text,
9254                        arity,
9255                        enclosing_namespace_context(node, source).as_deref(),
9256                        direct_type_binding_for_call
9257                            .as_ref()
9258                            .and_then(|binding| binding.unit.as_ref()),
9259                    )
9260                }) {
9261                    return Some(binding);
9262                }
9263            }
9264            direct_type_binding
9265        }),
9266        _ => None,
9267    }
9268}
9269
9270fn resolve_static_method_call_return_binding(
9271    analyzer: &CppGraphSource<'_>,
9272    visibility: &VisibilityIndex<'_>,
9273    file: &ProjectFile,
9274    source: &str,
9275    function: Node<'_>,
9276    arity: usize,
9277) -> Option<CppScanBinding> {
9278    if function.kind() != "qualified_identifier" {
9279        return None;
9280    }
9281    let qualified = normalize_cpp_reference_text(node_text(function, source));
9282    // A C++ qualified-id is `::`-joined with no embedded delimiters in any
9283    // single component (the shared splitter's operator-token merge keeps
9284    // `operator+`-style names intact), so re-tokenizing with the shared
9285    // structured splitter and peeling the terminal segment reproduces
9286    // `rsplit_once("::")`'s (owner, member) split exactly — same shape as
9287    // `cpp_out_of_line_function_owner`'s `qualified` split above.
9288    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9289        brokk_bifrost_core::analyzer::Language::Cpp,
9290        &qualified,
9291    );
9292    let (owner_text, member_name) = match parts.split_last() {
9293        Some((member, owner_parts)) if !owner_parts.is_empty() => {
9294            (owner_parts.join("::"), member.clone())
9295        }
9296        _ => {
9297            let scope = function.child_by_field_name("scope")?;
9298            let name = function.child_by_field_name("name")?;
9299            (
9300                node_text(scope, source).to_string(),
9301                node_text(name, source).to_string(),
9302            )
9303        }
9304    };
9305    let owner = visibility.resolve_type(file, &owner_text)?;
9306    let candidates = visibility
9307        .visible_members_for_owner_name(file, &owner, &member_name)
9308        .into_iter()
9309        .filter(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity))
9310        .cloned()
9311        .collect::<Vec<_>>();
9312    unanimous_return_binding(analyzer, visibility, file, &candidates)
9313}
9314
9315fn resolve_field_method_call_return_binding(
9316    analyzer: &CppGraphSource<'_>,
9317    visibility: &VisibilityIndex<'_>,
9318    file: &ProjectFile,
9319    source: &str,
9320    function: Node<'_>,
9321    arity: usize,
9322    receiver_resolver: Option<&ReceiverResolver<'_>>,
9323) -> Option<CppScanBinding> {
9324    debug_assert_eq!(
9325        function.kind(),
9326        "field_expression",
9327        "the member-call return binding answers only for a field-expression callee"
9328    );
9329    let receiver_resolver = receiver_resolver?;
9330    let field = function.child_by_field_name("field")?;
9331    let member_name = node_text(function_terminal_node(field), source);
9332    let receiver = function
9333        .child_by_field_name("argument")
9334        .or_else(|| function.named_child(0))?;
9335    let owners = receiver_resolver(receiver, source);
9336    let mut candidates = Vec::new();
9337    for owner in owners {
9338        let declaring_owner =
9339            match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
9340                EnclosingMemberOwnerResolution::Owner(owner) => owner,
9341                EnclosingMemberOwnerResolution::Missing => continue,
9342                EnclosingMemberOwnerResolution::Ambiguous => return None,
9343            };
9344        candidates.extend(
9345            visibility
9346                .visible_members_for_owner_name(file, &declaring_owner, member_name)
9347                .into_iter()
9348                .filter(|unit| {
9349                    unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity)
9350                })
9351                .cloned(),
9352        );
9353    }
9354    unanimous_return_binding(analyzer, visibility, file, &candidates)
9355}
9356
9357fn unanimous_return_binding(
9358    analyzer: &CppGraphSource<'_>,
9359    visibility: &VisibilityIndex<'_>,
9360    file: &ProjectFile,
9361    candidates: &[CodeUnit],
9362) -> Option<CppScanBinding> {
9363    let mut resolved_return: Option<CppScanBinding> = None;
9364    for function in candidates {
9365        let metadata = analyzer.signature_metadata(function);
9366        let return_types = if metadata.is_empty() {
9367            vec![cpp_function_return_type_text(analyzer, function)?]
9368        } else {
9369            metadata
9370                .iter()
9371                .map(|metadata| metadata.return_type_text().map(str::to_string))
9372                .collect::<Option<Vec<_>>>()?
9373        };
9374        for return_text in return_types {
9375            let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
9376            let name = normalize_cpp_type_name(&return_text);
9377            let binding = CppScanBinding::from_type_name(
9378                name.clone(),
9379                visibility
9380                    .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
9381                indirection,
9382            );
9383            if let Some(existing) = resolved_return.as_ref()
9384                && (existing.indirection != binding.indirection
9385                    || match (&existing.unit, &binding.unit) {
9386                        (Some(left), Some(right)) => !same_visible_symbol(left, right),
9387                        (None, None) => existing.type_name != binding.type_name,
9388                        (Some(_), None) | (None, Some(_)) => true,
9389                    })
9390            {
9391                return None;
9392            }
9393            resolved_return = Some(binding);
9394        }
9395    }
9396    resolved_return
9397}
9398
9399fn aliases_from_prepared_source(
9400    cpp: &dyn CppSource,
9401    token: QueryToken<'_>,
9402    file: &ProjectFile,
9403) -> Vec<CppAlias> {
9404    let Some(prepared) = cpp.prepared_syntax(token, file) else {
9405        return Vec::new();
9406    };
9407    let mut aliases = Vec::new();
9408    collect_cpp_aliases(prepared.tree().root_node(), prepared.source(), &mut aliases);
9409    aliases
9410}
9411
9412fn collect_cpp_aliases(root: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
9413    let mut stack = vec![root];
9414    while let Some(node) = stack.pop() {
9415        match node.kind() {
9416            "alias_declaration" if alias_has_visible_file_scope(node) => {
9417                if let Some(alias) = cpp_alias_from_alias_declaration(node, source) {
9418                    out.push(alias);
9419                }
9420            }
9421            "type_definition" if alias_has_visible_file_scope(node) => {
9422                collect_typedef_aliases(node, source, out)
9423            }
9424            _ => {}
9425        }
9426
9427        for index in (0..node.named_child_count()).rev() {
9428            if let Some(child) = node.named_child(index) {
9429                stack.push(child);
9430            }
9431        }
9432    }
9433}
9434
9435fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
9436    let mut current = node.parent();
9437    while let Some(parent) = current {
9438        match parent.kind() {
9439            "translation_unit"
9440            | "namespace_definition"
9441            | "declaration_list"
9442            | "linkage_specification" => current = parent.parent(),
9443            "template_declaration" => current = parent.parent(),
9444            _ => return false,
9445        }
9446    }
9447    true
9448}
9449
9450fn cpp_alias_from_alias_declaration(node: Node<'_>, source: &str) -> Option<CppAlias> {
9451    let name = node
9452        .child_by_field_name("name")
9453        .and_then(|node| normalize_reference_name(node_text(node, source)))?;
9454    let target = node
9455        .child_by_field_name("type")
9456        .and_then(|node| normalize_reference_name(node_text(node, source)))?;
9457    Some(CppAlias {
9458        name,
9459        target,
9460        namespace: enclosing_namespace_context(node, source),
9461    })
9462}
9463
9464fn collect_typedef_aliases(node: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
9465    let Some(type_node) = node.child_by_field_name("type") else {
9466        return;
9467    };
9468    let Some(target) = normalize_reference_name(node_text(type_node, source)) else {
9469        return;
9470    };
9471
9472    let mut cursor = node.walk();
9473    for child in node.named_children(&mut cursor) {
9474        if same_node(child, type_node) {
9475            continue;
9476        }
9477        if let Some(name) = extract_typedef_declarator_name(child, source) {
9478            out.push(CppAlias {
9479                name,
9480                target: target.clone(),
9481                namespace: enclosing_namespace_context(node, source),
9482            });
9483        }
9484    }
9485}
9486
9487fn extract_typedef_declarator_name(node: Node<'_>, source: &str) -> Option<String> {
9488    match node.kind() {
9489        "identifier" | "field_identifier" | "type_identifier" | "qualified_identifier" => {
9490            normalize_reference_name(node_text(node, source))
9491        }
9492        _ => node
9493            .child_by_field_name("declarator")
9494            .or_else(|| node.child_by_field_name("name"))
9495            .or_else(|| last_named_child(node))
9496            .and_then(|child| extract_typedef_declarator_name(child, source)),
9497    }
9498}
9499
9500fn last_named_child(node: Node<'_>) -> Option<Node<'_>> {
9501    let count = node.named_child_count();
9502    if count == 0 {
9503        None
9504    } else {
9505        node.named_child(count - 1)
9506    }
9507}
9508
9509pub fn collect_include_closure(
9510    analyzer: &CppGraphSource<'_>,
9511    include_targets: &IncludeTargetIndex,
9512    file: &ProjectFile,
9513    out: &mut HashSet<ProjectFile>,
9514    cancellation: Option<&CancellationToken>,
9515) {
9516    let mut stack = vec![file.clone()];
9517    while let Some(file) = stack.pop() {
9518        if cancellation.is_some_and(CancellationToken::is_cancelled) {
9519            break;
9520        }
9521        if !out.insert(file.clone()) {
9522            continue;
9523        }
9524        let imports = analyzer.import_statements(&file);
9525        for include in cpp_include_paths(&imports) {
9526            for target in resolve_include_targets_with_index(&file, &include, include_targets) {
9527                stack.push(target);
9528            }
9529        }
9530    }
9531}
9532
9533fn collect_visible_declarations(
9534    include_graph: &IncludeGraph,
9535    declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
9536    file: &ProjectFile,
9537    visited: &mut HashSet<ProjectFile>,
9538    out: &mut HashSet<CodeUnit>,
9539    cancellation: Option<&CancellationToken>,
9540) {
9541    let mut stack = vec![file.clone()];
9542    while let Some(file) = stack.pop() {
9543        if cancellation.is_some_and(CancellationToken::is_cancelled) {
9544            break;
9545        }
9546        if !visited.insert(file.clone()) {
9547            continue;
9548        }
9549        if let Some(declarations) = declarations_by_file.get(&file) {
9550            out.extend(declarations.iter().cloned());
9551        }
9552        stack.extend(include_graph.targets(&file).iter().cloned());
9553    }
9554}
9555
9556pub fn signature_arity(signature: Option<&str>) -> usize {
9557    let Some(signature) = signature else {
9558        return 0;
9559    };
9560    let inner = signature
9561        .find('(')
9562        .and_then(|open| {
9563            signature[open + 1..]
9564                .find(')')
9565                .map(|close| &signature[open + 1..open + 1 + close])
9566        })
9567        .unwrap_or(signature)
9568        .trim();
9569    if inner.is_empty() || inner == "void" {
9570        return 0;
9571    }
9572    cpp_split_top_level_commas(inner).count()
9573}
9574
9575fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
9576    let source = format!("void __bifrost_macro_parameters({replacement});");
9577    let mut parser = Parser::new();
9578    parser
9579        .set_language(&tree_sitter_cpp::LANGUAGE.into())
9580        .ok()?;
9581    let tree = parser.parse(&source, None)?;
9582    let root = tree.root_node();
9583    if root.has_error() {
9584        return None;
9585    }
9586    let declaration = root.named_child(0)?;
9587    let declarator = declaration.child_by_field_name("declarator")?;
9588    let parameters = declarator.child_by_field_name("parameters")?;
9589    let mut required = 0;
9590    let mut total = 0;
9591    let mut repeated = false;
9592    let mut cursor = parameters.walk();
9593    for parameter in parameters.children(&mut cursor) {
9594        match parameter.kind() {
9595            "parameter_declaration" => {
9596                if parameter.child_by_field_name("declarator").is_none()
9597                    && parameter
9598                        .child_by_field_name("type")
9599                        .is_some_and(|type_node| node_text(type_node, &source).trim() == "void")
9600                {
9601                    continue;
9602                }
9603                required += 1;
9604                total += 1;
9605            }
9606            "optional_parameter_declaration" => total += 1,
9607            "variadic_parameter" | "variadic_parameter_declaration" | "..." => {
9608                repeated = true;
9609            }
9610            _ => {}
9611        }
9612    }
9613    Some(CallableArity::new(required, total, repeated))
9614}
9615
9616pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> CallableArity {
9617    analyzer
9618        .signature_metadata(unit)
9619        .into_iter()
9620        .find_map(|metadata| metadata.callable_arity())
9621        .unwrap_or_else(|| CallableArity::exact(signature_arity(unit.signature())))
9622}
9623
9624pub fn cpp_callable_parameter_types(
9625    analyzer: &CppGraphSource<'_>,
9626    unit: &CodeUnit,
9627) -> Option<Vec<String>> {
9628    analyzer
9629        .signature_metadata(unit)
9630        .into_iter()
9631        .find_map(|metadata| metadata.callable_parameter_types().map(<[String]>::to_vec))
9632        .or_else(|| unit.signature().and_then(cpp_signature_param_types))
9633}
9634
9635fn merge_compatible_callable_arities(
9636    left: CallableArity,
9637    right: CallableArity,
9638) -> Option<CallableArity> {
9639    let total = left.total();
9640    let left_repeated = left.accepts(total.saturating_add(1));
9641    let right_repeated = right.accepts(right.total().saturating_add(1));
9642    if total != right.total() || left_repeated != right_repeated {
9643        return None;
9644    }
9645    let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
9646    Some(CallableArity::new(required, total, left_repeated))
9647}
9648
9649fn find_include_activation(
9650    cpp: &dyn CppSource,
9651    token: QueryToken<'_>,
9652    file: &ProjectFile,
9653    prepared: &PreparedSyntaxTree,
9654    donor_source: &ProjectFile,
9655) -> Option<usize> {
9656    let include_targets = cpp.include_target_index();
9657    let mut direct_includes = Vec::new();
9658    let mut nodes = vec![prepared.tree().root_node()];
9659    // An include activates for the whole file, so only an unconditional
9660    // directive counts here.
9661    let reference = CallableReferenceContext {
9662        file,
9663        position: None,
9664    };
9665    while let Some(node) = nodes.pop() {
9666        if node.kind() == "preproc_include" {
9667            if callable_preprocessor_context_is_visible_for_reference(
9668                node,
9669                prepared.source(),
9670                &reference,
9671            ) {
9672                let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
9673                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
9674                    if let Some(target) = unique_include_target(resolve_include_targets_with_index(
9675                        file,
9676                        &include,
9677                        include_targets,
9678                    )) {
9679                        direct_includes.push((node.end_byte(), target));
9680                    }
9681                }
9682            }
9683            continue;
9684        }
9685        for index in (0..node.named_child_count()).rev() {
9686            if let Some(child) = node.named_child(index) {
9687                nodes.push(child);
9688            }
9689        }
9690    }
9691    direct_includes.sort_by_key(|(activation, _)| *activation);
9692    let mut known_missing = HashSet::default();
9693    direct_includes
9694        .into_iter()
9695        .find(|(_, direct)| {
9696            unconditional_include_reaches(
9697                cpp,
9698                token,
9699                include_targets,
9700                direct,
9701                donor_source,
9702                file,
9703                &mut known_missing,
9704            )
9705        })
9706        .map(|(activation, _)| activation)
9707}
9708
9709fn find_conditional_include_projection_index(
9710    cpp: &dyn CppSource,
9711    token: QueryToken<'_>,
9712    file: &ProjectFile,
9713    prepared: &PreparedSyntaxTree,
9714    on_state: &dyn Fn(),
9715) -> ConditionalIncludeProjectionIndex {
9716    let include_targets = cpp.include_target_index();
9717    let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
9718        HashMap::default();
9719    let mut pending = Vec::new();
9720    let mut nodes = vec![prepared.tree().root_node()];
9721    while let Some(node) = nodes.pop() {
9722        if node.kind() == "preproc_include" {
9723            let Some(required_guards) = preprocessor_guard_environment(node, prepared.source())
9724            else {
9725                continue;
9726            };
9727            let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
9728            for include in cpp_include_paths(std::slice::from_ref(&raw)) {
9729                let Some(target) = unique_include_target(resolve_include_targets_with_index(
9730                    file,
9731                    &include,
9732                    include_targets,
9733                )) else {
9734                    continue;
9735                };
9736                pending.push((target, node.end_byte(), required_guards.clone()));
9737            }
9738            continue;
9739        }
9740        for index in (0..node.named_child_count()).rev() {
9741            if let Some(child) = node.named_child(index) {
9742                nodes.push(child);
9743            }
9744        }
9745    }
9746
9747    // One reached file can have several distinct compatible guard paths. Each
9748    // (file, activation byte) key keeps only the inclusion-minimal guard sets:
9749    // the consumers ask existence questions whose answers are monotone in the
9750    // guard set -- a path whose requirements hold, stay stable, and stay
9751    // compatible under one environment does so under every subset as well --
9752    // so a state subsumed by an existing subset cannot witness anything its
9753    // subset does not, and inserting a smaller set evicts the supersets it
9754    // subsumes. Exact-set dedup still terminated cycles, but dense `#ifdef`
9755    // lattices (QMK's per-keyboard feature guards) enumerated the powerset of
9756    // path-union guard sets through it: the state space, the per-key linear
9757    // scans, and resident memory all grew without bound (#2365).
9758    let mut expanded: HashMap<(ProjectFile, usize), Vec<HashSet<PreprocessorGuard>>> =
9759        HashMap::default();
9760    while let Some((current_file, activation_byte, required_guards)) = pending.pop() {
9761        let guard_sets = expanded
9762            .entry((current_file.clone(), activation_byte))
9763            .or_default();
9764        if guard_sets
9765            .iter()
9766            .any(|existing| existing.is_subset(&required_guards))
9767        {
9768            continue;
9769        }
9770        let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
9771            .drain(..)
9772            .partition(|existing| required_guards.is_subset(existing));
9773        *guard_sets = kept;
9774        guard_sets.push(required_guards.clone());
9775        if !evicted.is_empty()
9776            && let Some(projections) = projections_by_source.get_mut(&current_file)
9777        {
9778            projections.retain(|projection| {
9779                projection.activation_byte != activation_byte
9780                    || !evicted.contains(&projection.required_guards)
9781            });
9782        }
9783        on_state();
9784
9785        // A fresh minimal set has no equal in the store: equality would have
9786        // been caught by the subset check above.
9787        projections_by_source
9788            .entry(current_file.clone())
9789            .or_default()
9790            .push(ConditionalIncludeProjection {
9791                activation_byte,
9792                required_guards: required_guards.clone(),
9793            });
9794
9795        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
9796            continue;
9797        };
9798        let mut nodes = vec![current_prepared.tree().root_node()];
9799        while let Some(node) = nodes.pop() {
9800            if node.kind() == "preproc_include" {
9801                let Some(include_guards) =
9802                    preprocessor_guard_environment(node, current_prepared.source())
9803                else {
9804                    continue;
9805                };
9806                let Some(path_guards) =
9807                    merge_preprocessor_guards(&required_guards, &include_guards)
9808                else {
9809                    continue;
9810                };
9811                let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
9812                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
9813                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
9814                        &current_file,
9815                        &include,
9816                        include_targets,
9817                    )) else {
9818                        continue;
9819                    };
9820                    pending.push((target, activation_byte, path_guards.clone()));
9821                }
9822                continue;
9823            }
9824            for index in (0..node.named_child_count()).rev() {
9825                if let Some(child) = node.named_child(index) {
9826                    nodes.push(child);
9827                }
9828            }
9829        }
9830    }
9831
9832    projections_by_source
9833        .into_iter()
9834        .map(|(source, mut projections)| {
9835            projections.sort_by_key(|projection| projection.activation_byte);
9836            (source, Arc::from(projections))
9837        })
9838        .collect()
9839}
9840
9841/// Decide one conditional include target without materializing every source
9842/// reached by every guard combination. Paths whose requirements do not hold
9843/// at the reference cannot become feasible after adding nested include guards,
9844/// so discard them before expanding the next header.
9845#[allow(clippy::too_many_arguments)]
9846fn find_conditional_include_projection_for_source(
9847    cpp: &dyn CppSource,
9848    token: QueryToken<'_>,
9849    file: &ProjectFile,
9850    prepared: &PreparedSyntaxTree,
9851    donor_source: &ProjectFile,
9852    reference_guards: Option<&HashSet<PreprocessorGuard>>,
9853    reference_byte: usize,
9854    on_state: &dyn Fn(),
9855) -> bool {
9856    let Some(reference_guards) = reference_guards else {
9857        return false;
9858    };
9859    let include_targets = cpp.include_target_index();
9860    let mut pending = Vec::new();
9861    let mut nodes = vec![prepared.tree().root_node()];
9862    while let Some(node) = nodes.pop() {
9863        if node.kind() == "preproc_include" {
9864            let Some(required_guards) = preprocessor_guard_environment(node, prepared.source())
9865            else {
9866                continue;
9867            };
9868            if node.end_byte() > reference_byte
9869                || !guard_requirements_hold_at_reference(&required_guards, Some(reference_guards))
9870            {
9871                continue;
9872            }
9873            let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
9874            for include in cpp_include_paths(std::slice::from_ref(&raw)) {
9875                let Some(target) = unique_include_target(resolve_include_targets_with_index(
9876                    file,
9877                    &include,
9878                    include_targets,
9879                )) else {
9880                    continue;
9881                };
9882                if &target == donor_source {
9883                    return true;
9884                }
9885                pending.push((target, required_guards.clone()));
9886            }
9887            continue;
9888        }
9889        for index in (0..node.named_child_count()).rev() {
9890            if let Some(child) = node.named_child(index) {
9891                nodes.push(child);
9892            }
9893        }
9894    }
9895
9896    let mut expanded: HashMap<ProjectFile, Vec<HashSet<PreprocessorGuard>>> = HashMap::default();
9897    while let Some((current_file, required_guards)) = pending.pop() {
9898        let guard_sets = expanded.entry(current_file.clone()).or_default();
9899        if guard_sets.contains(&required_guards) {
9900            continue;
9901        }
9902        guard_sets.push(required_guards.clone());
9903        on_state();
9904
9905        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
9906            continue;
9907        };
9908        let mut nodes = vec![current_prepared.tree().root_node()];
9909        while let Some(node) = nodes.pop() {
9910            if node.kind() == "preproc_include" {
9911                let Some(include_guards) =
9912                    preprocessor_guard_environment(node, current_prepared.source())
9913                else {
9914                    continue;
9915                };
9916                let Some(path_guards) =
9917                    merge_preprocessor_guards(&required_guards, &include_guards)
9918                else {
9919                    continue;
9920                };
9921                if !guard_requirements_hold_at_reference(&path_guards, Some(reference_guards)) {
9922                    continue;
9923                }
9924                let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
9925                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
9926                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
9927                        &current_file,
9928                        &include,
9929                        include_targets,
9930                    )) else {
9931                        continue;
9932                    };
9933                    if &target == donor_source {
9934                        return true;
9935                    }
9936                    pending.push((target, path_guards.clone()));
9937                }
9938                continue;
9939            }
9940            for index in (0..node.named_child_count()).rev() {
9941                if let Some(child) = node.named_child(index) {
9942                    nodes.push(child);
9943                }
9944            }
9945        }
9946    }
9947    false
9948}
9949
9950/// Whether `translation_unit`'s unconditional `#include` closure reaches
9951/// `header`, directly or through any chain of headers.
9952///
9953/// The include-closure question asked on its own, for
9954/// [`crate::identity::cpp_header_body_files_are_related`]. The walk resolves
9955/// each include the way visibility does -- to a unique target or to nothing --
9956/// so a duplicated basename relates nothing, and it is memoized per file pair
9957/// on the analyzer.
9958///
9959/// The reference position is `translation_unit` itself: the question is
9960/// whether that unit compiles the header, so that unit's own dialect and
9961/// preprocessor context govern the walk.
9962pub fn cpp_include_closure_reaches(
9963    cpp: &dyn CppSource,
9964    token: QueryToken<'_>,
9965    translation_unit: &ProjectFile,
9966    header: &ProjectFile,
9967) -> bool {
9968    unconditional_include_reaches(
9969        cpp,
9970        token,
9971        cpp.include_target_index(),
9972        translation_unit,
9973        header,
9974        translation_unit,
9975        &mut HashSet::default(),
9976    )
9977}
9978
9979fn unconditional_include_reaches(
9980    cpp: &dyn CppSource,
9981    token: QueryToken<'_>,
9982    include_targets: &IncludeTargetIndex,
9983    first: &ProjectFile,
9984    donor_source: &ProjectFile,
9985    reference_file: &ProjectFile,
9986    known_missing: &mut HashSet<ProjectFile>,
9987) -> bool {
9988    if first == donor_source {
9989        return true;
9990    }
9991    if known_missing.contains(first) {
9992        return false;
9993    }
9994    let reference_is_c = reference_file
9995        .rel_path()
9996        .extension()
9997        .and_then(|extension| extension.to_str())
9998        == Some("c");
9999    if let Some(reaches) =
10000        cpp.cached_unconditional_include_reachability(first, donor_source, reference_is_c)
10001    {
10002        return reaches;
10003    }
10004    let mut visited = HashSet::default();
10005    let mut files = vec![first.clone()];
10006    // Only an unconditional directive extends the include reach, so the walk
10007    // asks the question without a reference position.
10008    let reference = CallableReferenceContext {
10009        file: reference_file,
10010        position: None,
10011    };
10012    while let Some(file) = files.pop() {
10013        if file == *donor_source {
10014            cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, true);
10015            return true;
10016        }
10017        if known_missing.contains(&file) || !visited.insert(file.clone()) {
10018            continue;
10019        }
10020        let Some(prepared) = cpp.prepared_syntax(token, &file) else {
10021            continue;
10022        };
10023        let mut nodes = vec![prepared.tree().root_node()];
10024        while let Some(node) = nodes.pop() {
10025            if node.kind() == "preproc_include" {
10026                if callable_preprocessor_context_is_visible_for_reference(
10027                    node,
10028                    prepared.source(),
10029                    &reference,
10030                ) {
10031                    let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
10032                    for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10033                        if let Some(target) = unique_include_target(
10034                            resolve_include_targets_with_index(&file, &include, include_targets),
10035                        ) {
10036                            files.push(target);
10037                        }
10038                    }
10039                }
10040                continue;
10041            }
10042            for index in (0..node.named_child_count()).rev() {
10043                if let Some(child) = node.named_child(index) {
10044                    nodes.push(child);
10045                }
10046            }
10047        }
10048    }
10049    known_missing.extend(visited);
10050    cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, false);
10051    false
10052}
10053
10054fn declaration_guard_requirements(
10055    analyzer: &CppGraphSource<'_>,
10056    cpp: &dyn CppSource,
10057    candidate: &CodeUnit,
10058) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
10059    let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
10060        return Vec::new();
10061    };
10062    let root = prepared.tree().root_node();
10063    analyzer
10064        .ranges(candidate)
10065        .into_iter()
10066        .filter_map(|range| {
10067            root.descendant_for_byte_range(range.start_byte, range.end_byte)
10068                .and_then(|node| preprocessor_guard_environment(node, prepared.source()))
10069                // A class name is injected into its own body at the declaration's
10070                // introduction point, not after the complete class range. Using
10071                // the start also preserves normal before/after ordering for aliases.
10072                .map(|required| (range.start_byte, required))
10073        })
10074        .collect()
10075}
10076
10077fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
10078    analyzer
10079        .ranges(candidate)
10080        .into_iter()
10081        .map(|range| range.start_byte)
10082        .min()
10083}
10084
10085/// The macro names every configuration in `contexts` defines -- the fact set
10086/// one file's compile-database coverage proves (#2011). `None` when the
10087/// database has no entry for the file, which is different from an empty
10088/// intersection: no entry means no coverage, while an empty intersection is
10089/// covered-and-proves-nothing.
10090fn context_fact_names(contexts: &[CppCompileContext]) -> Option<HashSet<String>> {
10091    let (first, rest) = contexts.split_first()?;
10092    Some(
10093        first
10094            .defined_macros
10095            .iter()
10096            .filter(|name| {
10097                rest.iter()
10098                    .all(|context| context.defined_macros.contains(*name))
10099            })
10100            .cloned()
10101            .collect(),
10102    )
10103}
10104
10105pub fn guard_requirements_hold_at_reference(
10106    required: &HashSet<PreprocessorGuard>,
10107    reference: Option<&HashSet<PreprocessorGuard>>,
10108) -> bool {
10109    reference.is_some_and(|active| {
10110        required
10111            .iter()
10112            .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
10113    })
10114}
10115
10116fn preprocessor_guard_holds_at_reference(
10117    required: &PreprocessorGuard,
10118    active: &HashSet<PreprocessorGuard>,
10119) -> bool {
10120    if active.contains(required) {
10121        return true;
10122    }
10123    let active_expression = BooleanGuardExpression::all(
10124        active
10125            .iter()
10126            .filter_map(PreprocessorGuard::as_boolean_expression),
10127    );
10128    required
10129        .as_boolean_expression()
10130        .is_some_and(|required| active_expression.implies(&required))
10131}
10132
10133/// Cross-file guard rule: two guard sets are compatible when neither one
10134/// contradicts the other. Use this instead of the subset test whenever the
10135/// guards come from a foreign file, which resolves its own conditionals
10136/// independently of the reference.
10137fn guards_compatible_at_reference(
10138    declaration: &HashSet<PreprocessorGuard>,
10139    reference: Option<&HashSet<PreprocessorGuard>>,
10140) -> bool {
10141    reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
10142}
10143
10144/// The byte range of the `#if`/`#elif`/`#else` chain that encloses the smallest
10145/// node covering `[start_byte, end_byte)`, or `None` when nothing there is
10146/// conditional.
10147///
10148/// Two declarations of one name that report the same chain stand in different
10149/// branches of it, so at most one of them is compiled in any configuration.
10150/// They are alternate spellings of a single declaration, not competing
10151/// declarations, and navigation must not present them as an ambiguity.
10152pub fn preprocessor_conditional_family_range(
10153    root: Node<'_>,
10154    start_byte: usize,
10155    end_byte: usize,
10156) -> Option<(usize, usize)> {
10157    let node = root.descendant_for_byte_range(start_byte, end_byte)?;
10158    let mut ancestor = Some(node);
10159    while let Some(current) = ancestor {
10160        if is_preprocessor_conditional(current)
10161            && preprocessor_conditional_contains_descendant(current, node)
10162        {
10163            let family = preprocessor_conditional_family_root(current);
10164            return Some((family.start_byte(), family.end_byte()));
10165        }
10166        ancestor = current.parent();
10167    }
10168    None
10169}
10170
10171fn preprocessor_conditional_family_for_declaration(node: Node<'_>) -> Option<Node<'_>> {
10172    let mut ancestor = node.parent();
10173    while let Some(current) = ancestor {
10174        if is_preprocessor_conditional(current)
10175            && preprocessor_conditional_contains_descendant(current, node)
10176        {
10177            let family = preprocessor_conditional_family_root(current);
10178            if preprocessor_conditional_family_has_terminal_else(family) {
10179                return Some(family);
10180            }
10181        }
10182        ancestor = current.parent();
10183    }
10184    None
10185}
10186
10187fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
10188    while let Some(parent) = conditional.parent() {
10189        let is_alternative = parent
10190            .child_by_field_name("alternative")
10191            .is_some_and(|alternative| {
10192                alternative.start_byte() == conditional.start_byte()
10193                    && alternative.end_byte() == conditional.end_byte()
10194            });
10195        if !is_alternative {
10196            break;
10197        }
10198        conditional = parent;
10199    }
10200    conditional
10201}
10202
10203fn preprocessor_conditional_family_has_terminal_else(mut conditional: Node<'_>) -> bool {
10204    loop {
10205        let Some(alternative) = conditional.child_by_field_name("alternative") else {
10206            return false;
10207        };
10208        match alternative.kind() {
10209            "preproc_else" => return true,
10210            "preproc_elif" => conditional = alternative,
10211            _ => return false,
10212        }
10213    }
10214}
10215
10216pub fn preprocessor_guard_environment(
10217    node: Node<'_>,
10218    source: &str,
10219) -> Option<HashSet<PreprocessorGuard>> {
10220    let mut guards = HashSet::default();
10221    let mut ancestor = node.parent();
10222    while let Some(conditional) = ancestor {
10223        if matches!(
10224            conditional.kind(),
10225            "preproc_if" | "preproc_ifdef" | "preproc_elif"
10226        ) && !is_file_covering_include_guard(conditional, source)
10227            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
10228            && preprocessor_conditional_contains_descendant(conditional, node)
10229        {
10230            let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
10231            match guard {
10232                PreprocessorGuard::Constant(true) => {
10233                    ancestor = conditional.parent();
10234                    continue;
10235                }
10236                PreprocessorGuard::Constant(false) => return None,
10237                _ => {}
10238            }
10239            if guards.contains(&guard.negated()) {
10240                return None;
10241            }
10242            guards.insert(guard);
10243        }
10244        ancestor = conditional.parent();
10245    }
10246    if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
10247        match guard {
10248            PreprocessorGuard::Constant(true) => {}
10249            PreprocessorGuard::Constant(false) => return None,
10250            _ => {
10251                if guards.contains(&guard.negated()) {
10252                    return None;
10253                }
10254                guards.insert(guard);
10255            }
10256        }
10257    }
10258    Some(guards)
10259}
10260
10261fn fragmented_statement_preprocessor_guard(
10262    descendant: Node<'_>,
10263    source: &str,
10264) -> Option<PreprocessorGuard> {
10265    // A conditional that starts before `} else if (...) {` crosses the
10266    // enclosing statement's grammar boundary. tree-sitter leaves its opener
10267    // as a `preproc_if` with a missing terminator in the consequence and
10268    // reparses the real `#endif` as a `preproc_call` in the alternative. Pair
10269    // those structured nodes before restoring the guard to intervening uses.
10270    let mut ancestor = descendant.parent();
10271    while let Some(statement) = ancestor {
10272        if statement.kind() == "if_statement"
10273            && let (Some(consequence), Some(alternative)) = (
10274                statement.child_by_field_name("consequence"),
10275                statement.child_by_field_name("alternative"),
10276            )
10277            && alternative.start_byte() <= descendant.start_byte()
10278            && descendant.end_byte() <= alternative.end_byte()
10279        {
10280            let mut cursor = consequence.walk();
10281            let openers = consequence
10282                .named_children(&mut cursor)
10283                .filter(|child| {
10284                    matches!(child.kind(), "preproc_if" | "preproc_ifdef")
10285                        && child
10286                            .child(child.child_count().saturating_sub(1))
10287                            .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
10288                })
10289                .collect::<Vec<_>>();
10290            if openers.len() != 1 {
10291                ancestor = statement.parent();
10292                continue;
10293            }
10294
10295            let mut terminators = Vec::new();
10296            let mut stack = vec![alternative];
10297            while let Some(node) = stack.pop() {
10298                if node.kind() == "preproc_call"
10299                    && node.start_byte() >= descendant.end_byte()
10300                    && node
10301                        .child_by_field_name("directive")
10302                        .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
10303                {
10304                    terminators.push(node);
10305                    continue;
10306                }
10307                for index in (0..node.named_child_count()).rev() {
10308                    if let Some(child) = node.named_child(index) {
10309                        stack.push(child);
10310                    }
10311                }
10312            }
10313            if terminators.len() == 1 {
10314                return simple_preprocessor_guard(openers[0], source);
10315            }
10316        }
10317        ancestor = statement.parent();
10318    }
10319    None
10320}
10321
10322fn preprocessor_guard_for_descendant(
10323    conditional: Node<'_>,
10324    descendant: Node<'_>,
10325    source: &str,
10326) -> Option<PreprocessorGuard> {
10327    let mut guard = simple_preprocessor_guard(conditional, source)?;
10328    if conditional
10329        .child_by_field_name("alternative")
10330        .is_some_and(|alternative| {
10331            alternative.start_byte() <= descendant.start_byte()
10332                && descendant.end_byte() <= alternative.end_byte()
10333        })
10334    {
10335        let alternative = conditional.child_by_field_name("alternative")?;
10336        // Tree-sitter nests an `#elif` chain in each `alternative` field. A
10337        // descendant in any later branch must first exclude the parent branch,
10338        // then collect the nested `preproc_elif` guard from its own ancestor.
10339        if !matches!(alternative.kind(), "preproc_else" | "preproc_elif") {
10340            return None;
10341        }
10342        guard = guard.negated();
10343    }
10344    Some(guard)
10345}
10346
10347fn preprocessor_conditional_contains_descendant(
10348    conditional: Node<'_>,
10349    descendant: Node<'_>,
10350) -> bool {
10351    cpp_displaced_preprocessor_boundary(conditional)
10352        .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
10353}
10354
10355pub fn merge_preprocessor_guards(
10356    left: &HashSet<PreprocessorGuard>,
10357    right: &HashSet<PreprocessorGuard>,
10358) -> Option<HashSet<PreprocessorGuard>> {
10359    let mut merged = left.clone();
10360    for guard in right {
10361        let boolean_negation = guard
10362            .as_boolean_expression()
10363            .map(|expression| expression.negated());
10364        if merged.contains(&guard.negated())
10365            || boolean_negation.is_some_and(|negated| {
10366                merged
10367                    .iter()
10368                    .filter_map(PreprocessorGuard::as_boolean_expression)
10369                    .any(|existing| existing == negated)
10370            })
10371        {
10372            return None;
10373        }
10374        merged.insert(guard.clone());
10375    }
10376    Some(merged)
10377}
10378
10379fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
10380    if conditional.kind() == "preproc_ifdef" {
10381        let name = conditional.child_by_field_name("name")?;
10382        let name = node_text(name, source).to_string();
10383        return match conditional.child(0)?.kind() {
10384            "#ifdef" => Some(PreprocessorGuard::Defined(name)),
10385            "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
10386            _ => None,
10387        };
10388    }
10389    let condition = conditional.child_by_field_name("condition")?;
10390    simple_preprocessor_expression_guard(condition, source).or_else(|| {
10391        Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
10392            node_text(condition, source),
10393        )))
10394    })
10395}
10396
10397fn simple_preprocessor_expression_guard(
10398    expression: Node<'_>,
10399    source: &str,
10400) -> Option<PreprocessorGuard> {
10401    match expression.kind() {
10402        "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
10403            node_text(expression, source).to_string(),
10404        ))),
10405        "number_literal" => match node_text(expression, source).trim() {
10406            "0" => Some(PreprocessorGuard::Constant(false)),
10407            "1" => Some(PreprocessorGuard::Constant(true)),
10408            _ => None,
10409        },
10410        "preproc_defined" => {
10411            let identifier = (0..expression.named_child_count())
10412                .filter_map(|index| expression.named_child(index))
10413                .find(|child| child.kind() == "identifier")?;
10414            Some(PreprocessorGuard::Defined(
10415                node_text(identifier, source).to_string(),
10416            ))
10417        }
10418        "unary_expression"
10419            if expression
10420                .child_by_field_name("operator")
10421                .is_some_and(|operator| operator.kind() == "!") =>
10422        {
10423            simple_preprocessor_expression_guard(
10424                expression.child_by_field_name("argument")?,
10425                source,
10426            )
10427            .map(|guard| guard.negated())
10428        }
10429        "parenthesized_expression" => (0..expression.named_child_count())
10430            .filter_map(|index| expression.named_child(index))
10431            .next()
10432            .and_then(|child| simple_preprocessor_expression_guard(child, source)),
10433        "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
10434            expression, source,
10435        ))),
10436        _ => None,
10437    }
10438}
10439
10440fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
10441    match expression.kind() {
10442        "number_literal" => match node_text(expression, source).trim() {
10443            "0" => BooleanGuardExpression::Constant(false),
10444            "1" => BooleanGuardExpression::Constant(true),
10445            _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
10446                expression, source,
10447            ))),
10448        },
10449        "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
10450        "preproc_defined" => {
10451            let identifier = (0..expression.named_child_count())
10452                .filter_map(|index| expression.named_child(index))
10453                .find(|child| child.kind() == "identifier");
10454            identifier.map_or_else(
10455                || {
10456                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
10457                        expression, source,
10458                    )))
10459                },
10460                |identifier| {
10461                    BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
10462                },
10463            )
10464        }
10465        "unary_expression"
10466            if expression
10467                .child_by_field_name("operator")
10468                .is_some_and(|operator| operator.kind() == "!") =>
10469        {
10470            expression.child_by_field_name("argument").map_or_else(
10471                || {
10472                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
10473                        expression, source,
10474                    )))
10475                },
10476                |argument| boolean_preprocessor_expression(argument, source).negated(),
10477            )
10478        }
10479        "parenthesized_expression" => (0..expression.named_child_count())
10480            .filter_map(|index| expression.named_child(index))
10481            .next()
10482            .map_or_else(
10483                || {
10484                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
10485                        expression, source,
10486                    )))
10487                },
10488                |child| boolean_preprocessor_expression(child, source),
10489            ),
10490        "binary_expression" => {
10491            let operands = || {
10492                Some((
10493                    boolean_preprocessor_expression(
10494                        expression.child_by_field_name("left")?,
10495                        source,
10496                    ),
10497                    boolean_preprocessor_expression(
10498                        expression.child_by_field_name("right")?,
10499                        source,
10500                    ),
10501                ))
10502            };
10503            match expression
10504                .child_by_field_name("operator")
10505                .map(|operator| operator.kind())
10506            {
10507                Some("&&") => operands().map_or_else(
10508                    || {
10509                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
10510                            expression, source,
10511                        )))
10512                    },
10513                    |(left, right)| BooleanGuardExpression::all([left, right]),
10514                ),
10515                Some("||") => operands().map_or_else(
10516                    || {
10517                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
10518                            expression, source,
10519                        )))
10520                    },
10521                    |(left, right)| BooleanGuardExpression::any([left, right]),
10522                ),
10523                _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
10524                    expression, source,
10525                ))),
10526            }
10527        }
10528        _ => {
10529            BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
10530        }
10531    }
10532}
10533
10534fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
10535    if targets.len() == 1 {
10536        targets.pop()
10537    } else {
10538        None
10539    }
10540}
10541
10542/// The declaration nodes of `candidate` in `prepared` that stand at a scope a
10543/// later reference can name.
10544///
10545/// A declaration inside a real function body, lambda, or nested block is block
10546/// local and is dropped. A declaration inside a parser-recovery wrapper that
10547/// merely looks callable -- an export macro between `class` and its name, or a
10548/// namespace-opening macro token before `namespace x {` -- keeps class or
10549/// namespace scope and is kept.
10550fn nameable_callable_declaration_nodes<'tree>(
10551    analyzer: &CppGraphSource<'_>,
10552    prepared: &'tree PreparedSyntaxTree,
10553    candidate: &CodeUnit,
10554) -> Vec<Node<'tree>> {
10555    let root = prepared.tree().root_node();
10556    analyzer
10557        .ranges(candidate)
10558        .into_iter()
10559        .filter_map(|range| {
10560            let mut declaration =
10561                root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
10562            // A declaration an attribute-like macro invocation swallowed lives
10563            // inside the `ERROR` the parser left, not inside a `declaration`
10564            // node, so that envelope is where the climb stops (#2552).
10565            while !matches!(
10566                declaration.kind(),
10567                "declaration" | "field_declaration" | "function_definition"
10568            ) && !crate::declarations::is_macro_wrapped_declaration_envelope(
10569                declaration,
10570                prepared.source(),
10571            ) {
10572                declaration = declaration.parent()?;
10573            }
10574            let mut ancestor = declaration.parent();
10575            while let Some(node) = ancestor {
10576                if node.kind() == "function_definition"
10577                    && is_recovered_declaration_scope_container(node, prepared.source())
10578                {
10579                    ancestor = node.parent();
10580                    continue;
10581                }
10582                if node.kind() == "compound_statement"
10583                    && node.parent().is_some_and(|parent| {
10584                        is_recovered_declaration_scope_container(parent, prepared.source())
10585                    })
10586                {
10587                    ancestor = node.parent().and_then(|parent| parent.parent());
10588                    continue;
10589                }
10590                if matches!(
10591                    node.kind(),
10592                    "compound_statement" | "function_definition" | "lambda_expression"
10593                ) {
10594                    return None;
10595                }
10596                ancestor = node.parent();
10597            }
10598            Some(declaration)
10599        })
10600        .collect()
10601}
10602
10603fn callable_declaration_activation_in_file(
10604    analyzer: &CppGraphSource<'_>,
10605    prepared: &PreparedSyntaxTree,
10606    candidate: &CodeUnit,
10607    reference: &CallableReferenceContext<'_>,
10608) -> Option<usize> {
10609    nameable_callable_declaration_nodes(analyzer, prepared, candidate)
10610        .into_iter()
10611        .filter(|declaration| {
10612            callable_preprocessor_context_is_visible_for_reference(
10613                *declaration,
10614                prepared.source(),
10615                reference,
10616            )
10617        })
10618        .map(callable_declaration_activation_byte)
10619        .min()
10620}
10621
10622/// C and C++ activate a declared name at the end of its declarator, not at the
10623/// end of the whole declaration. A function definition ends at the closing
10624/// brace of its body, so the declaration end byte would hide the function from
10625/// its own body and make self recursion unresolvable without a prototype.
10626fn callable_declaration_activation_byte(declaration: Node<'_>) -> usize {
10627    if declaration.kind() != "function_definition" {
10628        return declaration.end_byte();
10629    }
10630    declaration
10631        .child_by_field_name("declarator")
10632        .map_or(declaration.end_byte(), |declarator| declarator.end_byte())
10633}
10634
10635/// The reference side of a callable visibility question.
10636///
10637/// An include-graph walk and a whole-file arity activation ask the question
10638/// without one reference position, so they carry no `position` and therefore no
10639/// guard environment.
10640struct CallableReferenceContext<'a> {
10641    file: &'a ProjectFile,
10642    position: Option<CallableReferencePosition<'a>>,
10643}
10644
10645/// One reference position plus its preprocessor guard environment. The
10646/// environment is computed on demand because most declarations carry no
10647/// non-trivial guard.
10648struct CallableReferencePosition<'a> {
10649    prepared: &'a PreparedSyntaxTree,
10650    byte: usize,
10651    guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
10652}
10653
10654impl CallableReferenceContext<'_> {
10655    fn is_c(&self) -> bool {
10656        self.file
10657            .rel_path()
10658            .extension()
10659            .and_then(|extension| extension.to_str())
10660            == Some("c")
10661    }
10662
10663    fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
10664        let position = self.position.as_ref()?;
10665        position
10666            .guards
10667            .get_or_init(|| {
10668                position
10669                    .prepared
10670                    .tree()
10671                    .root_node()
10672                    .descendant_for_byte_range(position.byte, position.byte.saturating_add(1))
10673                    .and_then(|node| {
10674                        preprocessor_guard_environment(node, position.prepared.source())
10675                    })
10676            })
10677            .as_ref()
10678    }
10679}
10680
10681fn callable_preprocessor_context_is_visible_for_reference(
10682    node: Node<'_>,
10683    source: &str,
10684    reference: &CallableReferenceContext<'_>,
10685) -> bool {
10686    let reference_is_c = reference.is_c();
10687    let mut ancestor = node.parent();
10688    while let Some(conditional) = ancestor {
10689        if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
10690            && !is_file_covering_include_guard(conditional, source)
10691            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
10692            && preprocessor_conditional_contains_descendant(conditional, node)
10693        {
10694            let Some(guard) = preprocessor_guard_for_descendant(conditional, node, source) else {
10695                return false;
10696            };
10697            match guard {
10698                PreprocessorGuard::Constant(true) => {}
10699                PreprocessorGuard::Constant(false) => return false,
10700                PreprocessorGuard::Defined(name) if name == "__cplusplus" => {
10701                    if reference_is_c {
10702                        return false;
10703                    }
10704                }
10705                PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
10706                    if !reference_is_c {
10707                        return false;
10708                    }
10709                }
10710                // The declaration stands under a guard whose value this
10711                // analyzer cannot decide. It is still co-active with a
10712                // reference whose active guards imply it. Collecting one guard
10713                // per ancestor makes the whole walk a conjunction of the
10714                // declaration requirements.
10715                guard => {
10716                    if !reference
10717                        .guards()
10718                        .is_some_and(|active| preprocessor_guard_holds_at_reference(&guard, active))
10719                    {
10720                        return false;
10721                    }
10722                }
10723            }
10724        }
10725        ancestor = conditional.parent();
10726    }
10727    true
10728}
10729
10730fn flattened_macro_namespace_declaration_matches(
10731    analyzer: &CppGraphSource<'_>,
10732    cpp: &dyn CppSource,
10733    reference_file: &ProjectFile,
10734    visible_declaration: &CodeUnit,
10735    qualified_candidate: &CodeUnit,
10736    reference_byte: usize,
10737) -> bool {
10738    // Namespace-opening macros can leave tree-sitter unable to retain the
10739    // namespace owner after a later recovery point. In that shape the forward
10740    // declaration is indexed at translation-unit scope, while the definition
10741    // still has its qualified owner. Require all surviving structural evidence
10742    // before treating the declaration as activation for that definition.
10743    if visible_declaration.kind() != qualified_candidate.kind()
10744        || visible_declaration.identifier() != qualified_candidate.identifier()
10745        || visible_declaration.signature() != qualified_candidate.signature()
10746        || !visible_declaration.package_name().is_empty()
10747        || qualified_candidate.package_name().is_empty()
10748    {
10749        return false;
10750    }
10751
10752    let Some(prepared) = cpp.prepared_syntax(analyzer.token, visible_declaration.source()) else {
10753        return false;
10754    };
10755    let root = prepared.tree().root_node();
10756    let closing_brace_limit = if visible_declaration.source() == reference_file {
10757        reference_byte
10758    } else {
10759        usize::MAX
10760    };
10761
10762    analyzer
10763        .ranges(visible_declaration)
10764        .into_iter()
10765        .any(|range| {
10766            let Some(mut declaration) =
10767                root.descendant_for_byte_range(range.start_byte, range.end_byte)
10768            else {
10769                return false;
10770            };
10771            while !matches!(
10772                declaration.kind(),
10773                "declaration" | "field_declaration" | "function_definition"
10774            ) {
10775                let Some(parent) = declaration.parent() else {
10776                    return false;
10777                };
10778                declaration = parent;
10779            }
10780            if declaration
10781                .parent()
10782                .is_none_or(|parent| parent.kind() != "translation_unit")
10783                || !macro_displaced_cpp_return_type(declaration, prepared.source())
10784            {
10785                return false;
10786            }
10787
10788            let mut cursor = root.walk();
10789            root.named_children(&mut cursor).any(|sibling| {
10790                sibling.start_byte() >= declaration.end_byte()
10791                    && sibling.start_byte() < closing_brace_limit
10792                    && direct_unmatched_closing_brace(sibling)
10793            })
10794        })
10795}
10796
10797fn flattened_macro_namespace_components(
10798    declaration: Node<'_>,
10799    source: &str,
10800) -> Option<Vec<String>> {
10801    flattened_macro_function_namespace_components(declaration, source)
10802        .or_else(|| flattened_macro_error_namespace_components(declaration, source))
10803}
10804
10805fn flattened_macro_function_namespace_components(
10806    declaration: Node<'_>,
10807    source: &str,
10808) -> Option<Vec<String>> {
10809    let body = declaration
10810        .parent()
10811        .filter(|parent| parent.kind() == "compound_statement")?;
10812    let function = body.parent()?;
10813    if function.child_by_field_name("body") != Some(body) {
10814        return None;
10815    }
10816    let namespace_name = recovered_macro_namespace_name(function, source)?;
10817    let mut components = enclosing_namespace_components(declaration, source)?;
10818    components.push(namespace_name);
10819    Some(components)
10820}
10821
10822/// The namespace name a namespace-opening macro token displaced into a
10823/// synthetic `function_definition`, or `None` when `function` is not that
10824/// recovery shape.
10825///
10826/// `ABSL_NAMESPACE_BEGIN` (or `FMT_BEGIN_NAMESPACE`, ...) immediately before
10827/// `namespace x {` leaves tree-sitter with a `function_definition` whose type is
10828/// the macro token, whose declarator is the namespace name behind an `ERROR`
10829/// holding the `namespace` keyword, and whose body spans the whole namespace
10830/// region. The matching `*_NAMESPACE_END` sibling is what separates the recovery
10831/// artifact from a real function definition.
10832fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
10833    if function.kind() != "function_definition" || !function.has_error() {
10834        return None;
10835    }
10836    let body = function
10837        .child_by_field_name("body")
10838        .filter(|body| body.kind() == "compound_statement")?;
10839    let mut cursor = function.walk();
10840    let prefix = function
10841        .named_children(&mut cursor)
10842        .take_while(|child| child.start_byte() < body.start_byte())
10843        .filter(|child| child.kind() != "comment")
10844        .collect::<Vec<_>>();
10845    let begin_index = prefix.iter().rposition(|child| {
10846        flattened_macro_sentinel_name(*child, source)
10847            .is_some_and(|name| is_namespace_begin_sentinel(&name))
10848    })?;
10849    let mut identifiers = Vec::new();
10850    let mut stack = prefix[begin_index + 1..]
10851        .iter()
10852        .rev()
10853        .copied()
10854        .collect::<Vec<_>>();
10855    while let Some(current) = stack.pop() {
10856        if let Some(identifier) = direct_cpp_identifier_name(current, source) {
10857            identifiers.push(identifier);
10858            continue;
10859        }
10860        let mut cursor = current.walk();
10861        let children = current.named_children(&mut cursor).collect::<Vec<_>>();
10862        stack.extend(children.into_iter().rev());
10863    }
10864    let [keyword, namespace_name] = identifiers.as_slice() else {
10865        return None;
10866    };
10867    if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
10868    {
10869        return None;
10870    }
10871    let mut next = function.next_named_sibling();
10872    let next = loop {
10873        let candidate = next?;
10874        next = candidate.next_named_sibling();
10875        if candidate.kind() != "comment" {
10876            break candidate;
10877        }
10878    };
10879    flattened_macro_sentinel_name(next, source)
10880        .is_some_and(|name| is_namespace_end_sentinel(&name))
10881        .then(|| namespace_name.clone())
10882}
10883
10884/// A `function_definition` that exists only because tree-sitter recovered a
10885/// macro-decorated class head or a namespace-opening macro token. A declaration
10886/// in such a body keeps class or namespace scope, so a scope walk must step over
10887/// the wrapper instead of treating the declaration as block local.
10888fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
10889    crate::declarations::is_recovered_exported_class_container(node, source)
10890        || recovered_macro_namespace_name(node, source).is_some()
10891}
10892
10893fn flattened_macro_error_namespace_components(
10894    declaration: Node<'_>,
10895    source: &str,
10896) -> Option<Vec<String>> {
10897    let parent = declaration
10898        .parent()
10899        .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
10900    let mut cursor = parent.walk();
10901    let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
10902    let declaration_index = siblings
10903        .iter()
10904        .position(|candidate| same_node(*candidate, declaration))?;
10905    let begin_index = (0..declaration_index).rev().find(|index| {
10906        flattened_macro_sentinel_name(siblings[*index], source)
10907            .is_some_and(|name| is_namespace_begin_sentinel(&name))
10908    })?;
10909
10910    let significant = siblings[begin_index + 1..declaration_index]
10911        .iter()
10912        .copied()
10913        .filter(|node| node.kind() != "comment")
10914        .collect::<Vec<_>>();
10915    let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
10916        return None;
10917    };
10918    if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
10919        return None;
10920    }
10921    let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
10922    if significant[2..].iter().any(|node| {
10923        flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
10924            is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
10925        })
10926    }) {
10927        return None;
10928    }
10929
10930    let mut saw_namespace_close = false;
10931    for sibling in siblings.iter().skip(declaration_index + 1).copied() {
10932        if sibling.kind() == "comment" {
10933            continue;
10934        }
10935        if !saw_namespace_close {
10936            if direct_unmatched_closing_brace(sibling) {
10937                saw_namespace_close = true;
10938                continue;
10939            }
10940            if flattened_macro_sentinel_name(sibling, source).is_some() {
10941                return None;
10942            }
10943            continue;
10944        }
10945        if !flattened_macro_sentinel_name(sibling, source)
10946            .is_some_and(|name| is_namespace_end_sentinel(&name))
10947        {
10948            return None;
10949        }
10950        let mut components = enclosing_namespace_components(declaration, source)?;
10951        components.push(namespace_name);
10952        return Some(components);
10953    }
10954    None
10955}
10956
10957fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
10958    // At translation-unit scope the trailing `X_NAMESPACE_END` token parses as
10959    // an `expression_statement` with a missing semicolon; inside a namespace
10960    // body the same token stays a bare `type_identifier`.
10961    let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
10962        node.named_child(0)?
10963    } else {
10964        node
10965    };
10966    let candidate = direct_cpp_identifier_name(node, source).or_else(|| {
10967        node.child_by_field_name("type")
10968            .and_then(|type_node| direct_cpp_identifier_name(type_node, source))
10969    })?;
10970    (cpp_export_macro_token(&candidate)
10971        && (is_namespace_begin_sentinel(&candidate) || is_namespace_end_sentinel(&candidate)))
10972    .then_some(candidate)
10973}
10974
10975/// Namespace-opening macros are spelled both ways in the wild:
10976/// `ABSL_NAMESPACE_BEGIN` (abseil, nlohmann) and `FMT_BEGIN_NAMESPACE` (fmt).
10977fn is_namespace_begin_sentinel(name: &str) -> bool {
10978    name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE")
10979}
10980
10981fn is_namespace_end_sentinel(name: &str) -> bool {
10982    name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE")
10983}
10984
10985fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
10986    if node.kind() != "ERROR" || node.named_child_count() != 1 {
10987        return None;
10988    }
10989    let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
10990    (!cpp_export_macro_token(&name)).then_some(name)
10991}
10992
10993fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
10994    if !matches!(
10995        node.kind(),
10996        "identifier" | "namespace_identifier" | "type_identifier"
10997    ) {
10998        return None;
10999    }
11000    let name = normalize_cpp_whitespace(node_text(node, source));
11001    (!name.is_empty()).then_some(name)
11002}
11003
11004fn guard_requirement_sets_match(
11005    left: &[(usize, HashSet<PreprocessorGuard>)],
11006    right: &[(usize, HashSet<PreprocessorGuard>)],
11007) -> bool {
11008    left.len() == right.len()
11009        && left.iter().all(|(_, left_guards)| {
11010            right
11011                .iter()
11012                .any(|(_, right_guards)| left_guards == right_guards)
11013        })
11014        && right.iter().all(|(_, right_guards)| {
11015            left.iter()
11016                .any(|(_, left_guards)| right_guards == left_guards)
11017        })
11018}
11019
11020fn macro_displaced_cpp_return_type(declaration: Node<'_>, source: &str) -> bool {
11021    let Some(type_node) = declaration.child_by_field_name("type") else {
11022        return false;
11023    };
11024    let type_name = normalize_cpp_whitespace(node_text(type_node, source));
11025    !type_name.is_empty()
11026        && type_name
11027            .chars()
11028            .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
11029        && (0..declaration.named_child_count()).any(|index| {
11030            declaration
11031                .named_child(index)
11032                .is_some_and(|child| child.kind() == "ERROR")
11033        })
11034}
11035
11036fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
11037    node.kind() == "ERROR"
11038        && (0..node.child_count())
11039            .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
11040}
11041
11042pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
11043    let mut ancestor = node.parent();
11044    while let Some(parent) = ancestor {
11045        if is_preprocessor_conditional(parent)
11046            && !is_file_covering_include_guard(parent, source)
11047            && !is_split_cpp_language_linkage_wrapper(parent, node, source)
11048        {
11049            return false;
11050        }
11051        ancestor = parent.parent();
11052    }
11053    true
11054}
11055
11056fn is_split_cpp_language_linkage_wrapper(
11057    conditional: Node<'_>,
11058    descendant: Node<'_>,
11059    source: &str,
11060) -> bool {
11061    if conditional.child_by_field_name("alternative").is_some()
11062        || !matches!(
11063            simple_preprocessor_guard(conditional, source),
11064            Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
11065        )
11066    {
11067        return false;
11068    }
11069    let mut current = descendant.parent();
11070    let linkage = loop {
11071        let Some(node) = current else {
11072            return false;
11073        };
11074        if node == conditional {
11075            return false;
11076        }
11077        if node.kind() == "linkage_specification" {
11078            break node;
11079        }
11080        current = node.parent();
11081    };
11082    if linkage
11083        .child_by_field_name("value")
11084        .is_none_or(|value| node_text(value, source) != "\"C\"")
11085    {
11086        return false;
11087    }
11088    let Some(body) = linkage.child_by_field_name("body") else {
11089        return false;
11090    };
11091    let closes_opening_branch = (0..body.named_child_count())
11092        .filter_map(|index| body.named_child(index))
11093        .take_while(|child| child.end_byte() <= descendant.start_byte())
11094        .any(|child| {
11095            child.kind() == "preproc_call"
11096                && child
11097                    .child_by_field_name("directive")
11098                    .is_some_and(|directive| node_text(directive, source) == "#endif")
11099        });
11100    let reopens_for_closing_brace = (0..body.named_child_count())
11101        .filter_map(|index| body.named_child(index))
11102        .skip_while(|child| child.start_byte() < descendant.end_byte())
11103        .any(|child| {
11104            matches!(
11105                simple_preprocessor_guard(child, source),
11106                Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
11107            ) && (0..child.child_count()).any(|index| {
11108                child
11109                    .child(index)
11110                    .is_some_and(|token| token.kind() == "#endif" && token.is_missing())
11111            })
11112        });
11113    closes_opening_branch && reopens_for_closing_brace
11114}
11115
11116/// The argument list a call-shaped node supplies: `f(args)`, `new T(args)`,
11117/// `T{args}` and the member initializer `: field(args)`, whose grammar gives its
11118/// argument list no field name.
11119pub fn call_arguments_node(node: Node<'_>) -> Option<Node<'_>> {
11120    node.child_by_field_name("arguments")
11121        .or_else(|| node.child_by_field_name("parameters"))
11122        .or_else(|| node.child_by_field_name("value"))
11123        .or_else(|| first_named_child_of_kind(node, "argument_list"))
11124        .or_else(|| first_named_child_of_kind(node, "initializer_list"))
11125}
11126
11127pub fn call_arity(node: Node<'_>) -> usize {
11128    call_arguments_node(node)
11129        .map(|args| argument_children(args).count())
11130        .unwrap_or(0)
11131}
11132
11133pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
11134    let recovered_block_arguments = recovered_block_literal_arguments(node);
11135    (0..node.child_count())
11136        .filter_map(move |index| node.child(index))
11137        .filter(|child| child.is_named() && !child.is_extra())
11138        .flat_map(move |child| {
11139            if let Some((raw, left, right)) = recovered_block_arguments
11140                && child == raw
11141            {
11142                [Some(left), Some(right)]
11143            } else {
11144                [Some(child), None]
11145            }
11146        })
11147        .flatten()
11148}
11149
11150/// Recover the two ordinary C values that the C++ grammar folds into one
11151/// `new_expression` for `callee(new, trailing)`.
11152///
11153/// The malformed node has an exact grammar-owned shape: the anonymous `new`
11154/// token, an extra `ERROR` containing only the comma token, and the trailing
11155/// value in the `type` field.  The caller supplies the compilation-dialect
11156/// proof from [`reference_uses_c_semantics`]; a C++ source therefore never
11157/// reinterprets a real new-expression through this path.
11158pub fn recovered_c_new_expression_arguments(
11159    node: Node<'_>,
11160    uses_c_semantics: bool,
11161) -> Option<[Node<'_>; 2]> {
11162    if !uses_c_semantics || node.kind() != "new_expression" {
11163        return None;
11164    }
11165    let parent = node.parent()?;
11166    if parent.kind() != "argument_list" {
11167        return None;
11168    }
11169    let keyword = node.child(0)?;
11170    let error = node.child(1)?;
11171    let trailing = node.child(2)?;
11172    if node.child(3).is_some()
11173        || keyword.kind() != "new"
11174        || keyword.is_named()
11175        || keyword.child_count() != 0
11176        || error.kind() != "ERROR"
11177        || !error.is_extra()
11178        || error.child_count() != 1
11179        || error.child(0).is_none_or(|comma| comma.kind() != ",")
11180        || node.child_by_field_name("type") != Some(trailing)
11181        || trailing.kind() != "type_identifier"
11182    {
11183        return None;
11184    }
11185    Some([keyword, trailing])
11186}
11187
11188/// The recovered C value covering one focused source range, starting from any
11189/// node within the malformed new-expression.
11190pub fn recovered_c_new_expression_argument_at(
11191    mut node: Node<'_>,
11192    start_byte: usize,
11193    end_byte: usize,
11194    uses_c_semantics: bool,
11195) -> Option<Node<'_>> {
11196    loop {
11197        if let Some(arguments) = recovered_c_new_expression_arguments(node, uses_c_semantics) {
11198            return arguments.into_iter().find(|argument| {
11199                argument.start_byte() <= start_byte && end_byte <= argument.end_byte()
11200            });
11201        }
11202        node = node.parent()?;
11203    }
11204}
11205
11206fn recovered_c_keyword_argument_count(
11207    file: &ProjectFile,
11208    call: Node<'_>,
11209    arguments: Node<'_>,
11210    source: &str,
11211) -> usize {
11212    // A C identifier that is a C++ keyword can be displaced twice by the C++
11213    // grammar: first into a direct parameter-list `ERROR(keyword)`, then into
11214    // a direct argument-list `ERROR(',', keyword)`. Match those CST tokens in
11215    // the enclosing C function before restoring the otherwise dropped slot.
11216    if !is_c_source_file(file) || arguments.kind() != "argument_list" {
11217        return 0;
11218    }
11219    let mut ancestor = Some(call);
11220    let function = loop {
11221        let Some(current) = ancestor else {
11222            return 0;
11223        };
11224        if current.kind() == "function_definition" {
11225            break current;
11226        }
11227        ancestor = current.parent();
11228    };
11229    let Some(parameters) = function
11230        .child_by_field_name("declarator")
11231        .and_then(|declarator| declarator.child_by_field_name("parameters"))
11232    else {
11233        return 0;
11234    };
11235    let displaced_parameter_keywords = (0..parameters.child_count())
11236        .filter_map(|index| parameters.child(index))
11237        .filter(|error| error.kind() == "ERROR")
11238        .filter_map(|error| {
11239            let parameter = error.prev_named_sibling()?;
11240            if parameter.kind() != "parameter_declaration"
11241                || parameter.end_byte() != error.start_byte()
11242                || extract_variable_name(parameter, source).is_some()
11243            {
11244                return None;
11245            }
11246            let mut children = (0..error.child_count())
11247                .filter_map(|index| error.child(index))
11248                .filter(|child| !child.is_extra() && !child.is_missing());
11249            let keyword = children.next()?;
11250            (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
11251                .then_some(keyword)
11252        })
11253        .collect::<Vec<_>>();
11254    if displaced_parameter_keywords.is_empty() {
11255        return 0;
11256    }
11257
11258    (0..arguments.child_count())
11259        .filter_map(|index| arguments.child(index))
11260        .filter(|error| error.kind() == "ERROR" && error.is_extra())
11261        .filter(|error| {
11262            let mut children = (0..error.child_count())
11263                .filter_map(|index| error.child(index))
11264                .filter(|child| !child.is_extra() && !child.is_missing());
11265            let Some(comma) = children.next() else {
11266                return false;
11267            };
11268            let Some(keyword) = children.next() else {
11269                return false;
11270            };
11271            children.next().is_none()
11272                && comma.kind() == ","
11273                && !keyword.is_named()
11274                && keyword.child_count() == 0
11275                && displaced_parameter_keywords
11276                    .iter()
11277                    .any(|parameter| parameter.kind_id() == keyword.kind_id())
11278        })
11279        .count()
11280}
11281
11282fn recovered_block_literal_arguments<'tree>(
11283    arguments: Node<'tree>,
11284) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
11285    if arguments.kind() != "argument_list" {
11286        return None;
11287    }
11288    let mut raw_arguments = (0..arguments.child_count())
11289        .filter_map(|index| arguments.child(index))
11290        .filter(|child| child.is_named() && !child.is_extra());
11291    let raw = raw_arguments.next()?;
11292    if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
11293        return None;
11294    }
11295
11296    let left = raw.child_by_field_name("left")?;
11297    if left.is_missing() || left.start_byte() == left.end_byte() {
11298        return None;
11299    }
11300    let right = raw.child_by_field_name("right")?;
11301    if right.kind() != "compound_literal_expression"
11302        || right.is_missing()
11303        || right
11304            .child_by_field_name("type")
11305            .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
11306        || right
11307            .child_by_field_name("value")
11308            .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
11309    {
11310        return None;
11311    }
11312    let has_intervening_error = (0..raw.child_count())
11313        .filter_map(|index| raw.child(index))
11314        .any(|child| {
11315            child.kind() == "ERROR"
11316                && !child.is_missing()
11317                && child.start_byte() >= left.end_byte()
11318                && child.end_byte() <= right.start_byte()
11319        });
11320    has_intervening_error.then_some((raw, left, right))
11321}
11322
11323pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
11324    match node.kind() {
11325        "new_expression" => node
11326            .child_by_field_name("type")
11327            .or_else(|| node.named_child(0)),
11328        "compound_literal_expression" => node.child_by_field_name("type"),
11329        "call_expression" => node.child_by_field_name("function"),
11330        _ => None,
11331    }
11332}
11333
11334/// The structured type named by a C-style cast expression.
11335///
11336/// Tree-sitter wraps the actual type syntax in a `type_descriptor`.  Return its
11337/// structured `type` field so resolution retains qualified and nested syntax
11338/// without reparsing source text.
11339pub fn cast_expression_type_node(node: Node<'_>) -> Option<Node<'_>> {
11340    if node.kind() != "cast_expression" {
11341        return None;
11342    }
11343    let descriptor = node.child_by_field_name("type")?;
11344    if descriptor.kind() == "type_descriptor" {
11345        descriptor.child_by_field_name("type")
11346    } else {
11347        Some(descriptor)
11348    }
11349}
11350
11351pub fn field_initializer_constructs_target(
11352    node: Node<'_>,
11353    ctx: &ScanCtx<'_>,
11354    owner: &CodeUnit,
11355) -> bool {
11356    // A qualified name in a constructor initializer denotes a base
11357    // subobject constructor (`namespace::Base(args)`), not a member field.  The
11358    // field-initializer grammar exposes the qualified name as one structured
11359    // `qualified_identifier`; resolve its owner through the same lexical type
11360    // machinery used for ordinary C++ type references before considering the
11361    // initializer a hit.  This keeps an unrelated `namespace::Other(...)`, a
11362    // qualified non-constructor member, and an unresolved owner out of the
11363    // target constructor's inverse usage set.
11364    if first_named_child_of_kind(node, "qualified_identifier").is_some() {
11365        return qualified_base_initializer_constructs_target(node, ctx, owner);
11366    }
11367    let Some(name) = node
11368        .child_by_field_name("name")
11369        .or_else(|| first_named_child_of_kind(node, "field_identifier"))
11370        .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
11371    else {
11372        return false;
11373    };
11374    let field_name = node_text(name, ctx.source);
11375    ctx.visibility
11376        .visible_identifier_candidates(ctx.file, field_name)
11377        .filter(|unit| unit.is_field() && unit.identifier() == field_name)
11378        .any(|unit| field_declares_type(unit, ctx, owner))
11379}
11380
11381fn qualified_base_initializer_constructs_target(
11382    node: Node<'_>,
11383    ctx: &ScanCtx<'_>,
11384    owner: &CodeUnit,
11385) -> bool {
11386    let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
11387        return false;
11388    };
11389    let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
11390        return false;
11391    };
11392    let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
11393        return false;
11394    };
11395    let resolves_target = |components: &[String]| {
11396        matches!(
11397            ctx.visibility.resolve_type_components_lexically_for_target(
11398                &ctx.analyzer,
11399                ctx.file,
11400                components,
11401                is_globally_qualified_cpp_name(qualified),
11402                &lexical_scope,
11403                owner,
11404            ),
11405            LexicalTypeResolution::Resolved { unit, .. }
11406                if same_visible_symbol(&unit, owner)
11407        )
11408    };
11409    if resolves_target(&components) {
11410        return true;
11411    }
11412
11413    // Some real-world code spells a base mem-initializer as
11414    // `Base::Base(args)`. In that structured path the final component repeats
11415    // the constructor name; resolve the preceding type path. The terminal
11416    // identity check prevents an arbitrary qualified member from taking this
11417    // route.
11418    components
11419        .last()
11420        .is_some_and(|terminal| terminal == owner.identifier())
11421        && resolves_target(&components[..components.len() - 1])
11422}
11423
11424fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
11425    unit.signature()
11426        .is_some_and(|declaration| field_declaration_type_matches(declaration, unit, ctx, owner))
11427        || ctx
11428            .analyzer
11429            .get_source(unit, false)
11430            .is_some_and(|declaration| {
11431                field_declaration_type_matches(&declaration, unit, ctx, owner)
11432            })
11433}
11434
11435pub fn field_declared_binding(
11436    analyzer: &CppGraphSource<'_>,
11437    visibility: &VisibilityIndex<'_>,
11438    visible_from: &ProjectFile,
11439    field: &CodeUnit,
11440) -> Option<CppScanBinding> {
11441    let fact = visibility.field_declared_type_fact(analyzer, field)?;
11442    let normalized = normalize_field_type_text(&fact.type_text);
11443    let resolved = visibility.resolve_unique_canonical_type_for_declaration(
11444        analyzer,
11445        visible_from,
11446        field,
11447        &normalized,
11448    );
11449    let resolved = match (resolved, fact.template_arguments.as_deref()) {
11450        (Some(primary), Some(arguments)) => visibility
11451            .resolve_template_arguments(visible_from, primary, arguments)
11452            .ok(),
11453        (resolved, None) => resolved,
11454        (None, Some(_)) => None,
11455    }
11456    .or_else(|| anonymous_aggregate_field_owner(analyzer, visibility, visible_from, field));
11457    Some(CppScanBinding::from_type_name(
11458        normalized,
11459        resolved,
11460        fact.indirection,
11461    ))
11462}
11463
11464/// Resolve the receiver type minted for a named declarator on an anonymous C
11465/// aggregate, such as `struct { int r; } c`. The declaration index preserves
11466/// the aggregate as the nested class `Owner$c`, but the field's type fact has
11467/// no spelling that can name that class. Confirm the anonymous aggregate from
11468/// its parsed declaration, then use the owner's structured child relationship
11469/// to recover the one corresponding receiver type.
11470fn anonymous_aggregate_field_owner(
11471    analyzer: &CppGraphSource<'_>,
11472    visibility: &VisibilityIndex<'_>,
11473    visible_from: &ProjectFile,
11474    field: &CodeUnit,
11475) -> Option<CodeUnit> {
11476    let owner = type_owner_of(analyzer, field)?;
11477    if !owner.is_class() {
11478        return None;
11479    }
11480    let declaration = analyzer.get_source(field, false)?;
11481    let mut parser = Parser::new();
11482    parser
11483        .set_language(&tree_sitter_cpp::LANGUAGE.into())
11484        .ok()?;
11485    let tree = parser.parse(&declaration, None)?;
11486    let mut stack = vec![tree.root_node()];
11487    while let Some(node) = stack.pop() {
11488        if matches!(node.kind(), "declaration" | "field_declaration")
11489            && let Some(type_node) = node
11490                .child_by_field_name("type")
11491                .or_else(|| first_type_child(node))
11492            && matches!(type_node.kind(), "struct_specifier" | "union_specifier")
11493            && type_node.child_by_field_name("name").is_none()
11494            && declared_name_indirection(node, type_node, field.identifier(), &declaration)
11495                .is_some()
11496        {
11497            let matches = visibility
11498                .visible_members_for_owner_name(visible_from, &owner, field.identifier())
11499                .into_iter()
11500                .filter(|child| child.is_class() && child.identifier() == field.identifier())
11501                .collect::<Vec<_>>();
11502            return match matches.as_slice() {
11503                [child] => Some((*child).clone()),
11504                _ => None,
11505            };
11506        }
11507        let mut cursor = node.walk();
11508        stack.extend(node.named_children(&mut cursor));
11509    }
11510    None
11511}
11512
11513/// Resolve an anonymous C aggregate's generated owner from its declaration
11514/// range. Anonymous local structs and unions have no type name to enter into
11515/// the visibility index; declaration extraction gives them a structured class
11516/// identity keyed by the aggregate node's exact CST range instead. Matching
11517/// that range keeps nested aggregates and unrelated generated owners out of
11518/// the result without inspecting source text.
11519pub fn anonymous_aggregate_owner(
11520    analyzer: &CppGraphSource<'_>,
11521    file: &ProjectFile,
11522    node: Node<'_>,
11523) -> Option<CodeUnit> {
11524    if !matches!(node.kind(), "struct_specifier" | "union_specifier")
11525        || node.child_by_field_name("name").is_some()
11526    {
11527        return None;
11528    }
11529    let mut candidates = analyzer
11530        .declarations(file)
11531        .into_iter()
11532        .filter(|candidate| {
11533            candidate.is_class()
11534                && analyzer.ranges(candidate).into_iter().any(|range| {
11535                    range.start_byte == node.start_byte() && range.end_byte == node.end_byte()
11536                })
11537        })
11538        .collect::<Vec<_>>();
11539    candidates.sort_by_key(|candidate| candidate.fq_name());
11540    candidates.dedup();
11541    match candidates.as_slice() {
11542        [candidate] => Some(candidate.clone()),
11543        _ => None,
11544    }
11545}
11546
11547/// The one logical type the candidates name, or why they do not name one.
11548fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
11549    let Some(first) = candidates.first() else {
11550        return Err(TypeCandidateFailure::Unresolvable);
11551    };
11552    if candidates
11553        .iter()
11554        .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
11555    {
11556        Ok((*first).clone())
11557    } else {
11558        Err(TypeCandidateFailure::Ambiguous)
11559    }
11560}
11561
11562fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
11563    logical_type_candidate(candidates).ok()
11564}
11565
11566fn unique_type_candidate_preserving_alias(
11567    analyzer: &CppGraphSource<'_>,
11568    candidates: &[&CodeUnit],
11569) -> Option<CodeUnit> {
11570    let first = *candidates.first()?;
11571    if declared_type_alias(analyzer, first) {
11572        return candidates
11573            .iter()
11574            .all(|candidate| {
11575                declared_type_alias(analyzer, candidate)
11576                    && candidate.kind() == first.kind()
11577                    && candidate.fq_name() == first.fq_name()
11578                    && candidate.source() == first.source()
11579            })
11580            .then(|| first.clone());
11581    }
11582    if first.is_class() && indexed_c_tag_kind(analyzer, first).is_some() {
11583        let mut full_source = None;
11584        let mut tag_kind = None;
11585        for candidate in candidates.iter().copied() {
11586            let candidate_tag_kind = indexed_c_tag_kind(analyzer, candidate)?;
11587            if tag_kind
11588                .replace(candidate_tag_kind)
11589                .is_some_and(|existing| existing != candidate_tag_kind)
11590            {
11591                return None;
11592            }
11593            if cpp_class_declaration_strength(analyzer, candidate)
11594                == CppClassDeclarationStrength::Full
11595                && full_source
11596                    .replace(candidate.source())
11597                    .is_some_and(|existing| existing != candidate.source())
11598            {
11599                return None;
11600            }
11601        }
11602    }
11603    candidates
11604        .iter()
11605        .all(|candidate| {
11606            !declared_type_alias(analyzer, candidate)
11607                && candidate.kind() == first.kind()
11608                && candidate.fq_name() == first.fq_name()
11609        })
11610        .then(|| first.clone())
11611}
11612
11613fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
11614    is_type_alias(unit)
11615        || analyzer
11616            .type_alias_provider()
11617            .is_some_and(|provider| provider.is_type_alias(unit))
11618}
11619
11620pub fn field_declared_type_binding(
11621    analyzer: &CppGraphSource<'_>,
11622    visibility: &VisibilityIndex<'_>,
11623    visible_from: &ProjectFile,
11624    field: &CodeUnit,
11625) -> Option<(String, Option<CodeUnit>, i32)> {
11626    let fact = visibility.field_declared_type_fact(analyzer, field)?;
11627    let normalized = normalize_field_type_text(&fact.type_text);
11628    let primary = visibility.resolve_unique_canonical_type_for_declaration(
11629        analyzer,
11630        visible_from,
11631        field,
11632        &normalized,
11633    );
11634    let resolved = match (primary, fact.template_arguments.as_deref()) {
11635        (Some(primary), Some(arguments)) => visibility
11636            .resolve_template_arguments(visible_from, primary, arguments)
11637            .ok(),
11638        (resolved, None) => resolved,
11639        (None, Some(_)) => None,
11640    };
11641    Some((normalized, resolved, fact.indirection))
11642}
11643
11644fn decode_field_declared_type_fact(
11645    analyzer: &CppGraphSource<'_>,
11646    field: &CodeUnit,
11647) -> Option<DeclaredFieldTypeFact> {
11648    let declaration = analyzer.get_source(field, false)?;
11649    let mut parser = Parser::new();
11650    parser
11651        .set_language(&tree_sitter_cpp::LANGUAGE.into())
11652        .ok()?;
11653    // A field's indexed source is stored without its surrounding class body.
11654    // Give tree-sitter that grammatical context before checking recovery-only
11655    // field shapes such as `PyObject_HEAD Imaging image;`.
11656    let contextual_declaration = format!("struct __bifrost_field_context {{ {declaration} }};");
11657    let contextual_tree = parser.parse(&contextual_declaration, None)?;
11658    let mut stack = vec![contextual_tree.root_node()];
11659    while let Some(node) = stack.pop() {
11660        if let Some(recovered) = recovered_pyobject_head_field(node, &contextual_declaration)
11661            && node_text(recovered.declarator, &contextual_declaration) == field.identifier()
11662        {
11663            return Some(DeclaredFieldTypeFact {
11664                type_text: node_text(recovered.type_node, &contextual_declaration).to_string(),
11665                indirection: 0,
11666                template_arguments: None,
11667            });
11668        }
11669        if let Some(recovered) =
11670            recovered_function_like_field_declarator(node, &contextual_declaration)
11671            && node_text(recovered.name, &contextual_declaration) == field.identifier()
11672        {
11673            let type_node = node
11674                .child_by_field_name("type")
11675                .or_else(|| first_type_child(node))?;
11676            return Some(DeclaredFieldTypeFact {
11677                type_text: node_text(type_node, &contextual_declaration).to_string(),
11678                indirection: recovered.pointer_depth(),
11679                template_arguments: cpp_template_reference_arguments(
11680                    type_node,
11681                    &contextual_declaration,
11682                ),
11683            });
11684        }
11685        if let Some(fact) =
11686            decode_declared_field_type_node(node, field.identifier(), &contextual_declaration)
11687        {
11688            return Some(fact);
11689        }
11690        let mut cursor = node.walk();
11691        stack.extend(node.named_children(&mut cursor));
11692    }
11693    let tree = parser.parse(&declaration, None)?;
11694    let mut stack = vec![tree.root_node()];
11695    while let Some(node) = stack.pop() {
11696        if let Some(fact) = decode_declared_field_type_node(node, field.identifier(), &declaration)
11697        {
11698            return Some(fact);
11699        }
11700        let mut cursor = node.walk();
11701        stack.extend(node.named_children(&mut cursor));
11702    }
11703    None
11704}
11705
11706fn decode_declared_field_type_node(
11707    node: Node<'_>,
11708    field_name: &str,
11709    source: &str,
11710) -> Option<DeclaredFieldTypeFact> {
11711    if !matches!(node.kind(), "declaration" | "field_declaration") {
11712        return None;
11713    }
11714    let type_node = node
11715        .child_by_field_name("type")
11716        .or_else(|| first_type_child(node))?;
11717    let indirection = declared_name_indirection(node, type_node, field_name, source)?;
11718    let declared_type = if matches!(
11719        type_node.kind(),
11720        "class_specifier" | "struct_specifier" | "union_specifier"
11721    ) {
11722        type_node.child_by_field_name("name")
11723    } else {
11724        Some(type_node)
11725    };
11726    Some(DeclaredFieldTypeFact {
11727        type_text: declared_type.map_or_else(
11728            || field_name.to_string(),
11729            |declared_type| node_text(declared_type, source).to_string(),
11730        ),
11731        indirection,
11732        template_arguments: declared_type
11733            .and_then(|declared_type| cpp_template_reference_arguments(declared_type, source)),
11734    })
11735}
11736
11737/// Text of the type that a C or C++ alias declaration names, read from the
11738/// `type_definition` or `alias_declaration` node's `type` field.
11739///
11740/// The declaration text is never scanned. A function-pointer typedef
11741/// interleaves its aliased type with its declarator (`typedef R (*F)(int)`),
11742/// so no prefix or suffix of the spelling isolates the target.
11743///
11744/// An alias whose declarator is a function declarator names a function type:
11745/// `typedef R F(int)`, `typedef R (*F)(int)`, `typedef R *F(int)`, and
11746/// `using F = R (*)(int)`. The analyzer's type model names declared types only,
11747/// so such an alias has no canonical target. Its `type` field holds the return
11748/// type `R`, which is a different type from the alias, so this returns `None`
11749/// rather than that return type.
11750pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
11751    let mut parser = Parser::new();
11752    parser
11753        .set_language(&tree_sitter_cpp::LANGUAGE.into())
11754        .ok()?;
11755    let tree = parser.parse(declaration, None)?;
11756    let mut stack = vec![tree.root_node()];
11757    while let Some(node) = stack.pop() {
11758        let type_node = match node.kind() {
11759            "type_definition" => {
11760                let mut cursor = node.walk();
11761                if node
11762                    .children_by_field_name("declarator", &mut cursor)
11763                    .any(declarator_names_function_type)
11764                {
11765                    return None;
11766                }
11767                node.child_by_field_name("type")?
11768            }
11769            "alias_declaration" => {
11770                let type_node = node.child_by_field_name("type")?;
11771                if type_node
11772                    .child_by_field_name("declarator")
11773                    .is_some_and(declarator_names_function_type)
11774                {
11775                    return None;
11776                }
11777                type_node
11778            }
11779            _ => {
11780                let mut cursor = node.walk();
11781                let children = node.named_children(&mut cursor).collect::<Vec<_>>();
11782                stack.extend(children.into_iter().rev());
11783                continue;
11784            }
11785        };
11786        return Some(node_text(type_node, declaration).to_string());
11787    }
11788    None
11789}
11790
11791/// Whether an alias declaration's own declarator adds indirection that
11792/// [`cpp_alias_declaration_target_text`] does not report.
11793///
11794/// That function reads the declaration's `type` field, where `typedef Foo *Bar`
11795/// keeps only `Foo`: the `*` lives in the sibling declarator. Substituting such
11796/// an alias would equate `f(Bar)` with `f(Foo)`, so a comparison that cannot
11797/// prove the alias adds no indirection must refuse to follow it. A declaration
11798/// this cannot read at all is refused for the same reason.
11799fn cpp_alias_declaration_adds_indirection(declaration: &str) -> bool {
11800    let mut parser = Parser::new();
11801    if parser
11802        .set_language(&tree_sitter_cpp::LANGUAGE.into())
11803        .is_err()
11804    {
11805        return true;
11806    }
11807    let Some(tree) = parser.parse(declaration, None) else {
11808        return true;
11809    };
11810    let mut stack = vec![tree.root_node()];
11811    while let Some(node) = stack.pop() {
11812        let declarators = match node.kind() {
11813            "type_definition" => {
11814                let mut cursor = node.walk();
11815                node.children_by_field_name("declarator", &mut cursor)
11816                    .collect::<Vec<_>>()
11817            }
11818            "alias_declaration" => node
11819                .child_by_field_name("type")
11820                .and_then(|type_node| type_node.child_by_field_name("declarator"))
11821                .into_iter()
11822                .collect::<Vec<_>>(),
11823            _ => {
11824                let mut cursor = node.walk();
11825                let children = node.named_children(&mut cursor).collect::<Vec<_>>();
11826                stack.extend(children.into_iter().rev());
11827                continue;
11828            }
11829        };
11830        return declarators.into_iter().any(cpp_declarator_adds_indirection);
11831    }
11832    true
11833}
11834
11835/// True when an alias declarator names a function type.
11836///
11837/// The declarator chain is walked through the `declarator` field, so the
11838/// parameter list -- a sibling field -- is never entered and a parameter's own
11839/// function declarator cannot be mistaken for the alias's.
11840fn declarator_names_function_type(declarator: Node<'_>) -> bool {
11841    let mut current = Some(declarator);
11842    while let Some(node) = current {
11843        match node.kind() {
11844            "function_declarator" | "abstract_function_declarator" => return true,
11845            "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
11846                current = node.named_child(0);
11847            }
11848            _ => current = node.child_by_field_name("declarator"),
11849        }
11850    }
11851    false
11852}
11853
11854/// Whether one indexed field declaration is a function or function-pointer
11855/// value. This follows tree-sitter declarator fields and never infers
11856/// callability from source spelling.
11857pub fn cpp_field_declaration_names_function_type(declaration: &str, field_name: &str) -> bool {
11858    let mut parser = Parser::new();
11859    if parser
11860        .set_language(&tree_sitter_cpp::LANGUAGE.into())
11861        .is_err()
11862    {
11863        return false;
11864    }
11865    let Some(tree) = parser.parse(declaration, None) else {
11866        return false;
11867    };
11868    let mut stack = vec![tree.root_node()];
11869    while let Some(node) = stack.pop() {
11870        if matches!(node.kind(), "declaration" | "field_declaration") {
11871            let mut cursor = node.walk();
11872            if node
11873                .children_by_field_name("declarator", &mut cursor)
11874                .any(|declarator| {
11875                    declarator_name_node(declarator).is_some_and(|name| {
11876                        node_text(name, declaration) == field_name
11877                            && declarator_names_function_type(declarator)
11878                    })
11879                })
11880            {
11881                return true;
11882            }
11883        }
11884        let mut cursor = node.walk();
11885        stack.extend(node.named_children(&mut cursor));
11886    }
11887    false
11888}
11889
11890/// Whether one indexed alias declaration names a function or function-pointer
11891/// type. The alias name is matched through the declarator field so a function
11892/// type used by a parameter cannot be mistaken for the alias itself.
11893pub fn cpp_alias_declaration_names_function_type(declaration: &str, alias_name: &str) -> bool {
11894    let mut parser = Parser::new();
11895    if parser
11896        .set_language(&tree_sitter_cpp::LANGUAGE.into())
11897        .is_err()
11898    {
11899        return false;
11900    }
11901    let Some(tree) = parser.parse(declaration, None) else {
11902        return false;
11903    };
11904    let mut stack = vec![tree.root_node()];
11905    while let Some(node) = stack.pop() {
11906        match node.kind() {
11907            "type_definition" => {
11908                let mut cursor = node.walk();
11909                if node
11910                    .children_by_field_name("declarator", &mut cursor)
11911                    .any(|declarator| {
11912                        extract_typedef_declarator_name(declarator, declaration)
11913                            .is_some_and(|name| name == alias_name)
11914                            && declarator_names_function_type(declarator)
11915                    })
11916                {
11917                    return true;
11918                }
11919            }
11920            "alias_declaration" => {
11921                let names_alias = node
11922                    .child_by_field_name("name")
11923                    .is_some_and(|name| node_text(name, declaration) == alias_name);
11924                if names_alias
11925                    && node
11926                        .child_by_field_name("type")
11927                        .and_then(|type_node| type_node.child_by_field_name("declarator"))
11928                        .is_some_and(declarator_names_function_type)
11929                {
11930                    return true;
11931                }
11932            }
11933            _ => {}
11934        }
11935        let mut cursor = node.walk();
11936        stack.extend(node.named_children(&mut cursor));
11937    }
11938    false
11939}
11940
11941fn decode_structured_alias_target(
11942    analyzer: &CppGraphSource<'_>,
11943    unit: &CodeUnit,
11944) -> Option<StructuredAliasTarget> {
11945    analyzer
11946        .get_source(unit, false)
11947        .and_then(|declaration| decode_structured_alias_target_source(unit, &declaration, true))
11948        .or_else(|| {
11949            let signature = unit.signature()?;
11950            decode_structured_alias_target_source(unit, signature, false)
11951        })
11952}
11953
11954fn decode_structured_alias_target_source(
11955    unit: &CodeUnit,
11956    declaration: &str,
11957    require_top_level: bool,
11958) -> Option<StructuredAliasTarget> {
11959    let mut parser = Parser::new();
11960    parser
11961        .set_language(&tree_sitter_cpp::LANGUAGE.into())
11962        .ok()?;
11963    let tree = parser.parse(declaration, None)?;
11964    let mut stack = vec![tree.root_node()];
11965    while let Some(node) = stack.pop() {
11966        let type_node = match node.kind() {
11967            "type_definition" => {
11968                if require_top_level
11969                    && node
11970                        .parent()
11971                        .is_none_or(|parent| parent.kind() != "translation_unit")
11972                {
11973                    let mut cursor = node.walk();
11974                    stack.extend(node.named_children(&mut cursor));
11975                    continue;
11976                }
11977                let mut declarator_cursor = node.walk();
11978                let declarator = node
11979                    .children_by_field_name("declarator", &mut declarator_cursor)
11980                    .find(|declarator| {
11981                        extract_typedef_declarator_name(*declarator, declaration)
11982                            .is_some_and(|name| name == unit.identifier())
11983                    })?;
11984                if declarator_names_function_type(declarator) {
11985                    return None;
11986                }
11987                node.child_by_field_name("type")?
11988            }
11989            "alias_declaration" => {
11990                if require_top_level
11991                    && node
11992                        .parent()
11993                        .is_none_or(|parent| parent.kind() != "translation_unit")
11994                {
11995                    let mut cursor = node.walk();
11996                    stack.extend(node.named_children(&mut cursor));
11997                    continue;
11998                }
11999                let name = node.child_by_field_name("name")?;
12000                if node_text(name, declaration) != unit.identifier() {
12001                    return None;
12002                }
12003                let type_node = node.child_by_field_name("type")?;
12004                if type_node
12005                    .child_by_field_name("declarator")
12006                    .is_some_and(declarator_names_function_type)
12007                {
12008                    return None;
12009                }
12010                type_node
12011            }
12012            _ => {
12013                let mut cursor = node.walk();
12014                stack.extend(node.named_children(&mut cursor));
12015                continue;
12016            }
12017        };
12018        return structured_alias_type_target(type_node, declaration);
12019    }
12020    None
12021}
12022
12023fn structured_alias_type_target(
12024    mut type_node: Node<'_>,
12025    source: &str,
12026) -> Option<StructuredAliasTarget> {
12027    while type_node.kind() == "type_descriptor" {
12028        type_node = type_node.child_by_field_name("type")?;
12029    }
12030    if type_node.kind() == "primitive_type" {
12031        return Some(StructuredAliasTarget::Builtin);
12032    }
12033    if matches!(
12034        type_node.kind(),
12035        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
12036    ) {
12037        type_node = type_node.child_by_field_name("name")?;
12038    }
12039    let global = type_node.child_by_field_name("scope").is_none()
12040        && type_node.child(0).is_some_and(|child| child.kind() == "::");
12041    let mut components = Vec::new();
12042    append_structured_type_components(type_node, source, &mut components)?;
12043    let arguments = cpp_template_reference_arguments(type_node, source);
12044    (!components.is_empty()).then_some(StructuredAliasTarget::Named {
12045        components,
12046        global,
12047        arguments,
12048    })
12049}
12050
12051fn append_structured_type_components(
12052    node: Node<'_>,
12053    source: &str,
12054    out: &mut Vec<String>,
12055) -> Option<()> {
12056    match node.kind() {
12057        "identifier" | "namespace_identifier" | "type_identifier" => {
12058            out.push(node_text(node, source).to_string());
12059            Some(())
12060        }
12061        "template_type" => {
12062            append_structured_type_components(node.child_by_field_name("name")?, source, out)
12063        }
12064        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
12065            if let Some(scope) = node.child_by_field_name("scope") {
12066                append_structured_type_components(scope, source, out)?;
12067            }
12068            append_structured_type_components(node.child_by_field_name("name")?, source, out)
12069        }
12070        _ => None,
12071    }
12072}
12073
12074fn declared_name_indirection(
12075    declaration: Node<'_>,
12076    type_node: Node<'_>,
12077    field_name: &str,
12078    source: &str,
12079) -> Option<i32> {
12080    let mut stack = Vec::new();
12081    let mut cursor = declaration.walk();
12082    stack.extend(
12083        declaration
12084            .named_children(&mut cursor)
12085            .filter(|child| !same_node(*child, type_node)),
12086    );
12087    while let Some(node) = stack.pop() {
12088        if matches!(node.kind(), "identifier" | "field_identifier")
12089            && node_text(node, source) == field_name
12090        {
12091            let mut indirection = 0;
12092            let mut current = node.parent();
12093            while let Some(parent) = current {
12094                if same_node(parent, declaration) {
12095                    return Some(indirection);
12096                }
12097                if parent.kind() == "pointer_declarator" {
12098                    indirection += 1;
12099                }
12100                current = parent.parent();
12101            }
12102            return None;
12103        }
12104        let mut cursor = node.walk();
12105        stack.extend(node.named_children(&mut cursor));
12106    }
12107    None
12108}
12109
12110fn field_declaration_type_matches(
12111    declaration: &str,
12112    unit: &CodeUnit,
12113    ctx: &ScanCtx<'_>,
12114    owner: &CodeUnit,
12115) -> bool {
12116    ctx.visibility
12117        .resolves_to_type(&ctx.analyzer, ctx.file, declaration, owner)
12118        || field_type_prefix(declaration, unit.identifier()).is_some_and(|type_text| {
12119            let normalized = normalize_field_type_text(type_text);
12120            ctx.visibility
12121                .resolves_to_type(&ctx.analyzer, ctx.file, type_text, owner)
12122                || ctx.visibility.resolves_to_type(
12123                    &ctx.analyzer,
12124                    ctx.file,
12125                    normalized.as_str(),
12126                    owner,
12127                )
12128        })
12129}
12130
12131fn field_type_prefix<'a>(declaration: &'a str, field_name: &str) -> Option<&'a str> {
12132    let declaration = declaration
12133        .split(['=', ';'])
12134        .next()
12135        .unwrap_or(declaration)
12136        .trim();
12137    let index = declaration.rfind(field_name)?;
12138    let before = &declaration[..index];
12139    let after = &declaration[index + field_name.len()..];
12140    if before.chars().next_back().is_some_and(is_identifier_char)
12141        || after.chars().next().is_some_and(is_identifier_char)
12142    {
12143        return None;
12144    }
12145    Some(before.trim())
12146}
12147
12148fn normalize_field_type_text(type_text: &str) -> String {
12149    const FIELD_SPECIFIERS: [&str; 8] = [
12150        "extern ",
12151        "static ",
12152        "mutable ",
12153        "constexpr ",
12154        "constinit ",
12155        "inline ",
12156        "volatile ",
12157        "const ",
12158    ];
12159
12160    let mut normalized = normalize_type_text(type_text);
12161    loop {
12162        let Some(stripped) = FIELD_SPECIFIERS
12163            .iter()
12164            .find_map(|specifier| normalized.strip_prefix(specifier))
12165        else {
12166            return normalized;
12167        };
12168        normalized = normalize_type_text(stripped);
12169    }
12170}
12171
12172fn is_identifier_char(ch: char) -> bool {
12173    ch == '_' || ch.is_ascii_alphanumeric()
12174}
12175
12176pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
12177    let Some(type_node) = node.child_by_field_name("type") else {
12178        return false;
12179    };
12180    ctx.visibility.resolves_to_type(
12181        &ctx.analyzer,
12182        ctx.file,
12183        node_text(type_node, ctx.source),
12184        owner,
12185    )
12186}
12187
12188pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
12189    !ctx.analyzer
12190        .declarations(ctx.file)
12191        .into_iter()
12192        .filter(|unit| unit.is_function())
12193        .any(|unit| {
12194            ctx.analyzer.ranges(&unit).iter().any(|range| {
12195                node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
12196            })
12197        })
12198}
12199
12200/// How a `T var ...;` declaration initializes its object.
12201pub enum DeclarationConstructorInitializer<'tree> {
12202    /// Direct initialization, `T var(args)` or `T var{args}`: the argument list
12203    /// the declaration hands the constructor.
12204    Arguments(Node<'tree>),
12205    /// Copy initialization from one expression, `T var = expr`, which supplies a
12206    /// single constructor argument without spelling an argument list.
12207    Expression(Node<'tree>),
12208    /// `T var;`, which names no constructor argument at all.
12209    Empty,
12210}
12211
12212pub fn declaration_constructor_initializer(
12213    node: Node<'_>,
12214) -> DeclarationConstructorInitializer<'_> {
12215    let mut cursor = node.walk();
12216    for child in node.named_children(&mut cursor) {
12217        if child.kind() == "init_declarator" {
12218            let Some(value) = child
12219                .child_by_field_name("value")
12220                .or_else(|| first_named_child_of_kind(child, "initializer_list"))
12221                .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
12222            else {
12223                return DeclarationConstructorInitializer::Empty;
12224            };
12225            return match value.kind() {
12226                "argument_list" | "initializer_list" => {
12227                    DeclarationConstructorInitializer::Arguments(value)
12228                }
12229                "compound_literal_expression" => call_arguments_node(value)
12230                    .map_or(DeclarationConstructorInitializer::Empty, |arguments| {
12231                        DeclarationConstructorInitializer::Arguments(arguments)
12232                    }),
12233                _ => DeclarationConstructorInitializer::Expression(value),
12234            };
12235        }
12236        if is_declarator_node(child) {
12237            return declarator_parameters(child)
12238                .map_or(DeclarationConstructorInitializer::Empty, |parameters| {
12239                    DeclarationConstructorInitializer::Arguments(parameters)
12240                });
12241        }
12242    }
12243    DeclarationConstructorInitializer::Empty
12244}
12245
12246pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
12247    match declaration_constructor_initializer(node) {
12248        DeclarationConstructorInitializer::Arguments(arguments) => {
12249            argument_children(arguments).count()
12250        }
12251        DeclarationConstructorInitializer::Expression(_) => 1,
12252        DeclarationConstructorInitializer::Empty => 0,
12253    }
12254}
12255
12256/// The parameter list of the innermost declarator, which is where a
12257/// `T var(args)` declaration parsed as a function declarator keeps the
12258/// constructor arguments.
12259fn declarator_parameters(node: Node<'_>) -> Option<Node<'_>> {
12260    let mut current = node;
12261    loop {
12262        if let Some(parameters) = current.child_by_field_name("parameters") {
12263            return Some(parameters);
12264        }
12265        current = current.child_by_field_name("declarator")?;
12266    }
12267}
12268
12269pub(super) fn first_named_child_of_kind<'tree>(
12270    node: Node<'tree>,
12271    kind: &str,
12272) -> Option<Node<'tree>> {
12273    let mut cursor = node.walk();
12274    node.named_children(&mut cursor)
12275        .find(|child| child.kind() == kind)
12276}
12277
12278fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
12279    let mut stack = vec![root];
12280    while let Some(node) = stack.pop() {
12281        if node.kind() == kind {
12282            return Some(node);
12283        }
12284        for index in (0..node.named_child_count()).rev() {
12285            if let Some(child) = node.named_child(index) {
12286                stack.push(child);
12287            }
12288        }
12289    }
12290    None
12291}
12292
12293fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
12294    if node.kind() == "identifier" {
12295        return true;
12296    }
12297    if node.kind() == "parenthesized_expression" {
12298        return false;
12299    }
12300    if node.kind() == "call_expression" {
12301        return node
12302            .child_by_field_name("function")
12303            .is_some_and(|function| function.kind() == "identifier");
12304    }
12305    let mut stack = vec![node];
12306    while let Some(descendant) = stack.pop() {
12307        if descendant != node && descendant.kind() == "parenthesized_expression" {
12308            continue;
12309        }
12310        if descendant.kind() == "identifier" {
12311            return true;
12312        }
12313        if descendant.kind() == "call_expression" {
12314            if descendant
12315                .child_by_field_name("function")
12316                .is_some_and(|function| function.kind() == "identifier")
12317            {
12318                return true;
12319            }
12320            continue;
12321        }
12322        for index in (0..descendant.named_child_count()).rev() {
12323            if let Some(child) = descendant.named_child(index) {
12324                stack.push(child);
12325            }
12326        }
12327    }
12328    false
12329}
12330
12331fn macro_expansion_shape_is_safe(
12332    node: Node<'_>,
12333    source: &str,
12334    parameters: &[String],
12335    environment: &MacroEnvironment,
12336) -> bool {
12337    if matches!(node.kind(), "identifier" | "parenthesized_expression") {
12338        return true;
12339    }
12340    if node.kind() == "call_expression" {
12341        let Some(function) = node.child_by_field_name("function") else {
12342            return true;
12343        };
12344        if function.kind() != "identifier" {
12345            return true;
12346        }
12347        let function_name = node_text(function, source);
12348        if parameters
12349            .iter()
12350            .any(|parameter| parameter == function_name)
12351        {
12352            return false;
12353        }
12354        if !environment.may_bind(function_name) {
12355            return true;
12356        }
12357        let Some(arguments) = node.child_by_field_name("arguments") else {
12358            return false;
12359        };
12360        return argument_children(arguments).all(|argument| {
12361            if argument.kind() == "identifier"
12362                && parameters
12363                    .iter()
12364                    .any(|parameter| parameter == node_text(argument, source))
12365            {
12366                return false;
12367            }
12368            macro_expansion_shape_is_safe(argument, source, parameters, environment)
12369        });
12370    }
12371    let mut stack = vec![node];
12372    while let Some(descendant) = stack.pop() {
12373        if descendant != node {
12374            if descendant.kind() == "parenthesized_expression" {
12375                continue;
12376            }
12377            if descendant.kind() == "call_expression" {
12378                let expands = descendant
12379                    .child_by_field_name("function")
12380                    .filter(|function| function.kind() == "identifier")
12381                    .is_some_and(|function| environment.may_bind(node_text(function, source)));
12382                if expands {
12383                    return false;
12384                }
12385                continue;
12386            }
12387        }
12388        if descendant.kind() == "identifier" {
12389            let identifier = node_text(descendant, source);
12390            if parameters.iter().any(|parameter| parameter == identifier)
12391                || environment.may_bind(identifier)
12392            {
12393                return false;
12394            }
12395        }
12396        for index in (0..descendant.named_child_count()).rev() {
12397            if let Some(child) = descendant.named_child(index) {
12398                stack.push(child);
12399            }
12400        }
12401    }
12402    true
12403}
12404
12405fn structured_include_path<'a>(path: Node<'_>, source: &'a str) -> Option<&'a str> {
12406    let text = node_text(path, source);
12407    match path.kind() {
12408        "string_literal" => text.strip_prefix('"')?.strip_suffix('"'),
12409        "system_lib_string" => text.strip_prefix('<')?.strip_suffix('>'),
12410        _ => None,
12411    }
12412}
12413
12414fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
12415    let descendant = node;
12416    while let Some(parent) = node.parent() {
12417        if is_preprocessor_conditional(parent)
12418            && !is_file_covering_include_guard(parent, source)
12419            && preprocessor_conditional_contains_descendant(parent, descendant)
12420        {
12421            return true;
12422        }
12423        node = parent;
12424    }
12425    false
12426}
12427
12428/// The [`OwningPreprocessorConditionals`] of a macro event at `event`.
12429///
12430/// The descendant this walks up from is the one
12431/// [`VisibilityIndex::macro_event_condition_value`] starts from -- the
12432/// innermost node at the event's first byte, not the event node itself --
12433/// because containment compares that descendant's end against the recovered
12434/// conditional boundary, and the two nodes end in different places. An event
12435/// that [`has_preprocessor_conditional_ancestor`] rejects owns nothing: that
12436/// predicate is what has always decided whether an event is conditional at
12437/// all, and answering it first also skips the walk for the ordinary
12438/// unconditional event.
12439fn owning_preprocessor_conditionals(
12440    root: Node<'_>,
12441    event: Node<'_>,
12442    source: &str,
12443) -> OwningPreprocessorConditionals {
12444    if !has_preprocessor_conditional_ancestor(event, source) {
12445        return OwningPreprocessorConditionals::default();
12446    }
12447    let start = event.start_byte();
12448    let descendant = root
12449        .descendant_for_byte_range(start, start.saturating_add(1).min(source.len()))
12450        .expect("a byte inside the parsed tree names a descendant");
12451    let mut owners = Vec::new();
12452    let mut current = descendant.parent();
12453    while let Some(conditional) = current {
12454        if is_preprocessor_conditional(conditional)
12455            && !is_file_covering_include_guard(conditional, source)
12456            && preprocessor_conditional_contains_descendant(conditional, descendant)
12457        {
12458            owners.push(conditional.start_byte());
12459        }
12460        current = conditional.parent();
12461    }
12462    owners.into_boxed_slice()
12463}
12464
12465fn is_preprocessor_conditional(node: Node<'_>) -> bool {
12466    matches!(
12467        node.kind(),
12468        "preproc_if"
12469            | "preproc_ifdef"
12470            | "preproc_ifndef"
12471            | "preproc_elif"
12472            | "preproc_elifdef"
12473            | "preproc_else"
12474    )
12475}
12476
12477fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
12478    node.parent()
12479        .filter(|parent| parent.kind() == "translation_unit")
12480        .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
12481        && is_canonical_include_guard(node, source)
12482}
12483
12484fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
12485    if node.kind() != "preproc_ifdef"
12486        || node
12487            .child(0)
12488            .is_none_or(|directive| directive.kind() != "#ifndef")
12489        || node.child_by_field_name("alternative").is_some()
12490    {
12491        return false;
12492    }
12493    let Some(guard_name) = node.child_by_field_name("name") else {
12494        return false;
12495    };
12496    let mut cursor = node.walk();
12497    node.named_children(&mut cursor)
12498        .find(|child| *child != guard_name && child.kind() != "comment")
12499        .filter(|child| child.kind() == "preproc_def")
12500        .and_then(|definition| definition.child_by_field_name("name"))
12501        .is_some_and(|defined_name| {
12502            node_text(defined_name, source) == node_text(guard_name, source)
12503        })
12504}
12505
12506fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
12507    let mut guard = None;
12508    for index in 0..root.named_child_count() {
12509        let Some(child) = root.named_child(index) else {
12510            continue;
12511        };
12512        if child.kind() == "comment" || is_pragma_once(child, source) {
12513            continue;
12514        }
12515        if guard.is_none() && is_canonical_include_guard(child, source) {
12516            guard = Some(child);
12517        } else {
12518            return None;
12519        }
12520    }
12521    guard
12522        .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
12523        .map(|name| node_text(name, source).to_string())
12524}
12525
12526fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
12527    if (0..root.named_child_count())
12528        .filter_map(|index| root.named_child(index))
12529        .any(|child| is_pragma_once(child, source))
12530    {
12531        return MacroIncludeProtection::PragmaOnce;
12532    }
12533    top_level_canonical_include_guard_name(root, source)
12534        .map(MacroIncludeProtection::MacroGuard)
12535        .unwrap_or(MacroIncludeProtection::None)
12536}
12537
12538fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
12539    node.kind() == "preproc_call"
12540        && node
12541            .child_by_field_name("directive")
12542            .is_some_and(|directive| node_text(directive, source) == "#pragma")
12543        && node
12544            .child_by_field_name("argument")
12545            .is_some_and(|argument| node_text(argument, source).trim() == "once")
12546}
12547
12548fn parse_preproc_identifier(argument: &str) -> Option<String> {
12549    let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
12550    let mut parser = Parser::new();
12551    parser
12552        .set_language(&tree_sitter_cpp::LANGUAGE.into())
12553        .ok()?;
12554    let tree = parser.parse(&sentinel, None)?;
12555    if tree.root_node().has_error() {
12556        return None;
12557    }
12558    let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
12559    let identifier = statement.named_child(0)?;
12560    (identifier.kind() == "identifier" && statement.named_child_count() == 1)
12561        .then(|| node_text(identifier, &sentinel).to_string())
12562}
12563
12564pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
12565    match node.kind() {
12566        "identifier" | "field_identifier" => {
12567            let name = node_text(node, source).trim();
12568            (!name.is_empty()).then(|| name.to_string())
12569        }
12570        "abstract_array_declarator"
12571        | "abstract_function_declarator"
12572        | "abstract_parenthesized_declarator"
12573        | "abstract_pointer_declarator"
12574        | "abstract_reference_declarator" => None,
12575        "function_declarator" => node
12576            .child_by_field_name("declarator")
12577            .or_else(|| node.child_by_field_name("name"))
12578            .and_then(|child| extract_variable_name(child, source)),
12579        _ => node
12580            .child_by_field_name("declarator")
12581            .or_else(|| node.child_by_field_name("name"))
12582            .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
12583            .and_then(|child| extract_variable_name(child, source)),
12584    }
12585}
12586
12587/// Whether `file` is proven to use plain-C source semantics.
12588///
12589/// `Language::Cpp` intentionally serves both C and C++. Headers do not carry a
12590/// compilation dialect on their own, so only an exact `.c` source extension is
12591/// sufficient to reinterpret C++-grammar keyword nodes such as `this` as C
12592/// identifiers.
12593///
12594/// The exact-lowercase-`.c` rule itself lives in [`LanguageDialect::for_path`],
12595/// which extraction reads too (a `.c` file is extracted with C tag scope), so
12596/// the doctrine has exactly one definition.
12597pub fn is_c_source_file(file: &ProjectFile) -> bool {
12598    LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
12599}
12600
12601/// Whether tree-sitter parsed the operand of C `sizeof(T)` as an expression
12602/// identifier even though `T` may denote a typedef.
12603///
12604/// The grammar cannot distinguish `sizeof(value)` from `sizeof(Type)` without
12605/// semantic information. Keep this helper structural and narrow; callers must
12606/// still prove a visible type and reject an active ordinary-namespace shadow.
12607pub fn is_c_sizeof_expression_type_candidate(file: &ProjectFile, node: Node<'_>) -> bool {
12608    if !is_c_source_file(file) || node.kind() != "identifier" {
12609        return false;
12610    }
12611    let mut operand = node;
12612    while let Some(parent) = operand.parent().filter(|parent| {
12613        parent.kind() == "parenthesized_expression"
12614            && parent.named_child_count() == 1
12615            && parent.named_child(0) == Some(operand)
12616    }) {
12617        operand = parent;
12618    }
12619    operand.parent().is_some_and(|parent| {
12620        parent.kind() == "sizeof_expression" && parent.child_by_field_name("value") == Some(operand)
12621    })
12622}
12623
12624/// Return the type and member leaves of a C `offsetof` member designator.
12625///
12626/// `offsetof_expression` is a dedicated tree-sitter node, so its two operands
12627/// must be interpreted through their named fields.  In particular, do not
12628/// infer the aggregate from the enclosing lexical scope: an `offsetof` can
12629/// name a member of an unrelated aggregate, including a field promoted from
12630/// an anonymous union.  A missing or unsupported operand is deliberately
12631/// rejected so callers can keep the reference unresolved.
12632pub fn c_offsetof_member_parts(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
12633    if node.kind() != "field_identifier" {
12634        return None;
12635    }
12636    let expression = node.parent().filter(|parent| {
12637        parent.kind() == "offsetof_expression" && parent.child_by_field_name("member") == Some(node)
12638    })?;
12639    if expression.has_error() {
12640        return None;
12641    }
12642    let closing = expression.child(expression.child_count().saturating_sub(1))?;
12643    if closing.kind() != ")" || closing.is_missing() {
12644        return None;
12645    }
12646    let type_descriptor = expression.child_by_field_name("type")?;
12647    if type_descriptor.kind() != "type_descriptor"
12648        || type_descriptor.is_missing()
12649        || type_descriptor.has_error()
12650    {
12651        return None;
12652    }
12653    let type_specifier = type_descriptor.child_by_field_name("type")?;
12654    if type_specifier.is_missing() || type_specifier.has_error() {
12655        return None;
12656    }
12657    let type_reference = match type_specifier.kind() {
12658        "class_specifier" | "struct_specifier" | "union_specifier" => {
12659            type_specifier.child_by_field_name("name")?
12660        }
12661        _ => type_specifier,
12662    };
12663    (!type_reference.is_missing() && !type_reference.has_error()).then_some((type_reference, node))
12664}
12665
12666/// Whether `node` is the member leaf of an `offsetof_expression`, including a
12667/// malformed type operand.  Callers use this guard to prevent the ordinary
12668/// field-name heuristics from guessing an owner after structured resolution
12669/// has failed.
12670pub fn is_c_offsetof_member_node(node: Node<'_>) -> bool {
12671    node.kind() == "field_identifier"
12672        && node.parent().is_some_and(|parent| {
12673            parent.kind() == "offsetof_expression"
12674                && parent.child_by_field_name("member") == Some(node)
12675        })
12676}
12677
12678/// Whether `node` is a template argument name that tree-sitter spelled with
12679/// type syntax.
12680///
12681/// The grammar cannot tell a type argument from a non-type (value) argument, so
12682/// it gives both the same shape:
12683/// `template_argument_list -> type_descriptor -> type_identifier`. In
12684/// `std::array<W, N>` the type `W` and the constant `N` parse identically, and
12685/// so do `std::span<const uint8_t, ED448_LEN>`'s length and a nested type
12686/// member used as a real type argument.
12687///
12688/// This helper reports only the syntactic position. A caller must still prove
12689/// which namespace explains the spelling: forward navigation asks the type
12690/// namespace first and reads the leaf as a value only when no type explains it,
12691/// and the inverse field scan admits the leaf only when no visible type does
12692/// (#2556).
12693pub fn is_type_shaped_template_argument_name(node: Node<'_>) -> bool {
12694    if node.kind() != "type_identifier" {
12695        return false;
12696    }
12697    let Some(descriptor) = node
12698        .parent()
12699        .filter(|parent| parent.kind() == "type_descriptor")
12700    else {
12701        return false;
12702    };
12703    if descriptor.child_by_field_name("type") != Some(node) {
12704        return false;
12705    }
12706    let Some(arguments) = descriptor
12707        .parent()
12708        .filter(|parent| parent.kind() == "template_argument_list")
12709    else {
12710        return false;
12711    };
12712    arguments.parent().is_some_and(|owner| {
12713        matches!(
12714            owner.kind(),
12715            "template_type" | "template_function" | "template_method"
12716        ) && owner.child_by_field_name("arguments") == Some(arguments)
12717    })
12718}
12719
12720/// Whether a reference written in `file` reads C++ source with C semantics.
12721///
12722/// [`is_c_source_file`] answers the half a path settles on its own. The other
12723/// half is a header, which has no dialect of its own: it is read as C exactly
12724/// when every workspace translation unit that provably compiles it compiles it
12725/// as C ([`CppSource::header_uses_c_semantics`], issue #1970).
12726///
12727/// This is the gate for anything that is really about the compilation
12728/// language of the code being read -- which reading of an included header's
12729/// declarations is in scope, whether `this` is an ordinary identifier. It is
12730/// NOT the gate for a question that is genuinely about a `.c` file on disk;
12731/// those keep calling [`is_c_source_file`].
12732pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
12733    is_c_source_file(file) || cpp.header_uses_c_semantics(file)
12734}
12735
12736pub fn is_declarator_node(node: Node<'_>) -> bool {
12737    matches!(
12738        node.kind(),
12739        "identifier"
12740            | "field_identifier"
12741            | "pointer_declarator"
12742            | "reference_declarator"
12743            | "array_declarator"
12744            | "parenthesized_declarator"
12745            | "function_declarator"
12746    )
12747}
12748
12749/// One run of a container's children that tree-sitter parsed outside the
12750/// namespaces that really enclose it, with the namespaces that do.
12751#[derive(Clone, Debug, PartialEq, Eq)]
12752pub struct RecoveredNamespaceRegion {
12753    /// Start byte of the first child in the run.
12754    pub start: usize,
12755    /// End byte of the last child in the run.
12756    pub end: usize,
12757    /// The complete enclosing namespace path of the run, outermost first: the
12758    /// namespaces the parse tree still attributes to the container, then the
12759    /// ones recovery dropped.
12760    pub components: Vec<String>,
12761}
12762
12763/// The namespaces C++ parse recovery drops from a file's tree.
12764///
12765/// When tree-sitter cannot parse a construct inside a namespace body it closes
12766/// an inner scope with a MISSING brace, or skips an opening brace into an
12767/// ERROR node. Every real `}` after that then closes one scope too early: a
12768/// class body's `}` closes the namespace, the namespace's own `}` closes its
12769/// parent, and the outermost real closes land in a trailing ERROR node. The
12770/// declarations between a stolen close and the real one keep their byte
12771/// positions but lose their `namespace_definition` ancestors (Catch2's
12772/// `catch_matchers_templated.hpp`, issue #1537).
12773///
12774/// Braces balance in source that compiles, so the lost scopes are exactly what
12775/// a brace stack over tree-sitter's own tokens leaves open. Within one node, in
12776/// child order: a real `{` opens a scope (the node's namespace when the node is
12777/// a named namespace body, otherwise an opaque scope); a real `}` closes the
12778/// innermost open scope, or is owed to the parent when the node has none open;
12779/// a MISSING brace is not a token and does nothing; a child without errors is
12780/// balanced and contributes nothing; an error-marked child contributes the
12781/// closes it owes and the scopes it leaves open. Whatever is open before a
12782/// child starts, beyond the node's own scope, is what the parse lost for that
12783/// child. Consecutive children with the same lost namespaces form one
12784/// [`RecoveredNamespaceRegion`]. A file without parse errors has no regions.
12785#[derive(Clone, Debug, Default)]
12786pub struct OrphanedNamespaceScopeIndex {
12787    regions: Vec<RecoveredNamespaceRegion>,
12788}
12789
12790impl OrphanedNamespaceScopeIndex {
12791    pub fn build(root: Node<'_>, source: &str) -> Self {
12792        if !root.has_error() {
12793            return Self::default();
12794        }
12795        struct Frame<'tree> {
12796            node: Node<'tree>,
12797            children: std::vec::IntoIter<Node<'tree>>,
12798            /// The enclosing namespace path of this node's children before
12799            /// this node's own scopes: the parent's path plus what was open in
12800            /// the parent when this node started.
12801            scope: Vec<String>,
12802            /// The namespace this node's own real `{` opens; empty when the
12803            /// node is not a named namespace body.
12804            own_scope: Vec<String>,
12805            /// Scopes opened inside this node and still open, innermost last.
12806            /// A namespace carries its name components; an opaque scope (a
12807            /// class body, a function body, a brace inside an ERROR) is empty.
12808            open: Vec<Vec<String>>,
12809            /// Whether `open[0]` is this node's own scope.
12810            own_open: bool,
12811            /// Real closes seen with nothing open here; the parent closes them.
12812            owed: usize,
12813            /// The run of children currently sharing the same lost namespaces.
12814            run: Option<RecoveredNamespaceRegion>,
12815        }
12816        fn frame<'tree>(
12817            node: Node<'tree>,
12818            scope: Vec<String>,
12819            own_scope: Vec<String>,
12820        ) -> Frame<'tree> {
12821            let mut cursor = node.walk();
12822            let children = node.children(&mut cursor).collect::<Vec<_>>().into_iter();
12823            Frame {
12824                node,
12825                children,
12826                scope,
12827                own_scope,
12828                open: Vec::new(),
12829                own_open: false,
12830                owed: 0,
12831                run: None,
12832            }
12833        }
12834        let mut regions = Vec::new();
12835        let mut frames = vec![frame(root, Vec::new(), Vec::new())];
12836        while let Some(current) = frames.last_mut() {
12837            let Some(child) = current.children.next() else {
12838                let done = frames.pop().expect("the frame just borrowed");
12839                regions.extend(done.run);
12840                let Some(parent) = frames.last_mut() else {
12841                    break;
12842                };
12843                for _ in 0..done.owed {
12844                    if parent.open.pop().is_none() {
12845                        parent.owed += 1;
12846                    }
12847                }
12848                parent.own_open &= !parent.open.is_empty();
12849                parent.open.extend(done.open);
12850                continue;
12851            };
12852            match child.kind() {
12853                "{" if !child.is_missing() => {
12854                    let own = current.open.is_empty();
12855                    current.open.push(if own {
12856                        current.own_scope.clone()
12857                    } else {
12858                        Vec::new()
12859                    });
12860                    current.own_open |= own;
12861                    continue;
12862                }
12863                "}" if !child.is_missing() => {
12864                    if current.open.pop().is_none() {
12865                        current.owed += 1;
12866                    }
12867                    // The node's own close, or a close that reached it once
12868                    // every lost scope was popped: nothing of this node's is
12869                    // open for the children that follow.
12870                    current.own_open &= !current.open.is_empty();
12871                    continue;
12872                }
12873                _ => {}
12874            }
12875            let lost = &current.open[usize::from(current.own_open)..];
12876            let child_scope = current
12877                .scope
12878                .iter()
12879                .chain(current.open.iter().flatten())
12880                .cloned()
12881                .collect::<Vec<_>>();
12882            if lost.iter().any(|scope| !scope.is_empty()) {
12883                match &mut current.run {
12884                    Some(run) if run.components == child_scope => run.end = child.end_byte(),
12885                    run => {
12886                        regions.extend(run.take());
12887                        *run = Some(RecoveredNamespaceRegion {
12888                            start: child.start_byte(),
12889                            end: child.end_byte(),
12890                            components: child_scope.clone(),
12891                        });
12892                    }
12893                }
12894            } else {
12895                regions.extend(current.run.take());
12896            }
12897            if child.has_error() {
12898                let own_scope = namespace_body_name_components(current.node, child, source);
12899                frames.push(frame(child, child_scope, own_scope));
12900            }
12901        }
12902        Self { regions }
12903    }
12904
12905    pub fn is_empty(&self) -> bool {
12906        self.regions.is_empty()
12907    }
12908
12909    /// The bytes this index holds, for the analyzer cache's weight.
12910    pub fn approximate_size(&self) -> usize {
12911        self.regions.iter().fold(0usize, |total, region| {
12912            total
12913                .saturating_add(std::mem::size_of::<RecoveredNamespaceRegion>())
12914                .saturating_add(region.components.iter().map(String::len).sum::<usize>())
12915        })
12916    }
12917
12918    /// The innermost recovered region containing `byte`.
12919    pub fn region_at(&self, byte: usize) -> Option<&RecoveredNamespaceRegion> {
12920        self.regions
12921            .iter()
12922            .filter(|region| region.start <= byte && byte < region.end)
12923            .min_by_key(|region| region.end - region.start)
12924    }
12925
12926    /// The enclosing namespaces of `node`, outermost first, restoring the ones
12927    /// parse recovery dropped from its ancestor chain. The one answer both
12928    /// lookup directions and declaration collection use (issue #1537).
12929    pub fn enclosing_namespace_components(&self, node: Node<'_>, source: &str) -> Vec<String> {
12930        let mut parsed = Vec::new();
12931        let mut current = node.parent();
12932        while let Some(parent) = current {
12933            if parent.kind() == "namespace_definition"
12934                && let Some(name) = parent.child_by_field_name("name")
12935            {
12936                let mut components = Vec::new();
12937                if append_cpp_name_components(name, source, &mut components).is_some() {
12938                    parsed.push((parent.start_byte(), components));
12939                }
12940            }
12941            current = parent.parent();
12942        }
12943        parsed.reverse();
12944        self.restore_enclosing_namespaces(parsed, node.start_byte())
12945    }
12946
12947    /// [`Self::enclosing_namespace_components`] for a caller that has already
12948    /// climbed the ancestor chain: `parsed` lists the node's named
12949    /// `namespace_definition` ancestors outermost first, each with its start
12950    /// byte. A region covering the node supplies every namespace outside it;
12951    /// only the parsed ancestors that start inside the region still apply.
12952    pub fn restore_enclosing_namespaces(
12953        &self,
12954        parsed: Vec<(usize, Vec<String>)>,
12955        node_start: usize,
12956    ) -> Vec<String> {
12957        let Some(region) = self.region_at(node_start) else {
12958            return parsed
12959                .into_iter()
12960                .flat_map(|(_, components)| components)
12961                .collect();
12962        };
12963        region
12964            .components
12965            .iter()
12966            .cloned()
12967            .chain(
12968                parsed
12969                    .into_iter()
12970                    .filter(|(start, _)| *start >= region.start)
12971                    .flat_map(|(_, components)| components),
12972            )
12973            .collect()
12974    }
12975}
12976
12977/// The name components of the namespace whose body `body` is, or empty when
12978/// `body` is not the body of a named `namespace_definition` `parent`.
12979fn namespace_body_name_components(parent: Node<'_>, body: Node<'_>, source: &str) -> Vec<String> {
12980    let mut components = Vec::new();
12981    if body.kind() == "declaration_list"
12982        && parent.kind() == "namespace_definition"
12983        && parent.child_by_field_name("body") == Some(body)
12984        && let Some(name) = parent.child_by_field_name("name")
12985        && append_cpp_name_components(name, source, &mut components).is_none()
12986    {
12987        components.clear();
12988    }
12989    components
12990}
12991
12992#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12993pub enum RecoveredDeclaratorTypeContext {
12994    Declaration,
12995    FunctionDefinition,
12996    Parameter,
12997}
12998
12999/// Recognize a real type displaced into a qualified declarator by parser
13000/// recovery.
13001///
13002/// Tree-sitter parses `API Result *make(Arg);` as if `API` were the declared
13003/// type and `Result` were the scope of a qualified declarator with a missing
13004/// `::`. A template return such as `API Result<T> make()` uses a
13005/// `template_type` for the same recovered scope. The same recovery occurs for
13006/// macro-prefixed definitions, extern variables, and macro-decorated
13007/// parameters (`f(MACRO T* p)`, where the parameter's own `type` field takes
13008/// the macro). Keep this intentionally structural: the recovered scope must
13009/// have the grammar's missing separator, the qualified node must occupy the
13010/// declaration's declarator chain, a separate nonempty type must occupy the
13011/// normal type field, and the recovered name must unwrap to a real declarator
13012/// name.
13013pub fn recovered_macro_decorated_declarator_type(
13014    node: Node<'_>,
13015) -> Option<RecoveredDeclaratorTypeContext> {
13016    recovered_macro_decorated_type_node(node).map(|(_, context)| context)
13017}
13018
13019/// Return the declaration/function `type` displaced by a macro-shaped
13020/// qualified declarator, together with the enclosing declaration context.
13021/// Callers use the macro scope only as structural admission evidence; the
13022/// returned node is the real type reference to resolve and record.
13023pub fn recovered_macro_decorated_type_node(
13024    node: Node<'_>,
13025) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
13026    if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
13027        return None;
13028    }
13029    let qualified = node.parent()?;
13030    if qualified.kind() != "qualified_identifier"
13031        || qualified.child_by_field_name("scope") != Some(node)
13032        || !(0..qualified.child_count())
13033            .filter_map(|index| qualified.child(index))
13034            .any(|child| child.kind() == "::" && child.is_missing())
13035    {
13036        return None;
13037    }
13038    if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
13039        return None;
13040    }
13041
13042    let (declaration, context) = recovered_declarator_container(qualified)?;
13043    let type_node = declaration
13044        .child_by_field_name("type")
13045        .filter(|type_node| {
13046            *type_node != qualified
13047                && !type_node.is_missing()
13048                && type_node.start_byte() != type_node.end_byte()
13049        })?;
13050    Some((type_node, context))
13051}
13052
13053fn recovered_declarator_container(
13054    mut declarator: Node<'_>,
13055) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
13056    loop {
13057        let parent = declarator.parent()?;
13058        if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
13059            return Some((
13060                parent
13061                    .parent()
13062                    .filter(|declaration| declaration.kind() == "declaration")?,
13063                RecoveredDeclaratorTypeContext::Declaration,
13064            ));
13065        }
13066        if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
13067            return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
13068        }
13069        if parent.kind() == "function_definition"
13070            && has_field_child(parent, "declarator", declarator)
13071        {
13072            return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
13073        }
13074        // `f(MACRO T* p)` recovers exactly like `MACRO T *make(...)` does, one
13075        // level down: the parameter's `type` field takes the macro token and
13076        // the real type `T` becomes the recovered scope of the declarator.
13077        // Declining here left every xxhash `XXH_NOESCAPE` parameter with no
13078        // candidate at all (#1830).
13079        if matches!(
13080            parent.kind(),
13081            "parameter_declaration" | "optional_parameter_declaration"
13082        ) && has_field_child(parent, "declarator", declarator)
13083        {
13084            return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
13085        }
13086        if !matches!(
13087            parent.kind(),
13088            "array_declarator"
13089                | "function_declarator"
13090                | "parenthesized_declarator"
13091                | "pointer_declarator"
13092                | "pointer_type_declarator"
13093                | "reference_declarator"
13094        ) || !has_field_child(parent, "declarator", declarator)
13095        {
13096            return None;
13097        }
13098        declarator = parent;
13099    }
13100}
13101
13102fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
13103    let mut cursor = parent.walk();
13104    parent
13105        .children_by_field_name(field, &mut cursor)
13106        .any(|child| child == target)
13107}
13108
13109fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
13110    loop {
13111        if node.is_missing() || node.start_byte() == node.end_byte() {
13112            return false;
13113        }
13114        match node.kind() {
13115            "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
13116                return true;
13117            }
13118            "array_declarator"
13119            | "function_declarator"
13120            | "parenthesized_declarator"
13121            | "pointer_declarator"
13122            | "pointer_type_declarator"
13123            | "reference_declarator" => {
13124                let Some(declarator) = node.child_by_field_name("declarator") else {
13125                    return false;
13126                };
13127                node = declarator;
13128            }
13129            _ => return false,
13130        }
13131    }
13132}
13133
13134/// Aggregate-owner proof for a structurally recognized designated initializer.
13135pub enum DesignatedInitializerOwner {
13136    Resolved(CodeUnit),
13137    Unresolved,
13138}
13139
13140/// Recognize a designated-initializer field and, when possible, resolve its
13141/// aggregate owner.
13142///
13143/// Covers both the grammar's ordinary `field_designator` shape and the exact
13144/// recovery used for `.field = value` after a preprocessor-split array
13145/// initializer. Nested aggregate levels are deliberately left unresolved unless
13146/// the single outer level is the containing array initializer: resolving those
13147/// would require following the enclosing field's declared type. `None` means the
13148/// node is not a designator at all; an unresolved designator remains classified so
13149/// callers cannot fall through to unrelated global/member heuristics.
13150pub fn designated_initializer_owner(
13151    visibility: &VisibilityIndex<'_>,
13152    file: &ProjectFile,
13153    source: &str,
13154    node: Node<'_>,
13155) -> Option<DesignatedInitializerOwner> {
13156    if let Some(designator) = node
13157        .parent()
13158        .filter(|parent| parent.kind() == "field_designator")
13159    {
13160        let pair = designator.parent()?;
13161        if pair.kind() != "initializer_pair"
13162            || pair.child_by_field_name("designator") != Some(designator)
13163        {
13164            return None;
13165        }
13166        let initializer = pair.parent()?;
13167        if initializer.kind() != "initializer_list" {
13168            return None;
13169        }
13170        return Some(classified_designated_owner(initializer_list_owner(
13171            visibility,
13172            file,
13173            source,
13174            initializer,
13175        )));
13176    }
13177
13178    let init_declarator = node.parent()?;
13179    if init_declarator.child_by_field_name("declarator") != Some(node)
13180        || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
13181    {
13182        return None;
13183    }
13184    Some(classified_designated_owner(declaration_owner(
13185        visibility,
13186        file,
13187        source,
13188        init_declarator.parent()?,
13189    )))
13190}
13191
13192fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
13193    owner.map_or(
13194        DesignatedInitializerOwner::Unresolved,
13195        DesignatedInitializerOwner::Resolved,
13196    )
13197}
13198
13199fn initializer_list_owner(
13200    visibility: &VisibilityIndex<'_>,
13201    file: &ProjectFile,
13202    source: &str,
13203    initializer: Node<'_>,
13204) -> Option<CodeUnit> {
13205    let mut current = initializer;
13206    let mut outer_initializer_lists = 0usize;
13207    loop {
13208        let parent = current.parent()?;
13209        match parent.kind() {
13210            "initializer_pair" => return None,
13211            "initializer_list" => {
13212                outer_initializer_lists += 1;
13213                if outer_initializer_lists > 1 {
13214                    return None;
13215                }
13216                current = parent;
13217            }
13218            "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
13219                let declaration = parent.parent()?;
13220                if outer_initializer_lists == 1
13221                    && !parent
13222                        .child_by_field_name("declarator")
13223                        .is_some_and(contains_array_declarator)
13224                {
13225                    return None;
13226                }
13227                return declaration_owner(visibility, file, source, declaration);
13228            }
13229            "compound_literal_expression"
13230                if parent.child_by_field_name("value") == Some(current)
13231                    && outer_initializer_lists == 0 =>
13232            {
13233                let type_node = parent.child_by_field_name("type")?;
13234                return resolve_designated_owner_type(visibility, file, source, type_node);
13235            }
13236            "ERROR" => current = parent,
13237            _ => return None,
13238        }
13239    }
13240}
13241
13242fn declaration_owner(
13243    visibility: &VisibilityIndex<'_>,
13244    file: &ProjectFile,
13245    source: &str,
13246    declaration: Node<'_>,
13247) -> Option<CodeUnit> {
13248    if !matches!(declaration.kind(), "declaration" | "field_declaration") {
13249        return None;
13250    }
13251    let type_node = declaration
13252        .child_by_field_name("type")
13253        .or_else(|| first_type_child(declaration))?;
13254    resolve_designated_owner_type(visibility, file, source, type_node)
13255}
13256
13257fn resolve_designated_owner_type(
13258    visibility: &VisibilityIndex<'_>,
13259    file: &ProjectFile,
13260    source: &str,
13261    type_node: Node<'_>,
13262) -> Option<CodeUnit> {
13263    let type_name = normalize_type_text(node_text(type_node, source));
13264    visibility
13265        .resolve_type(file, &type_name)
13266        .filter(CodeUnit::is_class)
13267}
13268
13269fn contains_array_declarator(declarator: Node<'_>) -> bool {
13270    let mut stack = vec![declarator];
13271    while let Some(node) = stack.pop() {
13272        if node.kind() == "array_declarator" {
13273            return true;
13274        }
13275        if matches!(node.kind(), "initializer_list" | "compound_statement") {
13276            continue;
13277        }
13278        let mut cursor = node.walk();
13279        stack.extend(node.named_children(&mut cursor));
13280    }
13281    false
13282}
13283
13284pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
13285    let mut cursor = node.walk();
13286    node.named_children(&mut cursor).find(|child| {
13287        matches!(
13288            child.kind(),
13289            "type_identifier"
13290                | "primitive_type"
13291                | "qualified_identifier"
13292                | "scoped_type_identifier"
13293                | "struct_specifier"
13294                | "union_specifier"
13295                | "enum_specifier"
13296        )
13297    })
13298}
13299
13300pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
13301    visibility: &VisibilityIndex<'_>,
13302    file: &ProjectFile,
13303    source: &str,
13304    declarator: Node<'_>,
13305    type_text: Option<&str>,
13306    bindings: &LocalInferenceEngine<T>,
13307) -> bool {
13308    if !has_ancestor_kind(declarator, "compound_statement") {
13309        return false;
13310    }
13311    if declarator
13312        .child_by_field_name("declarator")
13313        .is_none_or(|declarator| declarator.kind() != "identifier")
13314    {
13315        return false;
13316    }
13317    if !type_text
13318        .and_then(|text| visibility.resolve_type(file, text))
13319        .is_some_and(|unit| unit.is_class())
13320    {
13321        return false;
13322    }
13323    declarator
13324        .child_by_field_name("parameters")
13325        .is_some_and(|parameters| {
13326            constructor_parameters_look_like_expressions(parameters, source, bindings)
13327        })
13328}
13329
13330fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
13331    parameters: Node<'_>,
13332    source: &str,
13333    bindings: &LocalInferenceEngine<T>,
13334) -> bool {
13335    let mut cursor = parameters.walk();
13336    parameters.named_children(&mut cursor).any(|parameter| {
13337        !matches!(
13338            parameter.kind(),
13339            "parameter_declaration" | "optional_parameter_declaration"
13340        ) || parameter_declaration_is_local_expression(parameter, source, bindings)
13341    })
13342}
13343
13344fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
13345    parameter: Node<'_>,
13346    source: &str,
13347    bindings: &LocalInferenceEngine<T>,
13348) -> bool {
13349    let text = node_text(parameter, source).trim();
13350    if text
13351        .chars()
13352        .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
13353        && bindings.is_shadowed(text)
13354    {
13355        return true;
13356    }
13357
13358    let Some(base) = parameter
13359        .child_by_field_name("type")
13360        .filter(|base| base.kind() == "type_identifier")
13361    else {
13362        return false;
13363    };
13364    let Some(subscript) = parameter
13365        .child_by_field_name("declarator")
13366        .filter(|declarator| declarator.kind() == "abstract_array_declarator")
13367    else {
13368        return false;
13369    };
13370    subscript.child_by_field_name("size").is_some()
13371        && bindings.is_shadowed(node_text(base, source).trim())
13372}
13373
13374pub fn is_declaration_name(node: Node<'_>) -> bool {
13375    let Some(parent) = node.parent() else {
13376        return false;
13377    };
13378    if parent
13379        .child_by_field_name("name")
13380        .is_some_and(|name| same_node(name, node))
13381    {
13382        if matches!(
13383            parent.kind(),
13384            "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
13385        ) {
13386            return cpp_tag_specifier_declares_name(parent);
13387        }
13388        if matches!(
13389            parent.kind(),
13390            "namespace_definition"
13391                | "namespace_alias_definition"
13392                | "alias_declaration"
13393                | "enumerator"
13394        ) {
13395            return true;
13396        }
13397    }
13398
13399    let mut current = Some(parent);
13400    while let Some(ancestor) = current {
13401        let type_definition = ancestor.kind() == "type_definition";
13402        let mut declarator_cursor = ancestor.walk();
13403        if ancestor
13404            .children_by_field_name("declarator", &mut declarator_cursor)
13405            .any(|declarator| declarator_name_path_contains(declarator, node, type_definition))
13406        {
13407            return true;
13408        }
13409        if matches!(
13410            ancestor.kind(),
13411            "declaration"
13412                | "field_declaration"
13413                | "parameter_declaration"
13414                | "optional_parameter_declaration"
13415                | "function_definition"
13416                | "type_definition"
13417                | "alias_declaration"
13418                | "class_specifier"
13419                | "struct_specifier"
13420                | "union_specifier"
13421                | "enum_specifier"
13422        ) {
13423            return false;
13424        }
13425        current = ancestor.parent();
13426    }
13427    false
13428}
13429
13430/// Whether tree-sitter recovered a qualified friend-class type as an ordinary
13431/// declaration's declarator inside a malformed class body.
13432///
13433/// An export macro between `class` and the class name can make the containing
13434/// body parse as a function body. A source declaration such as
13435/// `friend class internal::Friend;` then retains this exact structure:
13436/// `declaration(type: friend, ERROR(class), declarator: internal::Friend)`.
13437/// The declarator is a type reference despite its field role.
13438pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
13439    if !matches!(
13440        node.kind(),
13441        "qualified_identifier" | "scoped_type_identifier"
13442    ) {
13443        return false;
13444    }
13445    let Some(declaration) = node
13446        .parent()
13447        .filter(|parent| parent.kind() == "declaration")
13448    else {
13449        return false;
13450    };
13451    if declaration.child_by_field_name("declarator") != Some(node)
13452        || !declaration
13453            .child_by_field_name("type")
13454            .is_some_and(|friend| {
13455                friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
13456            })
13457    {
13458        return false;
13459    }
13460    let mut cursor = declaration.walk();
13461    let mut errors = declaration
13462        .named_children(&mut cursor)
13463        .filter(|child| child.kind() == "ERROR");
13464    let Some(error) = errors.next() else {
13465        return false;
13466    };
13467    errors.next().is_none()
13468        && error.named_child_count() == 1
13469        && error.named_child(0).is_some_and(|class| {
13470            class.kind() == "identifier" && node_text(class, source) == "class"
13471        })
13472}
13473
13474pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
13475    if !matches!(node.kind(), "identifier" | "field_identifier") {
13476        return false;
13477    }
13478    if let Some(parent) = node.parent() {
13479        if parent.kind() == "call_expression"
13480            && parent.child_by_field_name("function") == Some(node)
13481        {
13482            return false;
13483        }
13484        if matches!(parent.kind(), "labeled_statement" | "goto_statement")
13485            && parent.child_by_field_name("label") == Some(node)
13486        {
13487            return false;
13488        }
13489    }
13490    if is_declaration_name(node) {
13491        return false;
13492    }
13493    let mut current = node.parent();
13494    while let Some(ancestor) = current {
13495        match ancestor.kind() {
13496            "preproc_ifdef" | "preproc_ifndef" => {
13497                if ancestor
13498                    .child_by_field_name("name")
13499                    .is_some_and(|name| node_range_contains(name, node))
13500                {
13501                    return false;
13502                }
13503            }
13504            "preproc_if" | "preproc_elif" => {
13505                if ancestor
13506                    .child_by_field_name("condition")
13507                    .is_some_and(|condition| node_range_contains(condition, node))
13508                {
13509                    return false;
13510                }
13511            }
13512            "preproc_else" => {}
13513            kind if kind.starts_with("preproc_") => return false,
13514            _ => {}
13515        }
13516        if matches!(
13517            ancestor.kind(),
13518            "translation_unit" | "function_definition" | "compound_statement"
13519        ) {
13520            break;
13521        }
13522        current = ancestor.parent();
13523    }
13524    true
13525}
13526
13527fn node_range_contains(outer: Node<'_>, inner: Node<'_>) -> bool {
13528    outer.start_byte() <= inner.start_byte() && inner.end_byte() <= outer.end_byte()
13529}
13530
13531fn recovered_c_reference_node(
13532    visibility: &VisibilityIndex<'_>,
13533    file: &ProjectFile,
13534    node: Node<'_>,
13535    source: &str,
13536) -> bool {
13537    if node.start_byte() >= node.end_byte()
13538        || node.is_error()
13539        || node.is_missing()
13540        || !matches!(
13541            node.kind(),
13542            "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
13543        )
13544        || recovered_c_macro_binding_role(node)
13545        || recovered_c_label_role(node)
13546    {
13547        return false;
13548    }
13549    // A declaration name can be an identifier child of a recovered ERROR
13550    // (for example `EFI_STATUS Encode()` in C files parsed as C++). Do not
13551    // let a same-named macro turn that binder into a reference. An
13552    // assignment whose C callee follows the recovered `explicit` token is
13553    // the one expression-shaped exception: the generic declarator walk sees
13554    // its function_declarator as a declaration path, but the ERROR sibling
13555    // proves it is a call.
13556    let name = node_text(node, source);
13557    let recovered_function_call =
13558        recovered_c_function_declarator_call(visibility, file, node, name);
13559    let recovered_macro_call = recovered_c_function_declarator_invocation(node)
13560        && visibility.macro_name_may_be_bound_at(file, name, node.start_byte());
13561    let recovered_parenthesized_reference = recovered_c_parenthesized_declarator_reference(node);
13562    if is_declaration_name(node)
13563        && !recovered_c_explicit_assignment_callee(visibility, file, node, name)
13564        && !recovered_function_call
13565        && !recovered_macro_call
13566        && !recovered_parenthesized_reference
13567    {
13568        return false;
13569    }
13570
13571    if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
13572        return true;
13573    }
13574    if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
13575        return true;
13576    }
13577    if recovered_parenthesized_reference {
13578        return true;
13579    }
13580    if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
13581        if recovered_function_call {
13582            return true;
13583        }
13584        return visibility
13585            .visible_identifier_candidates(file, name)
13586            .any(|candidate| {
13587                candidate.is_class() || candidate.is_module() || is_type_alias(candidate)
13588            });
13589    }
13590    let visible = visibility
13591        .visible_identifier_candidates(file, name)
13592        .next()
13593        .is_some();
13594    visible
13595        && (recovered_c_reference_anchor(node)
13596            || recovered_c_error_expression_leaf(node)
13597            || recovered_function_call)
13598}
13599
13600fn push_recovered_c_range(
13601    ranges: &mut Vec<Range>,
13602    seen: &mut HashSet<(usize, usize)>,
13603    start_byte: usize,
13604    end_byte: usize,
13605    node: Node<'_>,
13606    limit: usize,
13607) -> bool {
13608    if start_byte >= end_byte || !seen.insert((start_byte, end_byte)) {
13609        return true;
13610    }
13611    if ranges.len() >= limit {
13612        return false;
13613    }
13614    ranges.push(Range {
13615        start_byte,
13616        end_byte,
13617        start_line: node.start_position().row,
13618        end_line: node.end_position().row,
13619    });
13620    true
13621}
13622
13623/// A reference leaf can sit directly beneath an ERROR while its ERROR parent
13624/// is still attached to a real expression (most often a recovered macro call
13625/// argument). The expression parent is the structured proof; an unindexed
13626/// identifier beneath a bare recovery envelope has no such proof.
13627fn recovered_c_error_expression_leaf(node: Node<'_>) -> bool {
13628    let mut current = node.parent();
13629    while let Some(parent) = current {
13630        if parent.is_error() {
13631            let Some(anchor) = parent.parent() else {
13632                return false;
13633            };
13634            return anchor.kind().ends_with("_expression")
13635                || matches!(
13636                    anchor.kind(),
13637                    "argument_list"
13638                        | "return_statement"
13639                        | "expression_statement"
13640                        | "case_statement"
13641                        | "initializer_list"
13642                        | "field_designator"
13643                        | "enumerator"
13644                );
13645        }
13646        if matches!(
13647            parent.kind(),
13648            "translation_unit" | "function_definition" | "compound_statement"
13649        ) {
13650            return false;
13651        }
13652        current = parent.parent();
13653    }
13654    false
13655}
13656
13657/// C recovery may represent a call as `identifier > function_declarator >
13658/// ERROR > compound_statement`. This shape is only a call when the malformed
13659/// declarator is attached to a real function body and the name is an indexed
13660/// visible callable. A declaration's `ERROR > function_declarator >
13661/// declaration` shape deliberately fails this test.
13662fn recovered_c_function_declarator_call(
13663    visibility: &VisibilityIndex<'_>,
13664    file: &ProjectFile,
13665    node: Node<'_>,
13666    name: &str,
13667) -> bool {
13668    if !matches!(
13669        node.kind(),
13670        "identifier" | "field_identifier" | "type_identifier"
13671    ) {
13672        return false;
13673    }
13674    recovered_c_function_declarator_invocation(node)
13675        && visibility
13676            .visible_identifier_candidates(file, name)
13677            .any(CodeUnit::is_function)
13678}
13679
13680/// Return whether a C identifier belongs to a call-shaped declarator that the
13681/// C++ grammar put under an `ERROR` node.
13682///
13683/// The malformed call can be direct (`f(arg)`) or nested in a parameter
13684/// declaration when one of its arguments looks like a type (`f(TYPE, value)`).
13685/// In both cases the CST retains the function-declarator and its enclosing
13686/// recovery envelope. We walk only those declarator/parameter nodes and stop
13687/// at a real expression-bearing boundary; declarations therefore cannot pass
13688/// this predicate merely because they have a parameter list.
13689fn recovered_c_function_declarator_invocation(node: Node<'_>) -> bool {
13690    let function_declarator = if node.parent().is_some_and(|parent| {
13691        parent.kind() == "function_declarator"
13692            && parent.child_by_field_name("declarator") == Some(node)
13693    }) {
13694        node.parent().expect("checked function declarator parent")
13695    } else {
13696        let Some(parameter) = node.parent().filter(|parent| {
13697            parent.kind() == "parameter_declaration"
13698                && parent.child_by_field_name("type") == Some(node)
13699        }) else {
13700            return false;
13701        };
13702        if !parameter
13703            .child_by_field_name("declarator")
13704            .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
13705        {
13706            return false;
13707        }
13708        let Some(parameters) = parameter
13709            .parent()
13710            .filter(|parent| parent.kind() == "parameter_list")
13711        else {
13712            return false;
13713        };
13714        let Some(function_declarator) = parameters
13715            .parent()
13716            .filter(|parent| parent.kind() == "function_declarator")
13717        else {
13718            return false;
13719        };
13720        function_declarator
13721    };
13722
13723    let Some(mut current) = function_declarator
13724        .parent()
13725        .filter(|parent| parent.is_error())
13726    else {
13727        return false;
13728    };
13729    loop {
13730        let Some(parent) = current.parent() else {
13731            return false;
13732        };
13733        if matches!(
13734            parent.kind(),
13735            "translation_unit"
13736                | "compound_statement"
13737                | "preproc_if"
13738                | "preproc_ifdef"
13739                | "preproc_ifndef"
13740                | "preproc_else"
13741                | "preproc_elif"
13742        ) {
13743            return true;
13744        }
13745        if parent.is_error()
13746            || matches!(
13747                parent.kind(),
13748                "parameter_declaration"
13749                    | "parameter_list"
13750                    | "function_declarator"
13751                    | "abstract_function_declarator"
13752                    | "parenthesized_declarator"
13753            )
13754        {
13755            current = parent;
13756            continue;
13757        }
13758        return false;
13759    }
13760}
13761
13762/// C permits an identifier named `typename`. The C++ grammar can recover an
13763/// assignment using that identifier as a declaration whose declarator is a
13764/// parenthesized argument list, for example `typename = f(ctx, value)`. Only
13765/// the argument retained beneath the nested `ERROR` is a reference; sibling
13766/// declarator identifiers remain binders/grammar artifacts.
13767fn recovered_c_parenthesized_declarator_reference(node: Node<'_>) -> bool {
13768    let Some(error) = node.parent().filter(|parent| parent.is_error()) else {
13769        return false;
13770    };
13771    if error.named_child_count() != 1 || error.named_child(0) != Some(node) {
13772        return false;
13773    }
13774    let Some(declarator) = error
13775        .parent()
13776        .filter(|parent| parent.kind() == "parenthesized_declarator")
13777    else {
13778        return false;
13779    };
13780    let Some(declaration) = declarator
13781        .parent()
13782        .filter(|parent| parent.kind() == "declaration")
13783    else {
13784        return false;
13785    };
13786    if declaration.child_by_field_name("declarator") != Some(declarator) {
13787        return false;
13788    }
13789    let Some(type_node) = declaration.child_by_field_name("type") else {
13790        return false;
13791    };
13792    type_node.kind() == "dependent_type"
13793        && type_node
13794            .child(0)
13795            .is_some_and(|keyword| keyword.kind() == "typename")
13796}
13797
13798fn recovered_c_explicit_assignment_callee(
13799    visibility: &VisibilityIndex<'_>,
13800    file: &ProjectFile,
13801    node: Node<'_>,
13802    name: &str,
13803) -> bool {
13804    let mut current = node;
13805    let error = loop {
13806        let Some(parent) = current.parent() else {
13807            return false;
13808        };
13809        if parent.is_error() {
13810            break parent;
13811        }
13812        current = parent;
13813    };
13814    let mut cursor = error.walk();
13815    let explicit_recovery_precedes_callee = error
13816        .named_children(&mut cursor)
13817        .take_while(|child| child.start_byte() < node.start_byte())
13818        .any(|child| child.kind() == "explicit_function_specifier");
13819    if !explicit_recovery_precedes_callee {
13820        return false;
13821    }
13822    visibility
13823        .visible_identifier_candidates(file, name)
13824        .any(CodeUnit::is_function)
13825}
13826
13827fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
13828    while let Some(parent) = node.parent() {
13829        if matches!(
13830            parent.kind(),
13831            "preproc_def" | "preproc_function_def" | "preproc_params"
13832        ) {
13833            return true;
13834        }
13835        if parent.is_error()
13836            || matches!(
13837                parent.kind(),
13838                "translation_unit" | "function_definition" | "compound_statement"
13839            )
13840        {
13841            return false;
13842        }
13843        node = parent;
13844    }
13845    false
13846}
13847
13848fn recovered_c_label_role(node: Node<'_>) -> bool {
13849    node.parent().is_some_and(|parent| {
13850        matches!(parent.kind(), "labeled_statement" | "goto_statement")
13851            && parent.child_by_field_name("label") == Some(node)
13852    })
13853}
13854
13855fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
13856    while let Some(parent) = node.parent() {
13857        if parent.is_error() {
13858            return false;
13859        }
13860        // A C macro call recovered as a function declarator can parse an
13861        // assignment-shaped argument as an optional parameter. Its
13862        // `default_value` field remains an expression role even though the
13863        // surrounding call shape is beneath ERROR.
13864        if parent.kind() == "optional_parameter_declaration"
13865            && parent
13866                .child_by_field_name("default_value")
13867                .is_some_and(|value| node_range_contains(value, node))
13868        {
13869            return true;
13870        }
13871        if parent.kind().ends_with("_expression")
13872            || matches!(
13873                parent.kind(),
13874                "argument_list"
13875                    | "return_statement"
13876                    | "expression_statement"
13877                    | "case_statement"
13878                    | "initializer_list"
13879                    | "init_declarator"
13880                    | "array_declarator"
13881                    | "field_designator"
13882                    | "enumerator"
13883            )
13884        {
13885            return true;
13886        }
13887        if matches!(
13888            parent.kind(),
13889            "translation_unit"
13890                | "function_definition"
13891                | "compound_statement"
13892                | "declaration"
13893                | "field_declaration"
13894                | "parameter_declaration"
13895        ) {
13896            return false;
13897        }
13898        node = parent;
13899    }
13900    false
13901}
13902
13903/// Whether a parameter declaration belongs to the callable scope whose body can
13904/// contain references to it.
13905///
13906/// Error recovery can wrap a macro-decorated class body in a synthetic outer
13907/// `function_definition`. Merely finding any callable ancestor would then leak
13908/// parameters from member prototypes into later member bodies. Require the
13909/// parameter to be inside that definition's own declarator instead.
13910pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
13911    let mut current = parameter.parent();
13912    while let Some(ancestor) = current {
13913        if ancestor.kind() == "lambda_expression" {
13914            return ancestor
13915                .child_by_field_name("declarator")
13916                .is_some_and(|declarator| {
13917                    declarator.start_byte() <= parameter.start_byte()
13918                        && parameter.end_byte() <= declarator.end_byte()
13919                });
13920        }
13921        if ancestor.kind() == "function_definition" {
13922            return ancestor
13923                .child_by_field_name("declarator")
13924                .is_some_and(|declarator| {
13925                    declarator.start_byte() <= parameter.start_byte()
13926                        && parameter.end_byte() <= declarator.end_byte()
13927                });
13928        }
13929        current = ancestor.parent();
13930    }
13931    false
13932}
13933
13934pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
13935    let mut current = node.parent();
13936    while let Some(ancestor) = current {
13937        if matches!(
13938            ancestor.kind(),
13939            "parameter_declaration" | "optional_parameter_declaration"
13940        ) {
13941            return ancestor
13942                .child_by_field_name("type")
13943                .is_some_and(|type_node| {
13944                    type_node.start_byte() <= node.start_byte()
13945                        && node.end_byte() <= type_node.end_byte()
13946                });
13947        }
13948        if matches!(
13949            ancestor.kind(),
13950            "function_definition" | "lambda_expression" | "compound_statement"
13951        ) {
13952            return false;
13953        }
13954        current = ancestor.parent();
13955    }
13956    false
13957}
13958
13959fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
13960    if specifier.child_by_field_name("body").is_some() {
13961        return true;
13962    }
13963    let mut current = specifier.parent();
13964    while let Some(ancestor) = current {
13965        match ancestor.kind() {
13966            "type_descriptor"
13967            | "parameter_declaration"
13968            | "optional_parameter_declaration"
13969            | "template_argument_list"
13970            | "cast_expression" => return false,
13971            "declaration" | "field_declaration" => {
13972                let mut cursor = ancestor.walk();
13973                return ancestor
13974                    .children_by_field_name("declarator", &mut cursor)
13975                    .next()
13976                    .is_none();
13977            }
13978            "translation_unit" => return true,
13979            _ => current = ancestor.parent(),
13980        }
13981    }
13982    false
13983}
13984
13985pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
13986    match node.kind() {
13987        "identifier"
13988        | "field_identifier"
13989        | "qualified_identifier"
13990        | "scoped_identifier"
13991        | "operator_name"
13992        | "destructor_name"
13993        | "literal_operator_name" => Some(node),
13994        "reference_declarator" | "parenthesized_declarator" => {
13995            node.named_child(0).and_then(declarator_name_node)
13996        }
13997        _ => node
13998            .child_by_field_name("declarator")
13999            .or_else(|| node.child_by_field_name("name"))
14000            .or_else(|| node.child_by_field_name("field"))
14001            .and_then(declarator_name_node),
14002    }
14003}
14004
14005fn declarator_name_path_contains(
14006    declarator: Node<'_>,
14007    candidate: Node<'_>,
14008    allow_type_identifier: bool,
14009) -> bool {
14010    let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
14011        return false;
14012    };
14013    let mut current = Some(declarator);
14014    while let Some(node) = current {
14015        if same_node(node, candidate) {
14016            return true;
14017        }
14018        if same_node(node, name) {
14019            return false;
14020        }
14021        current = node
14022            .child_by_field_name("declarator")
14023            .or_else(|| node.child_by_field_name("name"))
14024            .or_else(|| node.child_by_field_name("field"));
14025    }
14026    false
14027}
14028
14029fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
14030    match node.kind() {
14031        "identifier"
14032        | "field_identifier"
14033        | "operator_name"
14034        | "destructor_name"
14035        | "literal_operator_name" => Some(node),
14036        "type_identifier" if allow_type_identifier => Some(node),
14037        _ => node
14038            .child_by_field_name("declarator")
14039            .or_else(|| node.child_by_field_name("name"))
14040            .or_else(|| node.child_by_field_name("field"))
14041            .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
14042    }
14043}
14044
14045/// True when `node` is a component of a larger structured type node whose outer
14046/// range is the single reference surfaced to callers.
14047pub fn is_nested_type_node(node: Node<'_>) -> bool {
14048    node.parent().is_some_and(|parent| {
14049        matches!(
14050            parent.kind(),
14051            "qualified_identifier" | "scoped_type_identifier" | "template_type"
14052        )
14053    })
14054}
14055
14056pub struct OutOfLineMemberDefinitionOwners<'tree> {
14057    pub owners: Vec<(Node<'tree>, CodeUnit)>,
14058    innermost: Option<(Node<'tree>, CodeUnit)>,
14059}
14060
14061impl OutOfLineMemberDefinitionOwners<'_> {
14062    pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
14063        self.innermost.as_ref().map(|(node, owner)| (*node, owner))
14064    }
14065}
14066
14067pub struct QualifiedOwnerComponents<'tree> {
14068    pub nodes: Vec<Node<'tree>>,
14069    pub names: Vec<String>,
14070    pub global: bool,
14071}
14072
14073/// True when each structured qualifier on the callable-name path has a real
14074/// `::` token. A macro-prefixed return type can make tree-sitter insert a
14075/// zero-width missing separator and parse `TYPE Result<T> method()` as the
14076/// false qualified declarator `Result<T>::method`.
14077pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
14078    let mut stack = vec![node];
14079    let mut found_separator = false;
14080    while let Some(current) = stack.pop() {
14081        if !matches!(
14082            current.kind(),
14083            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
14084        ) {
14085            continue;
14086        }
14087        let mut current_has_separator = false;
14088        for index in 0..current.child_count() {
14089            let Some(child) = current.child(index) else {
14090                continue;
14091            };
14092            if child.kind() == "::" {
14093                if child.is_missing() {
14094                    return false;
14095                }
14096                current_has_separator = true;
14097                found_separator = true;
14098            }
14099        }
14100        if !current_has_separator {
14101            return false;
14102        }
14103        for field in ["scope", "name"] {
14104            if let Some(child) = current.child_by_field_name(field)
14105                && matches!(
14106                    child.kind(),
14107                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
14108                )
14109            {
14110                stack.push(child);
14111            }
14112        }
14113    }
14114    found_separator
14115}
14116
14117pub fn qualified_owner_components<'tree>(
14118    node: Node<'tree>,
14119    source: &str,
14120) -> Option<QualifiedOwnerComponents<'tree>> {
14121    if !qualified_name_has_concrete_scope_separators(node) {
14122        return None;
14123    }
14124    let mut nodes = cpp_name_component_nodes(node)?;
14125    nodes.pop()?;
14126    if nodes.is_empty() {
14127        return None;
14128    }
14129    let names = nodes
14130        .iter()
14131        .map(|component| node_text(*component, source).to_string())
14132        .collect();
14133    Some(QualifiedOwnerComponents {
14134        nodes,
14135        names,
14136        global: is_globally_qualified_cpp_name(node),
14137    })
14138}
14139
14140pub fn out_of_line_member_definition_owner<'tree>(
14141    analyzer: &CppGraphSource<'_>,
14142    visibility: &VisibilityIndex<'_>,
14143    file: &ProjectFile,
14144    source: &str,
14145    node: Node<'tree>,
14146) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
14147    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
14148        || !has_ancestor_kind(node, "function_definition")
14149        || !is_function_declarator_name_root(node)
14150    {
14151        return None;
14152    }
14153    let qualified = qualified_owner_components(node, source)?;
14154    let lexical_scope = enclosing_namespace_components(node, source)?;
14155    let mut owners = Vec::new();
14156    let mut innermost = None;
14157
14158    for component_count in 1..=qualified.names.len() {
14159        if let LexicalTypeResolution::Resolved { unit, .. } = visibility
14160            .resolve_type_components_lexically(
14161                analyzer,
14162                file,
14163                &qualified.names[..component_count],
14164                qualified.global,
14165                &lexical_scope,
14166            )
14167            && !owners
14168                .iter()
14169                .any(|(_, existing)| same_visible_symbol(existing, &unit))
14170        {
14171            if component_count == qualified.names.len() {
14172                innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
14173            }
14174            owners.push((qualified.nodes[component_count - 1], unit));
14175        }
14176    }
14177
14178    // The C++ analyzer has already reconciled an indexed out-of-line callable
14179    // against the include-visible class table. Consult that canonical owner
14180    // chain only when ordinary lexical lookup could not recover the innermost
14181    // owner.  A one-segment qualifier is safe here only when the enclosing
14182    // indexed callable has an authoritative class owner and the parser's
14183    // namespace path is a (possibly sparse) subsequence of that owner path.
14184    // The latter is what lets macro-wrapped namespace sentinels recover a
14185    // missing `time_internal`/`cord_internal` component without guessing an
14186    // unrelated short name.
14187    if innermost.is_none() {
14188        let indexed_owner_components = visibility
14189            .indexed_enclosing_owner_scope(analyzer, file, node)
14190            .or_else(|| {
14191                // Retain the legacy rendered-name fallback for the existing
14192                // multi-segment path when an enclosing owner chain is not
14193                // available (for example, cache-loaded units without parent
14194                // links).  One-segment recovery must stay canonical-only.
14195                if qualified.names.len() <= 1 {
14196                    return None;
14197                }
14198                let range = Range {
14199                    start_byte: node.start_byte(),
14200                    end_byte: node.end_byte(),
14201                    start_line: node.start_position().row,
14202                    end_line: node.end_position().row,
14203                };
14204                let start = analyzer.enclosing_code_unit(file, &range)?;
14205                let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
14206                    brokk_bifrost_core::analyzer::Language::Cpp,
14207                    &cpp_name_for(&start),
14208                );
14209                components.pop();
14210                Some(components)
14211            });
14212        if let Some(indexed_owner_components) = indexed_owner_components
14213            && indexed_owner_components.len() > qualified.names.len()
14214            && indexed_owner_components.ends_with(&qualified.names)
14215            && indexed_namespace_path_is_recoverable(
14216                &lexical_scope,
14217                &indexed_owner_components,
14218                qualified.names.len(),
14219            )
14220            // A globally-qualified one-segment owner is an explicit request
14221            // for the top-level binding; do not reinterpret it as a missing
14222            // namespace component.  Existing multi-segment global lookups
14223            // retain their historical indexed recovery.
14224            && (qualified.names.len() > 1 || !qualified.global)
14225        {
14226            let namespace_count = indexed_owner_components.len() - qualified.names.len();
14227            for component_count in 1..=qualified.names.len() {
14228                let expected = &indexed_owner_components[..namespace_count + component_count];
14229                let owner_node = qualified.nodes[component_count - 1];
14230                for owner in visibility
14231                    .visible_identifier_candidates(file, &qualified.names[component_count - 1])
14232                    .filter(|candidate| candidate.is_class())
14233                    .filter(|candidate| {
14234                        canonical_cpp_scope_components(candidate) == expected
14235                            && visibility.external_type_candidate_visible_in_context(
14236                                analyzer, file, candidate, node,
14237                            )
14238                    })
14239                {
14240                    if component_count == qualified.names.len() && innermost.is_none() {
14241                        innermost = Some((owner_node, owner.clone()));
14242                    }
14243                    if !owners
14244                        .iter()
14245                        .any(|(_, existing)| same_symbol(existing, owner))
14246                    {
14247                        owners.push((owner_node, owner.clone()));
14248                    }
14249                }
14250            }
14251        }
14252    }
14253    (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
14254}
14255
14256fn is_function_declarator_name_root(node: Node<'_>) -> bool {
14257    let mut current = node;
14258    while let Some(parent) = current.parent() {
14259        if parent.kind() == "function_declarator" {
14260            return parent.child_by_field_name("declarator") == Some(current);
14261        }
14262        if matches!(
14263            parent.kind(),
14264            "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
14265        ) && parent.child_by_field_name("declarator") == Some(current)
14266        {
14267            current = parent;
14268            continue;
14269        }
14270        return false;
14271    }
14272    false
14273}
14274
14275pub fn append_cpp_name_components(
14276    node: Node<'_>,
14277    source: &str,
14278    out: &mut Vec<String>,
14279) -> Option<()> {
14280    out.extend(
14281        cpp_name_component_nodes(node)?
14282            .into_iter()
14283            .map(|component| node_text(component, source).to_string()),
14284    );
14285    Some(())
14286}
14287
14288pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
14289    let mut components = Vec::new();
14290    append_cpp_name_components(node, source, &mut components)?;
14291    Some(components)
14292}
14293
14294/// Resolve a structured type spelling from an object-like macro replacement
14295/// when definition-site source order has no answer.
14296///
14297/// Macro replacement tokens are looked up where the macro is expanded, so a
14298/// type declared later in the defining header can still be their destination.
14299/// Without expanding every invocation, accept only one include-visible logical
14300/// class or alias whose structured path ends in the replacement components.
14301/// An ordinary lexical answer always takes precedence at the call site.
14302pub fn unique_macro_replacement_type_candidate(
14303    analyzer: &CppGraphSource<'_>,
14304    visibility: &VisibilityIndex<'_>,
14305    file: &ProjectFile,
14306    components: &[String],
14307) -> Option<CodeUnit> {
14308    let terminal = components.last()?;
14309    let mut candidates = Vec::new();
14310    for candidate in visibility
14311        .visible_identifier_candidates(file, terminal)
14312        .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
14313        .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
14314    {
14315        if !candidates
14316            .iter()
14317            .any(|existing| same_logical_symbol(existing, candidate))
14318        {
14319            candidates.push(candidate.clone());
14320        }
14321    }
14322    (candidates.len() == 1).then(|| candidates.remove(0))
14323}
14324
14325/// The base scopes named by member using-declarations for `member` in one
14326/// class source range.
14327///
14328/// The grammar supplies the qualified identifier and each component. Keep
14329/// this interpretation shared between forward overload lookup and inverse
14330/// owner routing rather than reparsing a rendered `Base::member` string at
14331/// either call site.
14332pub fn cpp_member_using_declaration_scopes(source: &str, member: &str) -> Vec<String> {
14333    let mut parser = Parser::new();
14334    if parser
14335        .set_language(&tree_sitter_cpp::LANGUAGE.into())
14336        .is_err()
14337    {
14338        return Vec::new();
14339    }
14340    let Some(tree) = parser.parse(source, None) else {
14341        return Vec::new();
14342    };
14343    let mut scopes = Vec::new();
14344    let mut pending = vec![tree.root_node()];
14345    while let Some(node) = pending.pop() {
14346        if node.kind() == "using_declaration" {
14347            let Some(imported) = node.named_child(0) else {
14348                continue;
14349            };
14350            let Some(mut components) = cpp_type_name_components(imported, source) else {
14351                continue;
14352            };
14353            if components.pop().as_deref() == Some(member) && !components.is_empty() {
14354                scopes.push(components.join("::"));
14355            }
14356            continue;
14357        }
14358        for index in (0..node.named_child_count()).rev() {
14359            if let Some(child) = node.named_child(index) {
14360                pending.push(child);
14361            }
14362        }
14363    }
14364    scopes
14365}
14366
14367/// Whether a structured using-declaration scope can name `qualified` as an
14368/// ancestor class. The boundary check prevents `Base` from matching
14369/// `OtherBase` while allowing a relative `Base` spelling to match `ns::Base`.
14370pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
14371    qualified == scope
14372        || qualified
14373            .strip_suffix(scope)
14374            .is_some_and(|prefix| prefix.ends_with("::"))
14375}
14376
14377/// Whether `node` is the direct structured type payload of a template
14378/// argument. This role remains meaningful even when a surrounding expression
14379/// is below tree-sitter recovery, because both the `template_argument_list`
14380/// and the `type_descriptor` retain their named fields.
14381pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
14382    let Some(type_descriptor) = node.parent() else {
14383        return false;
14384    };
14385    if type_descriptor.kind() != "type_descriptor"
14386        || type_descriptor.child_by_field_name("type") != Some(node)
14387    {
14388        return false;
14389    }
14390    let Some(arguments) = type_descriptor.parent() else {
14391        return false;
14392    };
14393    if arguments.kind() != "template_argument_list" {
14394        return false;
14395    }
14396    arguments.parent().is_some_and(|parent| {
14397        matches!(parent.kind(), "template_type" | "template_function")
14398            && parent.child_by_field_name("arguments") == Some(arguments)
14399    })
14400}
14401
14402pub fn cpp_template_reference_arguments(
14403    mut node: Node<'_>,
14404    source: &str,
14405) -> Option<Vec<CppTemplateExpression>> {
14406    loop {
14407        match node.kind() {
14408            "template_type" | "template_function" => {
14409                let arguments = node.child_by_field_name("arguments")?;
14410                let mut cursor = arguments.walk();
14411                return Some(
14412                    arguments
14413                        .named_children(&mut cursor)
14414                        .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
14415                        .map(|argument| CppTemplateExpression {
14416                            text: normalize_cpp_whitespace(node_text(argument, source)),
14417                            // One template term from a resolver query; see `ParentIndex::unindexed`.
14418                            term: cpp_template_term(
14419                                argument,
14420                                source,
14421                                &[],
14422                                &ParentIndex::unindexed(),
14423                            ),
14424                        })
14425                        .collect(),
14426                );
14427            }
14428            "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
14429                node = node
14430                    .child_by_field_name("name")
14431                    .or_else(|| node.child_by_field_name("type"))?;
14432            }
14433            _ => return None,
14434        }
14435    }
14436}
14437
14438fn cpp_reconcile_primary_template_parameters(
14439    candidates: &[(&CodeUnit, &CppTemplateMetadata)],
14440    preferred: &CodeUnit,
14441) -> Option<Vec<CppTemplateParameterMetadata>> {
14442    let canonical = candidates
14443        .iter()
14444        .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
14445    let mut merged = canonical
14446        .parameters
14447        .iter()
14448        .map(|parameter| CppTemplateParameterMetadata {
14449            name: parameter.name.clone(),
14450            kind: parameter.kind,
14451            variadic: parameter.variadic,
14452            default: None,
14453        })
14454        .collect::<Vec<_>>();
14455
14456    for (_, metadata) in candidates {
14457        if metadata.parameters.len() != merged.len() {
14458            return None;
14459        }
14460        let rename_bindings = metadata
14461            .parameters
14462            .iter()
14463            .zip(&merged)
14464            .map(|(parameter, canonical)| {
14465                (
14466                    parameter.name.clone(),
14467                    CppTemplateTerm::Parameter(canonical.name.clone()),
14468                )
14469            })
14470            .collect::<HashMap<_, _>>();
14471        for ((parameter, canonical), merged_parameter) in metadata
14472            .parameters
14473            .iter()
14474            .zip(&canonical.parameters)
14475            .zip(&mut merged)
14476        {
14477            if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
14478                return None;
14479            }
14480            let Some(default) = &parameter.default else {
14481                continue;
14482            };
14483            let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
14484            if let Some(existing) = &merged_parameter.default {
14485                if !cpp_template_terms_equal(&existing.term, &normalized_term) {
14486                    return None;
14487                }
14488            } else {
14489                merged_parameter.default = Some(CppTemplateExpression {
14490                    text: default.text.clone(),
14491                    term: normalized_term,
14492                });
14493            }
14494        }
14495    }
14496    Some(merged)
14497}
14498
14499pub fn cpp_bind_template_arguments(
14500    parameters: &[CppTemplateParameterMetadata],
14501    explicit_arguments: &[CppTemplateExpression],
14502) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
14503    let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
14504    if variadic_index.is_some_and(|index| {
14505        index + 1 != parameters.len()
14506            || parameters[index + 1..]
14507                .iter()
14508                .any(|parameter| parameter.variadic)
14509    }) {
14510        return None;
14511    }
14512    let fixed_count = variadic_index.unwrap_or(parameters.len());
14513    if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
14514        return None;
14515    }
14516    let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
14517    let mut expanded = explicit_arguments[..explicit_fixed_count]
14518        .iter()
14519        .map(cpp_clone_template_expression_iterative)
14520        .collect::<Vec<_>>();
14521    let mut bindings = HashMap::default();
14522    for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
14523        bindings.insert(
14524            parameter.name.clone(),
14525            cpp_clone_template_term_iterative(&argument.term),
14526        );
14527    }
14528    for parameter in &parameters[explicit_fixed_count..fixed_count] {
14529        let default = parameter.default.as_ref()?;
14530        let term = cpp_substitute_template_term(&default.term, &bindings)?;
14531        bindings.insert(parameter.name.clone(), term.clone());
14532        expanded.push(CppTemplateExpression {
14533            text: default.text.clone(),
14534            term,
14535        });
14536    }
14537    if let Some(index) = variadic_index {
14538        let packed_arguments = &explicit_arguments[explicit_fixed_count..];
14539        expanded.extend(
14540            packed_arguments
14541                .iter()
14542                .map(cpp_clone_template_expression_iterative),
14543        );
14544        bindings.insert(
14545            parameters[index].name.clone(),
14546            CppTemplateTerm::Node {
14547                kind: "parameter_pack".to_string(),
14548                children: packed_arguments
14549                    .iter()
14550                    .map(|argument| cpp_clone_template_term_iterative(&argument.term))
14551                    .collect(),
14552            },
14553        );
14554    }
14555    Some((expanded, bindings))
14556}
14557
14558fn cpp_specialization_matches(
14559    metadata: &CppTemplateMetadata,
14560    arguments: &[CppTemplateExpression],
14561) -> bool {
14562    if metadata.specialization_arguments.len() != arguments.len() {
14563        return false;
14564    }
14565    let parameter_names = metadata
14566        .parameters
14567        .iter()
14568        .map(|parameter| parameter.name.as_str())
14569        .collect::<HashSet<_>>();
14570    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
14571    for (pattern, argument) in metadata.specialization_arguments.iter().zip(arguments) {
14572        if !cpp_unify_template_term(
14573            &pattern.term,
14574            &argument.term,
14575            &parameter_names,
14576            &mut bindings,
14577        ) {
14578            return false;
14579        }
14580    }
14581    true
14582}
14583
14584fn cpp_specialization_more_specialized(
14585    candidate: &CppTemplateMetadata,
14586    other: &CppTemplateMetadata,
14587) -> bool {
14588    cpp_specialization_pattern_accepts(other, candidate)
14589        && !cpp_specialization_pattern_accepts(candidate, other)
14590}
14591
14592fn cpp_specialization_pattern_accepts(
14593    broader: &CppTemplateMetadata,
14594    narrower: &CppTemplateMetadata,
14595) -> bool {
14596    if broader.specialization_arguments.len() != narrower.specialization_arguments.len() {
14597        return false;
14598    }
14599    let parameter_names = broader
14600        .parameters
14601        .iter()
14602        .map(|parameter| parameter.name.as_str())
14603        .collect::<HashSet<_>>();
14604    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
14605    broader
14606        .specialization_arguments
14607        .iter()
14608        .zip(&narrower.specialization_arguments)
14609        .all(|(pattern, argument)| {
14610            cpp_unify_template_term(
14611                &pattern.term,
14612                &argument.term,
14613                &parameter_names,
14614                &mut bindings,
14615            )
14616        })
14617}
14618
14619pub fn cpp_substitute_template_term(
14620    term: &CppTemplateTerm,
14621    bindings: &HashMap<String, CppTemplateTerm>,
14622) -> Option<CppTemplateTerm> {
14623    enum Work<'a> {
14624        Visit(&'a CppTemplateTerm),
14625        Build { kind: String, child_count: usize },
14626    }
14627
14628    let mut work = vec![Work::Visit(term)];
14629    let mut substituted = Vec::new();
14630    while let Some(next) = work.pop() {
14631        match next {
14632            Work::Visit(CppTemplateTerm::Parameter(name)) => {
14633                substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
14634            }
14635            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
14636                substituted.push(CppTemplateTerm::Atom {
14637                    kind: kind.clone(),
14638                    text: text.clone(),
14639                });
14640            }
14641            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
14642                work.push(Work::Build {
14643                    kind: kind.clone(),
14644                    child_count: children.len(),
14645                });
14646                work.extend(children.iter().rev().map(Work::Visit));
14647            }
14648            Work::Build { kind, child_count } => {
14649                let children = substituted.split_off(substituted.len() - child_count);
14650                substituted.push(CppTemplateTerm::Node { kind, children });
14651            }
14652        }
14653    }
14654    substituted.pop()
14655}
14656
14657pub fn cpp_substitute_template_arguments(
14658    arguments: &[CppTemplateExpression],
14659    bindings: &HashMap<String, CppTemplateTerm>,
14660) -> Option<Vec<CppTemplateExpression>> {
14661    let mut substituted = Vec::new();
14662    for argument in arguments {
14663        let CppTemplateTerm::Node { kind, children } = &argument.term else {
14664            substituted.push(CppTemplateExpression {
14665                text: argument.text.clone(),
14666                term: cpp_substitute_template_term(&argument.term, bindings)?,
14667            });
14668            continue;
14669        };
14670        if kind != "parameter_pack_expansion" {
14671            substituted.push(CppTemplateExpression {
14672                text: argument.text.clone(),
14673                term: cpp_substitute_template_term(&argument.term, bindings)?,
14674            });
14675            continue;
14676        }
14677        let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
14678            return None;
14679        };
14680        if ellipsis != "..." {
14681            return None;
14682        }
14683
14684        let mut pack_names = Vec::new();
14685        let mut work = vec![pattern];
14686        while let Some(term) = work.pop() {
14687            match term {
14688                CppTemplateTerm::Parameter(name)
14689                    if matches!(
14690                        bindings.get(name),
14691                        Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
14692                    ) =>
14693                {
14694                    if !pack_names.contains(name) {
14695                        pack_names.push(name.clone());
14696                    }
14697                }
14698                CppTemplateTerm::Node { children, .. } => work.extend(children),
14699                CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
14700            }
14701        }
14702        let first_pack = pack_names.first()?;
14703        let CppTemplateTerm::Node {
14704            children: first_elements,
14705            ..
14706        } = bindings.get(first_pack)?
14707        else {
14708            return None;
14709        };
14710        let pack_len = first_elements.len();
14711        for pack_name in &pack_names {
14712            let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
14713                return None;
14714            };
14715            if children.len() != pack_len {
14716                return None;
14717            }
14718        }
14719        for index in 0..pack_len {
14720            let mut element_bindings = bindings.clone();
14721            for pack_name in &pack_names {
14722                let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
14723                    return None;
14724                };
14725                element_bindings.insert(
14726                    pack_name.clone(),
14727                    cpp_clone_template_term_iterative(&children[index]),
14728                );
14729            }
14730            substituted.push(CppTemplateExpression {
14731                text: argument.text.clone(),
14732                term: cpp_substitute_template_term(pattern, &element_bindings)?,
14733            });
14734        }
14735    }
14736    Some(substituted)
14737}
14738
14739fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
14740    enum Work<'a> {
14741        Visit(&'a CppTemplateTerm),
14742        Build { kind: String, child_count: usize },
14743    }
14744
14745    let mut work = vec![Work::Visit(term)];
14746    let mut cloned = Vec::new();
14747    while let Some(next) = work.pop() {
14748        match next {
14749            Work::Visit(CppTemplateTerm::Parameter(name)) => {
14750                cloned.push(CppTemplateTerm::Parameter(name.clone()));
14751            }
14752            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
14753                cloned.push(CppTemplateTerm::Atom {
14754                    kind: kind.clone(),
14755                    text: text.clone(),
14756                });
14757            }
14758            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
14759                work.push(Work::Build {
14760                    kind: kind.clone(),
14761                    child_count: children.len(),
14762                });
14763                work.extend(children.iter().rev().map(Work::Visit));
14764            }
14765            Work::Build { kind, child_count } => {
14766                let children = cloned.split_off(cloned.len() - child_count);
14767                cloned.push(CppTemplateTerm::Node { kind, children });
14768            }
14769        }
14770    }
14771    cloned
14772        .pop()
14773        .expect("template term traversal emits one root")
14774}
14775
14776fn cpp_clone_template_expression_iterative(
14777    expression: &CppTemplateExpression,
14778) -> CppTemplateExpression {
14779    CppTemplateExpression {
14780        text: expression.text.clone(),
14781        term: cpp_clone_template_term_iterative(&expression.term),
14782    }
14783}
14784
14785pub fn cpp_unify_template_term(
14786    pattern: &CppTemplateTerm,
14787    argument: &CppTemplateTerm,
14788    parameters: &HashSet<&str>,
14789    bindings: &mut HashMap<String, CppTemplateTerm>,
14790) -> bool {
14791    let mut work = vec![(pattern, argument)];
14792    while let Some((pattern, argument)) = work.pop() {
14793        match pattern {
14794            CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
14795                if let Some(bound) = bindings.get(name) {
14796                    if !cpp_template_terms_equal(bound, argument) {
14797                        return false;
14798                    }
14799                } else {
14800                    bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
14801                }
14802            }
14803            CppTemplateTerm::Atom {
14804                kind: pattern_kind,
14805                text: pattern_text,
14806            } => {
14807                if !matches!(
14808                    argument,
14809                    CppTemplateTerm::Atom { kind, text }
14810                        if kind == pattern_kind && text == pattern_text
14811                ) {
14812                    return false;
14813                }
14814            }
14815            CppTemplateTerm::Node {
14816                kind: pattern_kind,
14817                children: pattern_children,
14818            } => {
14819                let CppTemplateTerm::Node { kind, children } = argument else {
14820                    return false;
14821                };
14822                if kind != pattern_kind || children.len() != pattern_children.len() {
14823                    return false;
14824                }
14825                work.extend(pattern_children.iter().zip(children).rev());
14826            }
14827            CppTemplateTerm::Parameter(_) => return false,
14828        }
14829    }
14830    true
14831}
14832
14833fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
14834    let mut work = vec![(left, right)];
14835    while let Some((left, right)) = work.pop() {
14836        match (left, right) {
14837            (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
14838                if left != right {
14839                    return false;
14840                }
14841            }
14842            (
14843                CppTemplateTerm::Atom {
14844                    kind: left_kind,
14845                    text: left_text,
14846                },
14847                CppTemplateTerm::Atom {
14848                    kind: right_kind,
14849                    text: right_text,
14850                },
14851            ) => {
14852                if left_kind != right_kind || left_text != right_text {
14853                    return false;
14854                }
14855            }
14856            (
14857                CppTemplateTerm::Node {
14858                    kind: left_kind,
14859                    children: left_children,
14860                },
14861                CppTemplateTerm::Node {
14862                    kind: right_kind,
14863                    children: right_children,
14864                },
14865            ) => {
14866                if left_kind != right_kind || left_children.len() != right_children.len() {
14867                    return false;
14868                }
14869                work.extend(left_children.iter().zip(right_children).rev());
14870            }
14871            _ => return false,
14872        }
14873    }
14874    true
14875}
14876
14877pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
14878    let mut components = Vec::new();
14879    let mut stack = vec![node];
14880    while let Some(current) = stack.pop() {
14881        match current.kind() {
14882            "identifier"
14883            | "field_identifier"
14884            | "namespace_identifier"
14885            | "type_identifier"
14886            | "operator_name"
14887            | "destructor_name" => components.push(current),
14888            "template_type" | "template_function" => {
14889                stack.push(current.child_by_field_name("name")?);
14890            }
14891            "dependent_name" => stack.push(current.named_child(0)?),
14892            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
14893                stack.push(current.child_by_field_name("name")?);
14894                if let Some(scope) = current.child_by_field_name("scope") {
14895                    stack.push(scope);
14896                }
14897            }
14898            "nested_namespace_specifier" => {
14899                for index in (0..current.named_child_count()).rev() {
14900                    stack.push(current.named_child(index)?);
14901                }
14902            }
14903            _ => return None,
14904        }
14905    }
14906    Some(components)
14907}
14908
14909pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
14910    node.child_by_field_name("scope").is_none()
14911        && node.child(0).is_some_and(|child| child.kind() == "::")
14912}
14913
14914fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
14915    let mut namespaces = Vec::new();
14916    let mut current = node.parent();
14917    while let Some(parent) = current {
14918        if parent.kind() == "namespace_definition"
14919            && let Some(name) = parent.child_by_field_name("name")
14920        {
14921            let mut components = Vec::new();
14922            append_cpp_name_components(name, source, &mut components)?;
14923            namespaces.push(components);
14924        }
14925        current = parent.parent();
14926    }
14927    namespaces.reverse();
14928    Some(namespaces.into_iter().flatten().collect())
14929}
14930
14931/// Whether a parser-derived namespace path can be reconciled with an indexed
14932/// owner scope without inventing an unrelated short-name binding.
14933///
14934/// Macro namespace sentinels can make tree-sitter omit one or more namespace
14935/// definitions from the ancestor chain. Preserve the order of every namespace
14936/// that did survive parsing, but allow indexed components between them. An
14937/// empty path is accepted only when the declarator itself supplies a nested
14938/// owner suffix such as `Outer::Inner`: together with the indexed enclosing
14939/// owner chain, that suffix is structural evidence that a namespace was lost.
14940/// A one-segment owner at the translation-unit root remains insufficient.
14941fn indexed_namespace_path_is_recoverable(
14942    lexical_scope: &[String],
14943    indexed_owner_scope: &[String],
14944    explicit_owner_component_count: usize,
14945) -> bool {
14946    if lexical_scope.is_empty() {
14947        return explicit_owner_component_count > 1;
14948    }
14949    if lexical_scope.len() >= indexed_owner_scope.len() {
14950        return false;
14951    }
14952    let mut indexed = indexed_owner_scope.iter();
14953    lexical_scope
14954        .iter()
14955        .all(|component| indexed.any(|candidate| candidate == component))
14956}
14957
14958pub fn has_ancestor_kind(node: Node<'_>, kind: &str) -> bool {
14959    let mut current = node.parent();
14960    while let Some(parent) = current {
14961        if parent.kind() == kind {
14962            return true;
14963        }
14964        current = parent.parent();
14965    }
14966    false
14967}
14968
14969/// Whether a declaration type is initialized with a pointer cast.
14970///
14971/// This structured shape has an independent qualified occurrence in addition
14972/// to the cast descriptor below it. Other declarations must keep their normal
14973/// full-range occurrence only.
14974pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
14975    let mut current = Some(node);
14976    while let Some(candidate) = current {
14977        if candidate.kind() == "declaration" {
14978            let Some(type_node) = candidate.child_by_field_name("type") else {
14979                return false;
14980            };
14981            if !(type_node.start_byte() <= node.start_byte()
14982                && node.end_byte() <= type_node.end_byte())
14983            {
14984                return false;
14985            }
14986            let mut cursor = candidate.walk();
14987            return candidate.named_children(&mut cursor).any(|child| {
14988                child.kind() == "init_declarator"
14989                    && child
14990                        .child_by_field_name("value")
14991                        .is_some_and(|value| value.kind() == "cast_expression")
14992            });
14993        }
14994        current = candidate.parent();
14995    }
14996    false
14997}
14998
14999#[derive(Clone, Copy, PartialEq, Eq)]
15000pub(crate) enum QualifiedAliasReferenceKind {
15001    Ordinary,
15002    ConstructorWithExpressionArgument,
15003    ExhaustiveTemplate,
15004}
15005
15006/// Whether a qualified alias reference preserves the requested target.
15007///
15008/// The complete qualified spelling and its terminal identifier are both valid
15009/// occurrences when the visible alias path is structurally proven to name the
15010/// target. Template aliases use their bound arguments; ordinary aliases use
15011/// their structured primary chain.
15012pub(crate) fn qualified_alias_reference_preserves_target(
15013    node: Node<'_>,
15014    target: &CodeUnit,
15015    analyzer: &CppGraphSource<'_>,
15016    visibility: &VisibilityIndex<'_>,
15017    file: &ProjectFile,
15018    source: &str,
15019) -> Option<QualifiedAliasReferenceKind> {
15020    if !matches!(
15021        node.kind(),
15022        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
15023    ) {
15024        return None;
15025    }
15026    let components = cpp_type_name_components(node, source)?;
15027    let name = components.last()?;
15028    analyzer.type_alias_provider().and_then(|provider| {
15029        visibility
15030            .visible_identifier_candidates(file, name)
15031            .find_map(|candidate| {
15032                let proof = provider.is_type_alias(candidate)
15033                    && canonical_cpp_scope_components(candidate) == components
15034                    && visibility.external_type_candidate_visible_in_context(
15035                        analyzer, file, candidate, node,
15036                    )
15037                    && match cpp_template_reference_arguments(node, source) {
15038                        Some(arguments) => visibility.template_alias_arguments_preserve_target(
15039                            analyzer, file, candidate, &arguments, target,
15040                        ),
15041                        None => visibility.structured_alias_primary_preserves_target(
15042                            analyzer, file, candidate, target,
15043                        ),
15044                    };
15045                proof.then(|| {
15046                    if cpp_template_reference_arguments(node, source).is_some()
15047                        && visibility.is_exhaustive_same_fqn_type_declaration_family(
15048                            analyzer, file, candidate,
15049                        )
15050                    {
15051                        QualifiedAliasReferenceKind::ExhaustiveTemplate
15052                    } else if qualified_alias_constructor_has_expression_argument(node)
15053                        || qualified_alias_local_constructor_declaration(node)
15054                    {
15055                        QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
15056                    } else {
15057                        QualifiedAliasReferenceKind::Ordinary
15058                    }
15059                })
15060            })
15061    })
15062}
15063
15064pub(crate) fn qualified_alias_reference_requires_terminal(
15065    reference: Option<QualifiedAliasReferenceKind>,
15066) -> bool {
15067    matches!(
15068        reference,
15069        Some(
15070            QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
15071                | QualifiedAliasReferenceKind::ExhaustiveTemplate
15072        )
15073    )
15074}
15075
15076fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
15077    let Some(declaration) = node.parent().filter(|parent| {
15078        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
15079    }) else {
15080        return false;
15081    };
15082    let mut cursor = declaration.walk();
15083    declaration.named_children(&mut cursor).any(|child| {
15084        child.kind() == "init_declarator"
15085            && child
15086                .child_by_field_name("value")
15087                .filter(|value| value.kind() == "argument_list")
15088                .is_some_and(|arguments| {
15089                    let mut cursor = arguments.walk();
15090                    arguments.named_children(&mut cursor).any(|argument| {
15091                        let is_parameter = matches!(
15092                            argument.kind(),
15093                            "parameter_declaration" | "optional_parameter_declaration"
15094                        );
15095                        if is_parameter {
15096                            argument
15097                                .child_by_field_name("type")
15098                                .is_some_and(|type_node| {
15099                                    type_node.kind() == "type_identifier"
15100                                        && argument.child_by_field_name("declarator").is_none()
15101                                })
15102                        } else {
15103                            !argument.kind().ends_with("_literal")
15104                                && !matches!(argument.kind(), "true" | "false" | "nullptr")
15105                        }
15106                    })
15107                })
15108    })
15109}
15110
15111/// Tree-sitter represents a local C++ direct construction such as
15112/// `Alias value(argument)` as a function declarator. Restrict that recovery to
15113/// declarations inside a compound statement so namespace-scope function
15114/// declarations with the same qualified return type stay full-range only.
15115fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
15116    let Some(declaration) = node.parent().filter(|parent| {
15117        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
15118    }) else {
15119        return false;
15120    };
15121    if declaration
15122        .parent()
15123        .is_none_or(|parent| parent.kind() != "compound_statement")
15124    {
15125        return false;
15126    }
15127    let mut cursor = declaration.walk();
15128    declaration
15129        .named_children(&mut cursor)
15130        .any(|child| child.kind() == "function_declarator")
15131}
15132
15133/// Return the terminal identifier represented by a callable or type callee.
15134///
15135/// Qualified, scoped, template, and field wrappers are traversed through their
15136/// grammar fields so both function calls and type constructions emit the token
15137/// that names the referenced declaration.
15138pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
15139    loop {
15140        let next = match node.kind() {
15141            "qualified_identifier"
15142            | "scoped_identifier"
15143            | "template_method"
15144            | "template_function"
15145            | "template_type" => node.child_by_field_name("name"),
15146            "field_expression" => node.child_by_field_name("field"),
15147            _ => None,
15148        };
15149        let Some(next) = next else {
15150            return node;
15151        };
15152        node = next;
15153    }
15154}
15155
15156#[derive(Clone, Copy)]
15157pub struct RecoveredRelationalTemplateMemberCall<'tree> {
15158    pub receiver: Node<'tree>,
15159    pub member: Node<'tree>,
15160    pub arity: usize,
15161}
15162
15163/// Recover `receiver.member<argument>(call_arguments)` when tree-sitter chose
15164/// nested relational expressions instead of a `template_method` call.
15165///
15166/// The recovery uses only grammar fields: the selected field must be the left
15167/// side of `<`, that expression must be the left side of `>`, and the right
15168/// side of `>` must be the parenthesized call arguments. Semantic callers must
15169/// additionally prove the receiver owner and the member's template status.
15170pub fn recovered_relational_template_member_call(
15171    field: Node<'_>,
15172) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
15173    if field.kind() != "field_expression" {
15174        return None;
15175    }
15176    let receiver = field
15177        .child_by_field_name("argument")
15178        .or_else(|| field.child_by_field_name("object"))?;
15179    let member = field.child_by_field_name("field")?;
15180    let less = field.parent()?;
15181    if less.kind() != "binary_expression"
15182        || less.child_by_field_name("left") != Some(field)
15183        || less
15184            .child_by_field_name("operator")
15185            .is_none_or(|operator| operator.kind() != "<")
15186        || less.child_by_field_name("right").is_none()
15187    {
15188        return None;
15189    }
15190    let greater = less.parent()?;
15191    if greater.kind() != "binary_expression"
15192        || greater.child_by_field_name("left") != Some(less)
15193        || greater
15194            .child_by_field_name("operator")
15195            .is_none_or(|operator| operator.kind() != ">")
15196    {
15197        return None;
15198    }
15199    let arguments = greater.child_by_field_name("right")?;
15200    if arguments.kind() != "parenthesized_expression" {
15201        return None;
15202    }
15203    let arity = parenthesized_call_argument_arity(arguments)?;
15204    Some(RecoveredRelationalTemplateMemberCall {
15205        receiver,
15206        member,
15207        arity,
15208    })
15209}
15210
15211fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
15212    let expression = arguments.named_child(0)?;
15213    if expression.kind() != "comma_expression" {
15214        return Some(1);
15215    }
15216    let mut arity = 0usize;
15217    let mut stack = vec![expression];
15218    while let Some(node) = stack.pop() {
15219        if node.kind() == "comma_expression" {
15220            stack.push(node.child_by_field_name("right")?);
15221            stack.push(node.child_by_field_name("left")?);
15222        } else {
15223            arity += 1;
15224        }
15225    }
15226    Some(arity)
15227}
15228
15229/// Whether `node` is part of a call's callee expression, walking only through
15230/// the grammar wrappers that can structurally contain that callee.
15231pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
15232    while let Some(parent) = node.parent() {
15233        match parent.kind() {
15234            "call_expression" => {
15235                return parent
15236                    .child_by_field_name("function")
15237                    .or_else(|| parent.named_child(0))
15238                    == Some(node);
15239            }
15240            "qualified_identifier"
15241            | "scoped_identifier"
15242            | "template_function"
15243            | "template_type"
15244            | "field_expression" => node = parent,
15245            _ => return false,
15246        }
15247    }
15248    false
15249}
15250
15251pub fn type_reference_hit_node(node: Node<'_>) -> Node<'_> {
15252    if is_call_callee_node(node) {
15253        function_terminal_node(node)
15254    } else {
15255        node
15256    }
15257}
15258
15259pub fn normalize_type_text(value: &str) -> String {
15260    strip_tag_type_prefix(
15261        normalize_cpp_whitespace(value)
15262            .trim_start_matches("const ")
15263            .trim_end_matches('*')
15264            .trim_end_matches('&')
15265            .trim(),
15266    )
15267    .to_string()
15268}
15269
15270fn strip_tag_type_prefix(value: &str) -> &str {
15271    let value = value.trim_start_matches("const ");
15272    value
15273        .strip_prefix("struct ")
15274        .or_else(|| value.strip_prefix("class "))
15275        .or_else(|| value.strip_prefix("enum "))
15276        .unwrap_or(value)
15277        .trim()
15278}
15279
15280pub fn normalize_reference_name(value: &str) -> Option<String> {
15281    let normalized = normalize_cpp_reference_text(value);
15282    (!normalized.is_empty()).then_some(normalized)
15283}
15284
15285pub fn normalize_cpp_reference_text(value: &str) -> String {
15286    let mut text = normalize_cpp_whitespace(value)
15287        .trim_start_matches("new ")
15288        .trim()
15289        .to_string();
15290    if let Some(index) = text.find(['(', '{']) {
15291        text.truncate(index);
15292    }
15293    if let Some(index) = text.find('<') {
15294        text.truncate(index);
15295    }
15296    let normalized = text
15297        .trim()
15298        .trim_start_matches("const ")
15299        .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
15300        .trim_matches(':')
15301        .trim();
15302    strip_tag_type_prefix(normalized).to_string()
15303}
15304
15305pub fn cpp_name_for(unit: &CodeUnit) -> String {
15306    let short = unit.short_name().replace(['.', '$'], "::");
15307    if unit.package_name().is_empty() {
15308        short
15309    } else {
15310        format!("{}::{}", unit.package_name(), short)
15311    }
15312}
15313
15314/// Render an indexed C++ qualified name from its authoritative FqName
15315/// segments. Unlike the legacy `cpp_name_for` renderer, this preserves dots
15316/// that belong to a template argument (for example `Args...`).
15317fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
15318    let fq = unit.fq();
15319    if fq.is_empty() {
15320        return None;
15321    }
15322    let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
15323    Some(
15324        fq.segments()
15325            .iter()
15326            .map(|&segment| interner.resolve(segment).0)
15327            .collect::<Vec<_>>()
15328            .join("::"),
15329    )
15330}
15331
15332fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
15333    canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
15334        || unit.fq().is_empty() && cpp_name_for(unit) == expected
15335}
15336
15337/// Return the indexed C++ owner scope without reparsing its rendered name.
15338///
15339/// Template spellings are opaque within an indexed `FqName` segment.  In
15340/// particular, the ellipsis in a parameter pack (`Args...`) is part of the
15341/// `AtomicHook<...>` type segment; feeding the legacy all-`::` rendering back
15342/// through `parse_symbol_path` would mistake those dots for component
15343/// separators.  Cache-loaded/legacy units may still have an empty structured
15344/// name, so retain the parser only as that explicit fallback.
15345pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
15346    let fq = unit.fq();
15347    if !fq.is_empty() {
15348        let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
15349        let scope = fq
15350            .segments()
15351            .iter()
15352            .filter_map(|&segment| {
15353                let (text, kind) = interner.resolve(segment);
15354                matches!(
15355                    kind,
15356                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
15357                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
15358                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
15359                )
15360                .then(|| text.to_string())
15361            })
15362            .collect();
15363        return scope;
15364    }
15365    brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
15366        brokk_bifrost_core::analyzer::Language::Cpp,
15367        &cpp_name_for(unit),
15368    )
15369}
15370
15371// fqname-M4: the second stage splits on the individual chars '.', '-', '>'
15372// (not the substring "->"), which deliberately reduces an `operator->`-style
15373// terminal segment to an empty tail rather than keeping it intact; the shared
15374// structured splitter's cpp operator-token merge would keep `operator->`
15375// whole instead, changing this function's result — `name_matches_callable`'s
15376// `expected.starts_with("operator")` fallback exists specifically to
15377// compensate for that reduction, and a pinned regression test
15378// (`operator-> must not be reduced with terminal_name-style punctuation
15379// splitting`) asserts today's char-class behavior. Not equivalence-provable;
15380// revisit alongside that pinned test if it is ever relaxed.
15381pub fn terminal_name(value: &str) -> &str {
15382    value
15383        .rsplit("::")
15384        .next()
15385        .unwrap_or(value)
15386        .rsplit(['.', '-', '>'])
15387        .next()
15388        .unwrap_or(value)
15389        .trim()
15390}
15391
15392pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
15393    terminal_name(&normalize_cpp_reference_text(value)) == expected
15394}
15395
15396pub fn name_matches_callable(value: &str, expected: &str) -> bool {
15397    name_matches_terminal(value, expected)
15398        || expected.starts_with("operator")
15399            && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
15400}
15401
15402pub fn name_mentions(value: &str, expected: &str) -> bool {
15403    normalize_cpp_reference_text(value)
15404        .split("::")
15405        .any(|part| part == expected)
15406}
15407
15408pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
15409    let cpp_name = cpp_name_for(unit);
15410    if reference.contains("::") {
15411        return reference == cpp_name;
15412    }
15413    reference == cpp_name
15414        || terminal_name(reference) == unit.identifier()
15415            && (unit.package_name().is_empty() || reference == unit.identifier())
15416}
15417
15418pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
15419    match kind {
15420        TargetKind::Type
15421        | TargetKind::Constructor
15422        | TargetKind::Method
15423        | TargetKind::MemberField => true,
15424        TargetKind::FreeFunction => unit.is_function(),
15425        TargetKind::GlobalField => unit.is_field(),
15426        TargetKind::Macro => unit.is_macro(),
15427    }
15428}
15429
15430pub fn is_type_alias(unit: &CodeUnit) -> bool {
15431    unit.kind() == CodeUnitType::Field
15432        && unit.signature().is_some_and(|signature| {
15433            signature.starts_with("typedef ") || signature.starts_with("using ")
15434        })
15435}
15436
15437fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
15438    let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
15439    let target_name = cpp_name_for(target);
15440    if normalized.contains("::") {
15441        return normalized == target_name;
15442    }
15443    if let Some(namespace) = alias.namespace.as_deref() {
15444        return namespace_prefixes(namespace)
15445            .into_iter()
15446            .any(|prefix| format!("{prefix}::{normalized}") == target_name);
15447    }
15448    target.package_name().is_empty() && normalized == target.identifier()
15449}
15450
15451/// The declared return type text of a C++ function unit, with leading declaration specifiers
15452/// stripped, e.g. `T*` for `T* operator->()`.
15453pub fn cpp_function_return_type_text(
15454    analyzer: &CppGraphSource<'_>,
15455    function: &CodeUnit,
15456) -> Option<String> {
15457    let metadata = analyzer.signature_metadata(function);
15458    if !metadata.is_empty() {
15459        let first = metadata.first()?.return_type_text()?;
15460        return metadata
15461            .iter()
15462            .all(|metadata| metadata.return_type_text() == Some(first))
15463            .then(|| first.to_string());
15464    }
15465    let signature = cpp_function_signature_text(analyzer, function)?;
15466    cpp_function_return_type_text_from_signature(&signature)
15467}
15468
15469fn cpp_function_signature_text(
15470    analyzer: &CppGraphSource<'_>,
15471    function: &CodeUnit,
15472) -> Option<String> {
15473    function
15474        .signature()
15475        .filter(|signature| signature.contains(function.identifier()))
15476        .map(str::to_string)
15477        .or_else(|| analyzer.signatures(function).first().cloned())
15478        .or_else(|| analyzer.get_source(function, false))
15479}
15480
15481fn cpp_function_return_type_text_from_signature(signature: &str) -> Option<String> {
15482    let open = signature.find('(')?;
15483    let name_at = cpp_function_name_start(signature, open)?;
15484    if let Some(return_type) = cpp_trailing_return_type(&signature[name_at..]) {
15485        return Some(return_type);
15486    }
15487    let type_text = cpp_strip_leading_template_clause(&signature[..name_at])
15488        .split_whitespace()
15489        .filter(|token| {
15490            !matches!(
15491                *token,
15492                "static" | "virtual" | "inline" | "constexpr" | "explicit" | "friend"
15493            )
15494        })
15495        .collect::<Vec<_>>()
15496        .join(" ");
15497    let type_text = type_text.trim();
15498    (!type_text.is_empty()).then(|| type_text.to_string())
15499}
15500
15501fn cpp_function_name_start(signature: &str, open: usize) -> Option<usize> {
15502    let before_parameters = &signature[..open];
15503    if let Some(operator_at) = before_parameters.rfind("operator") {
15504        let boundary = operator_at == 0
15505            || before_parameters[..operator_at]
15506                .chars()
15507                .next_back()
15508                .is_some_and(|ch| !(ch == '_' || ch.is_ascii_alphanumeric()));
15509        if boundary {
15510            return Some(operator_at);
15511        }
15512    }
15513    before_parameters
15514        .rfind(|ch: char| !(ch == '_' || ch.is_ascii_alphanumeric()))
15515        .map(|index| index + 1)
15516}
15517
15518fn cpp_trailing_return_type(signature_from_name: &str) -> Option<String> {
15519    let open = signature_from_name.find('(')?;
15520    let mut depth = 0i32;
15521    for (offset, ch) in signature_from_name[open..].char_indices() {
15522        match ch {
15523            '(' => depth += 1,
15524            ')' => {
15525                depth -= 1;
15526                if depth == 0 {
15527                    let rest = signature_from_name[open + offset + ch.len_utf8()..].trim_start();
15528                    let arrow = rest.find("->")?;
15529                    let return_type = rest[arrow + 2..].trim_start();
15530                    let return_type = return_type
15531                        .split(['{', ';'])
15532                        .next()
15533                        .unwrap_or(return_type)
15534                        .trim();
15535                    return (!return_type.is_empty()).then(|| return_type.to_string());
15536                }
15537            }
15538            _ => {}
15539        }
15540    }
15541    None
15542}
15543
15544/// Strip a leading `template <...>` parameter clause, leaving the declaration that follows.
15545/// Returns the input unchanged when there is no such clause.
15546fn cpp_strip_leading_template_clause(text: &str) -> &str {
15547    let trimmed = text.trim_start();
15548    let Some(rest) = trimmed.strip_prefix("template") else {
15549        return text;
15550    };
15551    let rest = rest.trim_start();
15552    if !rest.starts_with('<') {
15553        return text;
15554    }
15555    let mut depth = 0i32;
15556    for (offset, ch) in rest.char_indices() {
15557        match ch {
15558            '<' => depth += 1,
15559            '>' => {
15560                depth -= 1;
15561                if depth == 0 {
15562                    return rest[offset + ch.len_utf8()..].trim_start();
15563                }
15564            }
15565            _ => {}
15566        }
15567    }
15568    text
15569}
15570
15571pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
15572    // fqname-M4: `cpp_name_for` is a bespoke all-`::` rendering of the unit's
15573    // name (it replaces every `.`/`$` in `short_name` with `::`), which is NOT
15574    // the same string `default_parent_fq_name`/`fq().parent()` would render:
15575    // the structured `FqName`'s native cpp display deliberately keeps `.` (not
15576    // `::`) between a trailing `Package` segment and a following `Type`
15577    // segment (see `separator` in `fq_name.rs`, landed for issue #1163), so
15578    // popping the unit's own `fq()` segment would NOT reproduce this
15579    // fully-`::`-joined string. Left as a split on the locally-built
15580    // all-colon string rather than the unit's structured name.
15581    cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
15582        namespace
15583            .strip_prefix("anonymous_namespace::")
15584            .unwrap_or(namespace)
15585            .to_string()
15586    })
15587}
15588
15589fn namespace_prefixes(namespace: &str) -> Vec<String> {
15590    // `namespace` is built by `cpp_name_for`/`cpp_namespace_for` with every
15591    // non-`::` separator already converted to `::`, so re-tokenizing it with
15592    // the shared structured splitter and progressively popping the last
15593    // component reproduces the `rsplit_once("::")` outward walk exactly (same
15594    // shape as `cpp_qualifier_lookup_tiers`'s namespace-chain walk).
15595    let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
15596        brokk_bifrost_core::analyzer::Language::Cpp,
15597        namespace,
15598    );
15599    let mut prefixes = Vec::new();
15600    while !parts.is_empty() {
15601        prefixes.push(parts.join("::"));
15602        parts.pop();
15603    }
15604    prefixes
15605}
15606
15607fn nearest_namespace_candidates(
15608    candidates: Vec<CodeUnit>,
15609    normalized: &str,
15610    lexical_namespace: Option<&str>,
15611) -> Vec<CodeUnit> {
15612    if normalized.contains("::") {
15613        return candidates;
15614    }
15615    if let Some(namespace) = lexical_namespace {
15616        for prefix in namespace_prefixes(namespace) {
15617            let scoped = candidates
15618                .iter()
15619                .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
15620                .cloned()
15621                .collect::<Vec<_>>();
15622            if !scoped.is_empty() {
15623                return scoped;
15624            }
15625        }
15626    }
15627    candidates
15628        .into_iter()
15629        .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
15630        .collect()
15631}
15632
15633pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
15634    let mut namespaces = Vec::new();
15635    let mut current = node.parent();
15636    while let Some(parent) = current {
15637        if parent.kind() == "namespace_definition"
15638            && let Some(name) = parent.child_by_field_name("name")
15639        {
15640            let namespace = normalize_cpp_reference_text(node_text(name, source));
15641            if !namespace.is_empty() {
15642                namespaces.push(namespace);
15643            }
15644        }
15645        current = parent.parent();
15646    }
15647    if namespaces.is_empty() {
15648        None
15649    } else {
15650        namespaces.reverse();
15651        Some(namespaces.join("::"))
15652    }
15653}
15654
15655/// Like [`precise_parent_of`], but drops module (namespace) parents. A namespace is a scope, not a
15656/// type or receiver, so namespace-scoped functions and constants resolve as free functions and
15657/// globals rather than members.
15658pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
15659    type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
15660}
15661
15662fn type_owner_resolution(
15663    analyzer: &CppGraphSource<'_>,
15664    code_unit: &CodeUnit,
15665) -> Option<ResolvedTypeOwner> {
15666    precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
15667}
15668
15669fn target_type_owner_resolution(
15670    analyzer: &CppGraphSource<'_>,
15671    code_unit: &CodeUnit,
15672) -> Option<ResolvedTypeOwner> {
15673    match type_owner_resolution(analyzer, code_unit) {
15674        Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
15675        Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
15676    }
15677}
15678
15679/// Recover method identity for an indexed out-of-line definition when the
15680/// ordinary parent edge is absent. Prefer the unique include-visible forward
15681/// declaration, then classify exact-FQN class declarations elsewhere in the
15682/// workspace. A unique complete declaration wins; otherwise multiple forward
15683/// declarations are one owner only when they all share one logical identity.
15684/// The qualified callable FQN proves that owner spelling even when its defining
15685/// header is outside the scan file's include closure, while unknown or competing
15686/// complete declarations remain ambiguous.
15687/// This is deliberately target-only: canonical declaration resolution must
15688/// continue to prefer the callable definition rather than replacing it with
15689/// the recovered owner.
15690fn target_forward_owner_resolution(
15691    analyzer: &CppGraphSource<'_>,
15692    code_unit: &CodeUnit,
15693) -> Option<ResolvedTypeOwner> {
15694    if !code_unit.is_function() {
15695        return None;
15696    }
15697    // A top-level free function has no owner at all, and `FqName::parent`
15698    // answers the empty name rather than `None` for a one-segment identity.
15699    // `default_parent_fq_name`, which this replaced, filtered that case out;
15700    // asking the relational store for the empty name is a batch error that
15701    // fails the whole target frontier.
15702    let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
15703    let cpp = analyzer.cpp?;
15704    let mut visible_files = HashSet::default();
15705    collect_include_closure(
15706        analyzer,
15707        cpp.include_target_index(),
15708        code_unit.source(),
15709        &mut visible_files,
15710        None,
15711    );
15712    let candidates = analyzer.workspace_definitions().exact(&owner_name);
15713    let visible_candidates = candidates
15714        .iter()
15715        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
15716        .cloned()
15717        .collect::<Vec<_>>();
15718    match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
15719        DirectOwnerResolution::UniqueFull(unit) => {
15720            return Some(ResolvedTypeOwner {
15721                unit,
15722                is_forward_declaration: false,
15723            });
15724        }
15725        DirectOwnerResolution::ForwardsOnly(forwards) => {
15726            return (forwards.len() == 1).then(|| ResolvedTypeOwner {
15727                unit: forwards.into_iter().next().unwrap(),
15728                is_forward_declaration: true,
15729            });
15730        }
15731        DirectOwnerResolution::Ambiguous => return None,
15732        DirectOwnerResolution::None => {}
15733    }
15734
15735    let candidates = candidates
15736        .into_iter()
15737        .filter(|candidate| candidate.is_class())
15738        .collect::<Vec<_>>();
15739    let (unit, is_forward_declaration) =
15740        match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
15741            DirectOwnerResolution::UniqueFull(unit) => (unit, false),
15742            DirectOwnerResolution::ForwardsOnly(forwards) => {
15743                (unique_logical_forward_owner(forwards)?, true)
15744            }
15745            DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
15746        };
15747    Some(ResolvedTypeOwner {
15748        unit,
15749        is_forward_declaration,
15750    })
15751}
15752
15753pub fn precise_parent_of(
15754    analyzer: &CppGraphSource<'_>,
15755    visibility: &VisibilityIndex<'_>,
15756    code_unit: &CodeUnit,
15757) -> Option<CodeUnit> {
15758    visibility.cached_precise_parent_of(analyzer, code_unit)
15759}
15760
15761fn precise_parent_resolution(
15762    analyzer: &CppGraphSource<'_>,
15763    code_unit: &CodeUnit,
15764) -> Option<ResolvedTypeOwner> {
15765    #[cfg(any(test, feature = "test-support"))]
15766    if let Some(cpp) = analyzer.cpp {
15767        cpp.record_cpp_parent_resolution_for_test();
15768    }
15769    if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
15770        return Some(ResolvedTypeOwner {
15771            unit,
15772            is_forward_declaration: false,
15773        });
15774    }
15775    let fallback = analyzer.parent_of(code_unit);
15776    if !code_unit.owner_is_type_scope() {
15777        return fallback.map(|unit| ResolvedTypeOwner {
15778            unit,
15779            is_forward_declaration: false,
15780        });
15781    }
15782    let owner_fq = code_unit
15783        .fq()
15784        .parent()
15785        .expect("a unit with an owner identifier has a structured parent");
15786    let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
15787    match same_source_owner(analyzer, code_unit, &owner_candidates) {
15788        DirectOwnerResolution::UniqueFull(owner) => {
15789            return Some(ResolvedTypeOwner {
15790                unit: owner,
15791                is_forward_declaration: false,
15792            });
15793        }
15794        DirectOwnerResolution::Ambiguous => return None,
15795        DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
15796    }
15797    match directly_included_owner(analyzer, code_unit, &owner_candidates) {
15798        DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
15799            unit: owner,
15800            is_forward_declaration: false,
15801        }),
15802        DirectOwnerResolution::Ambiguous => None,
15803        DirectOwnerResolution::ForwardsOnly(forwards) => {
15804            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
15805                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
15806                    unit: owner,
15807                    is_forward_declaration: false,
15808                }),
15809                FullOwnerResolution::None => {
15810                    unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
15811                        unit,
15812                        is_forward_declaration: true,
15813                    })
15814                }
15815                FullOwnerResolution::Ambiguous => None,
15816            }
15817        }
15818        DirectOwnerResolution::None => {
15819            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
15820                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
15821                    unit: owner,
15822                    is_forward_declaration: false,
15823                }),
15824                FullOwnerResolution::Ambiguous => None,
15825                FullOwnerResolution::None => fallback
15826                    .filter(|parent| {
15827                        parent.source() == code_unit.source()
15828                            && parent.fq() == &owner_fq
15829                            && (!parent.is_class()
15830                                || cpp_class_declaration_strength(analyzer, parent)
15831                                    == CppClassDeclarationStrength::Full)
15832                    })
15833                    .map(|unit| ResolvedTypeOwner {
15834                        unit,
15835                        is_forward_declaration: false,
15836                    }),
15837            }
15838        }
15839    }
15840}
15841
15842fn exact_structural_type_parent(
15843    analyzer: &CppGraphSource<'_>,
15844    code_unit: &CodeUnit,
15845) -> Option<CodeUnit> {
15846    if !code_unit.is_function() && !code_unit.is_field() {
15847        return None;
15848    }
15849    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
15850    let cpp = analyzer.cpp?;
15851    let parent = cpp.structural_parent_of(code_unit)?;
15852    (!parent.is_module()
15853        && parent.source() == code_unit.source()
15854        && parent.package_name() == code_unit.package_name()
15855        && parent.short_name() == encoded_owner)
15856        .then_some(parent)
15857}
15858
15859fn same_source_owner(
15860    analyzer: &CppGraphSource<'_>,
15861    code_unit: &CodeUnit,
15862    owner_candidates: &[CodeUnit],
15863) -> DirectOwnerResolution {
15864    let candidates = owner_candidates
15865        .iter()
15866        .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
15867        .cloned()
15868        .collect::<Vec<_>>();
15869    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
15870    classify_direct_owner_candidates(analyzer, candidates.into_iter())
15871}
15872
15873fn visible_full_cpp_owner(
15874    analyzer: &CppGraphSource<'_>,
15875    code_unit: &CodeUnit,
15876    owner_candidates: &[CodeUnit],
15877) -> FullOwnerResolution {
15878    let Some(cpp) = analyzer.cpp else {
15879        return FullOwnerResolution::None;
15880    };
15881    let mut visible_files = HashSet::default();
15882    collect_include_closure(
15883        analyzer,
15884        cpp.include_target_index(),
15885        code_unit.source(),
15886        &mut visible_files,
15887        None,
15888    );
15889    let candidates = owner_candidates
15890        .iter()
15891        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
15892        .cloned()
15893        .collect::<Vec<_>>();
15894    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
15895    let mut full_definition = None;
15896    for candidate in candidates {
15897        match cpp_class_declaration_strength(analyzer, &candidate) {
15898            CppClassDeclarationStrength::Full if full_definition.is_some() => {
15899                return FullOwnerResolution::Ambiguous;
15900            }
15901            CppClassDeclarationStrength::Full => full_definition = Some(candidate),
15902            CppClassDeclarationStrength::Forward => {}
15903            CppClassDeclarationStrength::Unknown => return FullOwnerResolution::Ambiguous,
15904        }
15905    }
15906    full_definition.map_or(FullOwnerResolution::None, FullOwnerResolution::Unique)
15907}
15908
15909pub enum DirectOwnerResolution {
15910    None,
15911    ForwardsOnly(Vec<CodeUnit>),
15912    UniqueFull(CodeUnit),
15913    Ambiguous,
15914}
15915
15916enum FullOwnerResolution {
15917    None,
15918    Unique(CodeUnit),
15919    Ambiguous,
15920}
15921
15922#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15923pub enum CppClassDeclarationStrength {
15924    Full,
15925    Forward,
15926    Unknown,
15927}
15928
15929fn directly_included_owner(
15930    analyzer: &CppGraphSource<'_>,
15931    code_unit: &CodeUnit,
15932    owner_candidates: &[CodeUnit],
15933) -> DirectOwnerResolution {
15934    let Some(cpp) = analyzer.cpp else {
15935        return DirectOwnerResolution::None;
15936    };
15937    let imports = analyzer.import_statements(code_unit.source());
15938    let direct_includes: HashSet<ProjectFile> = cpp_include_paths(&imports)
15939        .into_iter()
15940        .flat_map(|include| {
15941            resolve_include_targets_with_index(
15942                code_unit.source(),
15943                &include,
15944                cpp.include_target_index(),
15945            )
15946        })
15947        .collect();
15948    let candidates = owner_candidates
15949        .iter()
15950        .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
15951        .cloned()
15952        .collect::<Vec<_>>();
15953    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
15954    classify_direct_owner_candidates(analyzer, candidates.into_iter())
15955}
15956
15957fn prefer_member_declaring_owners(
15958    analyzer: &CppGraphSource<'_>,
15959    member: &CodeUnit,
15960    candidates: Vec<CodeUnit>,
15961) -> Vec<CodeUnit> {
15962    let matching = candidates
15963        .iter()
15964        .filter(|owner| owner_declares_member(analyzer, owner, member))
15965        .cloned()
15966        .collect::<Vec<_>>();
15967    if matching.is_empty() {
15968        candidates
15969    } else {
15970        matching
15971    }
15972}
15973
15974fn owner_declares_member(
15975    analyzer: &CppGraphSource<'_>,
15976    owner: &CodeUnit,
15977    member: &CodeUnit,
15978) -> bool {
15979    analyzer.direct_children(owner).into_iter().any(|child| {
15980        child.kind() == member.kind()
15981            && child.identifier() == member.identifier()
15982            && child.signature() == member.signature()
15983    })
15984}
15985
15986fn classify_direct_owner_candidates(
15987    analyzer: &CppGraphSource<'_>,
15988    candidates: impl Iterator<Item = CodeUnit>,
15989) -> DirectOwnerResolution {
15990    collapse_owner_candidates(candidates.map(|candidate| {
15991        let strength = cpp_class_declaration_strength(analyzer, &candidate);
15992        (candidate, strength)
15993    }))
15994}
15995
15996pub fn collapse_owner_candidates(
15997    candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
15998) -> DirectOwnerResolution {
15999    let mut full_definition = None;
16000    let mut forwards = Vec::new();
16001    for (candidate, strength) in candidates {
16002        match strength {
16003            CppClassDeclarationStrength::Full if full_definition.is_some() => {
16004                return DirectOwnerResolution::Ambiguous;
16005            }
16006            CppClassDeclarationStrength::Full => full_definition = Some(candidate),
16007            CppClassDeclarationStrength::Forward => forwards.push(candidate),
16008            CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
16009        }
16010    }
16011    if let Some(owner) = full_definition {
16012        DirectOwnerResolution::UniqueFull(owner)
16013    } else if !forwards.is_empty() {
16014        DirectOwnerResolution::ForwardsOnly(forwards)
16015    } else {
16016        DirectOwnerResolution::None
16017    }
16018}
16019
16020#[cfg(any(test, feature = "test-support"))]
16021pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
16022    unique_logical_forward_owner(forwards)
16023}
16024
16025fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
16026    let first = forwards.pop()?;
16027    forwards
16028        .iter()
16029        .all(|forward| same_logical_symbol(forward, &first))
16030        .then_some(first)
16031}
16032
16033pub fn cpp_class_declaration_strength(
16034    analyzer: &CppGraphSource<'_>,
16035    candidate: &CodeUnit,
16036) -> CppClassDeclarationStrength {
16037    // The answer is a pure function of the unit's ranges and its file's tree,
16038    // and the inverse scan asks it once per declaration seed. On a translation
16039    // unit the parser could not fully recover, each ask re-derives the
16040    // export-macro recovery shapes from the file's `ERROR` subtrees, so without
16041    // this memo one file's scan is quadratic in its own size: 97% of Catch2's
16042    // 284 s inverse scan of `extras/catch_amalgamated.cpp` was in this call
16043    // (#1496).
16044    let Some(cpp) = analyzer.cpp else {
16045        return uncached_cpp_class_declaration_strength(analyzer, candidate);
16046    };
16047    if let Some(strength) = cpp.cached_class_declaration_strength(candidate) {
16048        return strength;
16049    }
16050    let strength = uncached_cpp_class_declaration_strength(analyzer, candidate);
16051    cpp.cache_class_declaration_strength(candidate, strength);
16052    strength
16053}
16054
16055fn uncached_cpp_class_declaration_strength(
16056    analyzer: &CppGraphSource<'_>,
16057    candidate: &CodeUnit,
16058) -> CppClassDeclarationStrength {
16059    if let Some(cpp) = analyzer.cpp
16060        && let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source())
16061    {
16062        return cpp_class_declaration_strength_in_tree(
16063            analyzer,
16064            &cpp.recovered_export_class_index(analyzer.token, candidate.source()),
16065            candidate,
16066            prepared.source(),
16067            prepared.tree().root_node(),
16068        );
16069    }
16070    let Some(source) = analyzer.indexed_source(candidate.source()) else {
16071        return CppClassDeclarationStrength::Unknown;
16072    };
16073    #[cfg(any(test, feature = "test-support"))]
16074    if let Some(cpp) = analyzer.cpp {
16075        cpp.record_cpp_class_strength_parse_for_test();
16076    }
16077    let mut parser = Parser::new();
16078    if parser
16079        .set_language(&tree_sitter_cpp::LANGUAGE.into())
16080        .is_err()
16081    {
16082        return CppClassDeclarationStrength::Unknown;
16083    }
16084    let Some(tree) = parser.parse(&source, None) else {
16085        return CppClassDeclarationStrength::Unknown;
16086    };
16087    // This branch reparses a file the analyzer has no prepared tree for, so its
16088    // recovery index is that one tree's and cannot be shared.
16089    let recovered_export_classes =
16090        CppRecoveredExportClassIndex::build(tree.root_node(), source.as_str());
16091    cpp_class_declaration_strength_in_tree(
16092        analyzer,
16093        &recovered_export_classes,
16094        candidate,
16095        &source,
16096        tree.root_node(),
16097    )
16098}
16099
16100fn cpp_class_declaration_strength_in_tree(
16101    analyzer: &CppGraphSource<'_>,
16102    recovered_export_classes: &CppRecoveredExportClassIndex,
16103    candidate: &CodeUnit,
16104    source: &str,
16105    root: Node<'_>,
16106) -> CppClassDeclarationStrength {
16107    let ranges = analyzer.ranges(candidate);
16108    let mut saw_forward = false;
16109    for range in ranges {
16110        // The recovered export-macro shapes answer for their own ranges; only a
16111        // range no recovery claims is read as a plain specifier.
16112        match recovered_class_body_at(
16113            recovered_export_classes,
16114            root,
16115            source,
16116            candidate.identifier(),
16117            &range,
16118        ) {
16119            Some(true) => return CppClassDeclarationStrength::Full,
16120            Some(false) => {
16121                saw_forward = true;
16122                continue;
16123            }
16124            None => {}
16125        }
16126        // Only a node covering the range's start byte can be the specifier for
16127        // this range, so apply that test where nodes enter the stack rather
16128        // than where they leave it. Pushing first meant one ask enqueued every
16129        // sibling at every level it descended, which on a translation unit with
16130        // thousands of top-level declarations is a per-ask cost proportional to
16131        // the file (#1496).
16132        let covers_range_start = |node: &Node<'_>| {
16133            node.start_byte() <= range.start_byte && node.end_byte() >= range.start_byte
16134        };
16135        let mut stack = Vec::new();
16136        if covers_range_start(&root) {
16137            stack.push(root);
16138        }
16139        while let Some(node) = stack.pop() {
16140            if node.start_byte() == range.start_byte
16141                && node.end_byte() == range.end_byte
16142                && matches!(
16143                    node.kind(),
16144                    "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
16145                )
16146            {
16147                if cpp_class_node_has_body(node) {
16148                    return CppClassDeclarationStrength::Full;
16149                }
16150                saw_forward = true;
16151            }
16152            let mut cursor = node.walk();
16153            stack.extend(node.named_children(&mut cursor).filter(covers_range_start));
16154        }
16155    }
16156    if saw_forward {
16157        CppClassDeclarationStrength::Forward
16158    } else {
16159        CppClassDeclarationStrength::Unknown
16160    }
16161}
16162
16163fn cpp_class_node_has_body(node: Node<'_>) -> bool {
16164    node.child_by_field_name("body").is_some() || {
16165        let mut cursor = node.walk();
16166        node.named_children(&mut cursor).any(|child| {
16167            matches!(
16168                child.kind(),
16169                "declaration_list" | "field_declaration_list" | "enumerator_list"
16170            )
16171        })
16172    }
16173}
16174
16175#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16176enum CppCTagKind {
16177    Struct,
16178    Union,
16179}
16180
16181fn indexed_c_tag_kind(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CppCTagKind> {
16182    let declaration = analyzer.get_source(code_unit, false)?;
16183    let mut parser = Parser::new();
16184    parser
16185        .set_language(&tree_sitter_cpp::LANGUAGE.into())
16186        .ok()?;
16187    let tree = parser.parse(&declaration, None)?;
16188    let mut stack = vec![tree.root_node()];
16189    while let Some(node) = stack.pop() {
16190        let kind = match node.kind() {
16191            "struct_specifier" => CppCTagKind::Struct,
16192            "union_specifier" => CppCTagKind::Union,
16193            _ => {
16194                let mut cursor = node.walk();
16195                stack.extend(node.named_children(&mut cursor));
16196                continue;
16197            }
16198        };
16199        if node
16200            .child_by_field_name("name")
16201            .is_some_and(|name| node_text(name, &declaration) == code_unit.identifier())
16202        {
16203            return Some(kind);
16204        }
16205        let mut cursor = node.walk();
16206        stack.extend(node.named_children(&mut cursor));
16207    }
16208    None
16209}
16210
16211pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
16212    if !code_unit.owner_is_type_scope() {
16213        return None;
16214    }
16215    let owner_fq = code_unit.fq().parent()?;
16216    ctx.analyzer
16217        .workspace_definitions()
16218        .exact(&owner_fq)
16219        .into_iter()
16220        .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
16221}
16222
16223pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
16224    left.kind() == right.kind()
16225        && left.fq_name() == right.fq_name()
16226        && left.signature() == right.signature()
16227        && left.source() == right.source()
16228}
16229
16230pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
16231    same_symbol(left, right) || same_logical_symbol(left, right)
16232}
16233
16234pub fn same_visible_global_field_symbol(
16235    analyzer: &CppGraphSource<'_>,
16236    internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
16237    left: &CodeUnit,
16238    right: &CodeUnit,
16239) -> bool {
16240    if same_symbol(left, right) {
16241        return true;
16242    }
16243    if !same_logical_symbol(left, right) {
16244        return false;
16245    }
16246    if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
16247        || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
16248    {
16249        left.source() == right.source()
16250    } else {
16251        true
16252    }
16253}
16254
16255fn cpp_global_field_has_internal_linkage_cached(
16256    analyzer: &CppGraphSource<'_>,
16257    cache: &mut HashMap<CodeUnit, bool>,
16258    candidate: &CodeUnit,
16259) -> bool {
16260    if let Some(internal) = cache.get(candidate) {
16261        return *internal;
16262    }
16263    #[cfg(any(test, feature = "test-support"))]
16264    note_cpp_global_field_internal_linkage_classification_for_test();
16265    let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
16266    cache.insert(candidate.clone(), internal);
16267    internal
16268}
16269
16270#[cfg(any(test, feature = "test-support"))]
16271thread_local! {
16272    static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
16273}
16274
16275#[cfg(any(test, feature = "test-support"))]
16276fn note_cpp_global_field_internal_linkage_classification_for_test() {
16277    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
16278        count.set(count.get() + 1);
16279    });
16280}
16281
16282#[cfg(any(test, feature = "test-support"))]
16283pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
16284    body: impl FnOnce() -> T,
16285) -> (T, usize) {
16286    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
16287        count.set(0);
16288        let result = body();
16289        let observed = count.get();
16290        count.set(0);
16291        (result, observed)
16292    })
16293}
16294
16295pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
16296    left.kind() == right.kind()
16297        && left.fq_name() == right.fq_name()
16298        && left.signature() == right.signature()
16299}
16300
16301pub fn cpp_global_field_has_internal_linkage(
16302    analyzer: &CppGraphSource<'_>,
16303    candidate: &CodeUnit,
16304) -> bool {
16305    if !candidate.is_field() || candidate.short_name().contains('.') {
16306        return false;
16307    }
16308    let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
16309        return false;
16310    };
16311    match local_linkage {
16312        CppFieldLinkage::Internal => true,
16313        CppFieldLinkage::External => false,
16314        CppFieldLinkage::InternalUnlessExternalPeer => {
16315            !cpp_global_field_linkage_peers(analyzer, candidate)
16316                .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
16317                .any(|linkage| matches!(linkage, CppFieldLinkage::External))
16318        }
16319    }
16320}
16321
16322fn cpp_global_field_linkage_peers<'a>(
16323    analyzer: &CppGraphSource<'a>,
16324    candidate: &'a CodeUnit,
16325) -> impl Iterator<Item = CodeUnit> + 'a {
16326    let name = candidate.fq().clone();
16327    analyzer
16328        .workspace_definitions()
16329        .exact(&name)
16330        .into_iter()
16331        .filter(move |peer| {
16332            if peer == candidate {
16333                return false;
16334            }
16335            #[cfg(any(test, feature = "test-support"))]
16336            note_cpp_global_field_linkage_peer_inspection_for_test();
16337            same_logical_symbol(peer, candidate)
16338        })
16339}
16340
16341#[cfg(any(test, feature = "test-support"))]
16342thread_local! {
16343    static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
16344}
16345
16346#[cfg(any(test, feature = "test-support"))]
16347fn note_cpp_global_field_linkage_peer_inspection_for_test() {
16348    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
16349        count.set(count.get() + 1);
16350    });
16351}
16352
16353#[cfg(any(test, feature = "test-support"))]
16354pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
16355    body: impl FnOnce() -> T,
16356) -> (T, usize) {
16357    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
16358        count.set(0);
16359        let result = body();
16360        let observed = count.get();
16361        count.set(0);
16362        (result, observed)
16363    })
16364}
16365
16366fn cpp_global_field_declaration_linkage(
16367    analyzer: &CppGraphSource<'_>,
16368    candidate: &CodeUnit,
16369) -> Option<CppFieldLinkage> {
16370    if let Some(linkage) = analyzer.cpp_field_linkage(candidate) {
16371        return Some(linkage);
16372    }
16373    let cpp = analyzer.cpp?;
16374    if let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) {
16375        return cpp_global_field_declaration_linkage_in_tree(
16376            analyzer,
16377            candidate,
16378            prepared.source(),
16379            prepared.tree().root_node(),
16380        );
16381    }
16382    let source = analyzer.indexed_source(candidate.source())?;
16383    let mut parser = Parser::new();
16384    if parser
16385        .set_language(&tree_sitter_cpp::LANGUAGE.into())
16386        .is_err()
16387    {
16388        return None;
16389    }
16390    let tree = parser.parse(&source, None)?;
16391    cpp_global_field_declaration_linkage_in_tree(analyzer, candidate, &source, tree.root_node())
16392}
16393
16394fn cpp_global_field_declaration_linkage_in_tree(
16395    analyzer: &CppGraphSource<'_>,
16396    candidate: &CodeUnit,
16397    source: &str,
16398    root: Node<'_>,
16399) -> Option<CppFieldLinkage> {
16400    analyzer.ranges(candidate).iter().find_map(|range| {
16401        node_for_exact_range(root, range)
16402            .and_then(enclosing_cpp_field_declaration)
16403            .map(|declaration| {
16404                // One question about one declaration; see `ParentIndex::unindexed`.
16405                cpp_field_declaration_linkage(declaration, source, &ParentIndex::unindexed())
16406            })
16407    })
16408}
16409
16410fn enclosing_cpp_field_declaration(mut node: Node<'_>) -> Option<Node<'_>> {
16411    loop {
16412        if matches!(node.kind(), "declaration" | "field_declaration") {
16413            return Some(node);
16414        }
16415        node = node.parent()?;
16416    }
16417}
16418
16419#[cfg(test)]
16420mod tests {
16421    use super::*;
16422
16423    #[test]
16424    fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
16425        let source = "int size(void) { return sizeof(((Payload))); }\n";
16426        let mut parser = Parser::new();
16427        parser
16428            .set_language(&tree_sitter_cpp::LANGUAGE.into())
16429            .expect("C++ grammar");
16430        let tree = parser.parse(source, None).expect("fixture tree");
16431        let start = source.find("Payload").expect("sizeof operand");
16432        let node = tree
16433            .root_node()
16434            .named_descendant_for_byte_range(start, start + "Payload".len())
16435            .expect("focused operand");
16436        let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
16437        let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
16438
16439        assert_eq!(node.kind(), "identifier");
16440        assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
16441        assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
16442    }
16443
16444    fn parse_cpp(source: &str) -> Tree {
16445        let mut parser = Parser::new();
16446        parser
16447            .set_language(&tree_sitter_cpp::LANGUAGE.into())
16448            .expect("C++ grammar");
16449        parser.parse(source, None).expect("fixture tree")
16450    }
16451
16452    fn named_node_at<'tree>(tree: &'tree Tree, source: &str, needle: &str) -> Node<'tree> {
16453        let start = source.find(needle).expect("fixture needle");
16454        tree.root_node()
16455            .named_descendant_for_byte_range(start, start + needle.len())
16456            .expect("node at needle")
16457    }
16458
16459    /// Two macro-decorated class heads make tree-sitter close `detail` at the
16460    /// first class's `}`, `matchers` at the second's, and `app` at `detail`'s
16461    /// real `}`; the tail parses at translation-unit level and the two real
16462    /// closes for `matchers` and `app` land in a trailing ERROR (#1537).
16463    const STOLEN_BRACE_CASCADE: &str = r#"namespace app {
16464namespace matchers {
16465    namespace detail {
16466        class API [[nodiscard]] First {
16467        public:
16468            int value() const { return count_ + 1; }
16469        private:
16470            int count_;
16471        };
16472        class API [[nodiscard]] Second {
16473        public:
16474            int value() const { return count_ + 2; }
16475        private:
16476            int count_;
16477        };
16478    } // namespace detail
16479
16480    template <typename T>
16481    void tail_function(MatcherBase<T> const& value);
16482
16483    class TailClass {};
16484} // namespace matchers
16485} // namespace app
16486
16487struct AfterAll {};
16488"#;
16489
16490    #[test]
16491    fn orphaned_namespace_scope_index_restores_a_stolen_brace_cascade() {
16492        let source = STOLEN_BRACE_CASCADE;
16493        let tree = parse_cpp(source);
16494        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
16495
16496        let tail_class = named_node_at(&tree, source, "TailClass");
16497        assert!(
16498            !has_ancestor_kind(tail_class, "namespace_definition"),
16499            "the fixture must reproduce the recovery: the tail has no namespace ancestor"
16500        );
16501        let displaced = named_node_at(&tree, source, "Second");
16502        assert_eq!(
16503            enclosing_namespace_components(displaced, source),
16504            Some(vec!["app".to_string(), "matchers".to_string()]),
16505            "the fixture must displace the second class out of detail"
16506        );
16507
16508        let components = |needle: &str| {
16509            index.enclosing_namespace_components(named_node_at(&tree, source, needle), source)
16510        };
16511        assert_eq!(components("First"), ["app", "matchers", "detail"]);
16512        assert_eq!(components("Second"), ["app", "matchers", "detail"]);
16513        assert_eq!(components("MatcherBase<T>"), ["app", "matchers"]);
16514        assert_eq!(components("tail_function"), ["app", "matchers"]);
16515        assert_eq!(components("TailClass"), ["app", "matchers"]);
16516        assert!(components("AfterAll").is_empty());
16517    }
16518
16519    #[test]
16520    fn orphaned_namespace_scope_index_is_empty_without_lost_scopes() {
16521        let clean = "namespace a { namespace b { class C {}; } class D {}; }\n";
16522        let tree = parse_cpp(clean);
16523        assert!(!tree.root_node().has_error());
16524        assert!(OrphanedNamespaceScopeIndex::build(tree.root_node(), clean).is_empty());
16525
16526        // A namespace that merely contains a parse error closes where its
16527        // brace says; the declarations after it keep their parsed scope.
16528        let damaged = "namespace a { namespace b { UNKNOWN_MACRO(x) } class C {}; }\n";
16529        let tree = parse_cpp(damaged);
16530        assert!(tree.root_node().has_error());
16531        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), damaged);
16532        assert_eq!(
16533            index.enclosing_namespace_components(named_node_at(&tree, damaged, "class C"), damaged),
16534            ["a"]
16535        );
16536    }
16537
16538    #[test]
16539    fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
16540        let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
16541        assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
16542        assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
16543        assert!(indexed_namespace_path_is_recoverable(
16544            &["cache".to_string()],
16545            &indexed,
16546            1,
16547        ));
16548    }
16549
16550    #[test]
16551    fn sort_lookup_units_totally_orders_every_identity_field() {
16552        let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
16553        let base = CodeUnit::with_signature(
16554            file.clone(),
16555            CodeUnitType::Function,
16556            "scope",
16557            "value",
16558            Some("()".to_string()),
16559            false,
16560        );
16561        let different_kind = CodeUnit::with_signature(
16562            file.clone(),
16563            CodeUnitType::Field,
16564            "scope",
16565            "value",
16566            Some("()".to_string()),
16567            false,
16568        );
16569        let synthetic = base.with_synthetic(true);
16570
16571        let interner = segment_interner();
16572        let mut member_fq = FqName::new();
16573        member_fq.push(interner.intern("scope", SegmentKind::Package));
16574        member_fq.push(interner.intern("value", SegmentKind::Member));
16575        let different_package_boundary = CodeUnit::from_fq(
16576            file.clone(),
16577            CodeUnitType::Function,
16578            member_fq,
16579            0,
16580            Some("()".to_string()),
16581            false,
16582        );
16583
16584        let mut unknown_fq = FqName::new();
16585        unknown_fq.push(interner.intern("scope", SegmentKind::Package));
16586        unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
16587        let different_segment_kind = CodeUnit::from_fq(
16588            file,
16589            CodeUnitType::Function,
16590            unknown_fq,
16591            1,
16592            Some("()".to_string()),
16593            false,
16594        );
16595
16596        let input = vec![
16597            base,
16598            different_kind,
16599            synthetic,
16600            different_package_boundary,
16601            different_segment_kind,
16602        ];
16603        let mut expected = input.clone();
16604        sort_lookup_units(&mut expected);
16605        assert!(expected.windows(2).all(|pair| {
16606            let mut ordered = pair.to_vec();
16607            sort_lookup_units(&mut ordered);
16608            ordered == pair && pair[0] != pair[1]
16609        }));
16610
16611        let mut reversed = input.clone();
16612        reversed.reverse();
16613        sort_lookup_units(&mut reversed);
16614        assert_eq!(reversed, expected);
16615
16616        let mut rotated = input;
16617        rotated.rotate_left(2);
16618        sort_lookup_units(&mut rotated);
16619        assert_eq!(rotated, expected);
16620    }
16621
16622    #[test]
16623    fn displaced_preprocessor_terminator_bounds_the_real_guard() {
16624        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";
16625        let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
16626        let parse = |source: &str| {
16627            let mut parser = Parser::new();
16628            parser
16629                .set_language(&tree_sitter_cpp::LANGUAGE.into())
16630                .expect("C++ grammar");
16631            parser.parse(source, None).expect("fixture tree")
16632        };
16633
16634        let tree = parse(damaged);
16635        let root = tree.root_node();
16636        let target = damaged.find("target").expect("target byte");
16637        let declaration = root
16638            .descendant_for_byte_range(target, target + "target".len())
16639            .and_then(|mut node| {
16640                loop {
16641                    if node.kind() == "declaration" {
16642                        break Some(node);
16643                    }
16644                    node = node.parent()?;
16645                }
16646            })
16647            .expect("declaration after the displaced terminator");
16648        let conditional = declaration
16649            .parent()
16650            .filter(|node| node.kind() == "preproc_ifdef")
16651            .expect("damaged inner conditional");
16652        let outer = conditional
16653            .parent()
16654            .filter(|node| node.kind() == "preproc_ifdef")
16655            .expect("ordinary outer include guard");
16656        let terminator = cpp_displaced_preprocessor_terminator(conditional)
16657            .expect("structured displaced #endif");
16658        assert_eq!(node_text(terminator, damaged), "#endif");
16659        assert!(terminator.end_byte() <= declaration.start_byte());
16660        assert!(!preprocessor_conditional_contains_descendant(
16661            conditional,
16662            declaration
16663        ));
16664        assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
16665        assert!(preprocessor_conditional_contains_descendant(
16666            outer,
16667            declaration
16668        ));
16669
16670        let tree = parse(guarded);
16671        let conditional = tree
16672            .root_node()
16673            .named_child(0)
16674            .filter(|node| node.kind() == "preproc_ifdef")
16675            .expect("ordinary conditional");
16676        let declaration = conditional
16677            .named_children(&mut conditional.walk())
16678            .find(|node| node.kind() == "declaration")
16679            .expect("guarded declaration");
16680        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
16681        assert!(preprocessor_conditional_contains_descendant(
16682            conditional,
16683            declaration
16684        ));
16685
16686        let damaged_alternative = format!(
16687            "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
16688            "UNUSED(value)\n".repeat(64)
16689        );
16690        let tree = parse(&damaged_alternative);
16691        let conditional = tree
16692            .root_node()
16693            .named_child(0)
16694            .filter(|node| node.kind() == "preproc_ifdef")
16695            .expect("outer conditional with an alternative");
16696        assert!(conditional.has_error());
16697        assert!(conditional.child_by_field_name("alternative").is_some());
16698        assert!(
16699            conditional
16700                .child(conditional.child_count() - 1)
16701                .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
16702        );
16703        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
16704
16705        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";
16706        let tree = parse(split_declaration);
16707        let root = tree.root_node();
16708        let conditional = root
16709            .named_children(&mut root.walk())
16710            .find(|node| node.kind() == "preproc_ifdef" && node.start_position().row == 3)
16711            .expect("split declaration conditional");
16712        let target = split_declaration
16713            .find("static int target")
16714            .expect("target byte");
16715        let boundary =
16716            cpp_displaced_preprocessor_boundary(conditional).expect("split declaration boundary");
16717        assert!(boundary.end_byte <= target, "{boundary:?}");
16718        assert_eq!(boundary.end_line, 9, "{boundary:?}");
16719        let target_node = root
16720            .descendant_for_byte_range(target, target + "static".len())
16721            .expect("target node");
16722        assert!(!preprocessor_conditional_contains_descendant(
16723            conditional,
16724            target_node
16725        ));
16726    }
16727
16728    #[test]
16729    fn fragmented_reference_guard_is_recovered() {
16730        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";
16731        let mut parser = Parser::new();
16732        parser
16733            .set_language(&tree_sitter_cpp::LANGUAGE.into())
16734            .expect("C++ grammar");
16735        let tree = parser.parse(source, None).expect("fixture tree");
16736        let start = source.rfind("helper").expect("reference byte");
16737        let node = tree
16738            .root_node()
16739            .descendant_for_byte_range(start, start + "helper".len())
16740            .expect("reference node");
16741        let mut expected = HashSet::default();
16742        expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
16743            vec![
16744                BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
16745                BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
16746            ],
16747        )));
16748        assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
16749    }
16750
16751    #[test]
16752    fn expression_defined_and_ifndef_guards_are_incompatible() {
16753        let source = "#if defined(WIN_MODE)\nint selected;\n#endif\n#ifndef WIN_MODE\nint rejected;\n#endif\n";
16754        let mut parser = Parser::new();
16755        parser
16756            .set_language(&tree_sitter_cpp::LANGUAGE.into())
16757            .expect("C++ grammar");
16758        let tree = parser.parse(source, None).expect("fixture tree");
16759        let root = tree.root_node();
16760        let selected_start = source.find("selected").expect("selected declaration");
16761        let rejected_start = source.find("rejected").expect("rejected declaration");
16762        let selected = root
16763            .descendant_for_byte_range(selected_start, selected_start + "selected".len())
16764            .expect("selected node");
16765        let rejected = root
16766            .descendant_for_byte_range(rejected_start, rejected_start + "rejected".len())
16767            .expect("rejected node");
16768        let selected_guards =
16769            preprocessor_guard_environment(selected, source).expect("selected guards");
16770        let rejected_guards =
16771            preprocessor_guard_environment(rejected, source).expect("rejected guards");
16772
16773        assert!(
16774            merge_preprocessor_guards(&selected_guards, &rejected_guards).is_none(),
16775            "opposite spellings of one macro guard must contradict"
16776        );
16777    }
16778
16779    #[test]
16780    fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
16781        let source = r#"#ifdef _WIN32
16782#if defined(__cplusplus)
16783extern "C"
16784#endif
16785int platform_api(void);
16786#endif
16787
16788#ifdef _WIN32
16789static int entropy_target(void) { return 0; }
16790#else
16791#ifdef HAVE_COMMON_RANDOM
16792static int other_target(void) { return 0; }
16793#elif defined(HAVE_GETENTROPY)
16794static int entropy_target(void) { return 1; }
16795static int use_entropy(void) { return entropy_target(); }
16796#endif
16797#endif
16798"#;
16799        let mut parser = Parser::new();
16800        parser
16801            .set_language(&tree_sitter_cpp::LANGUAGE.into())
16802            .expect("C++ grammar");
16803        let tree = parser.parse(source, None).expect("fixture tree");
16804        let start = source.rfind("entropy_target()").expect("reference");
16805        let node = tree
16806            .root_node()
16807            .descendant_for_byte_range(start, start + "entropy_target".len())
16808            .expect("reference node");
16809        let guards = preprocessor_guard_environment(node, source).expect("active C branch");
16810        assert!(
16811            guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
16812            "{guards:#?}"
16813        );
16814        assert!(
16815            guards.contains(&PreprocessorGuard::Undefined(
16816                "HAVE_COMMON_RANDOM".to_string()
16817            )),
16818            "{guards:#?}"
16819        );
16820        assert!(
16821            guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
16822            "{guards:#?}"
16823        );
16824        assert!(
16825            !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
16826            "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
16827        );
16828    }
16829
16830    #[test]
16831    fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
16832        let source = "#define KEY 42\n#ifdef ENABLE_KEYS\nint classify(int value) {\n    switch (value) {\n        case KEY: return 1;\n        default: return 0;\n    }\n}\n#endif\n";
16833        let mut parser = Parser::new();
16834        parser
16835            .set_language(&tree_sitter_cpp::LANGUAGE.into())
16836            .expect("C++ grammar");
16837        let tree = parser.parse(source, None).expect("fixture tree");
16838        let root = tree.root_node();
16839        let node_at = |text: &str, start: usize| {
16840            root.descendant_for_byte_range(start, start + text.len())
16841                .expect("token node")
16842        };
16843
16844        let key_start = source.find("case KEY").expect("case label") + "case ".len();
16845        let guard_start = source.find("ENABLE_KEYS").expect("guard name");
16846        assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
16847        assert!(!is_ordinary_macro_reference_node(node_at(
16848            "ENABLE_KEYS",
16849            guard_start,
16850        )));
16851    }
16852
16853    #[test]
16854    fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
16855        let source = "#if HAVE_ARM_NEON\nstatic int target(void) { return 1; }\n#endif\n#if HAVE_ARM_NEON && ENABLE_FAST_PATH\nint use(void) { return target(); }\n#endif\n";
16856        let mut parser = Parser::new();
16857        parser
16858            .set_language(&tree_sitter_cpp::LANGUAGE.into())
16859            .expect("C++ grammar");
16860        let tree = parser.parse(source, None).expect("fixture tree");
16861        let root = tree.root_node();
16862        let definition_start = source.find("target(void)").expect("definition");
16863        let reference_start = source.rfind("target()").expect("reference");
16864        let definition = root
16865            .descendant_for_byte_range(definition_start, definition_start + "target".len())
16866            .expect("definition node");
16867        let reference = root
16868            .descendant_for_byte_range(reference_start, reference_start + "target".len())
16869            .expect("reference node");
16870        let required =
16871            preprocessor_guard_environment(definition, source).expect("definition guard");
16872        let active = preprocessor_guard_environment(reference, source).expect("reference guard");
16873        assert!(guard_requirements_hold_at_reference(
16874            &required,
16875            Some(&active)
16876        ));
16877    }
16878
16879    #[test]
16880    fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
16881        let source = "g_autoptr(FuChunkArray) self = make_array();";
16882        let mut parser = Parser::new();
16883        parser
16884            .set_language(&tree_sitter_cpp::LANGUAGE.into())
16885            .expect("C++ grammar");
16886        let tree = parser.parse(source, None).expect("fixture tree");
16887        let statement = tree.root_node().named_child(0).expect("statement");
16888        let binding =
16889            recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
16890        assert_eq!(binding.name, "self");
16891        assert_eq!(binding.type_name, "FuChunkArray");
16892        assert_eq!(binding.pointer_depth, 1);
16893
16894        let near_miss = "holder(FuChunkArray) self = make_array();";
16895        let tree = parser.parse(near_miss, None).expect("near-miss tree");
16896        let statement = tree.root_node().named_child(0).expect("statement");
16897        assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
16898    }
16899
16900    #[test]
16901    fn boolean_guard_normalization_proves_equivalence_and_implication() {
16902        let windows = BooleanGuardExpression::Defined("WIN32".to_string());
16903        let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
16904        let negated_windows_branch =
16905            BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
16906        let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
16907        assert_eq!(negated_windows_branch, portable);
16908
16909        let missing_a = BooleanGuardExpression::Undefined("A".to_string());
16910        let missing_b = BooleanGuardExpression::Undefined("B".to_string());
16911        let missing_c = BooleanGuardExpression::Undefined("C".to_string());
16912        let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
16913        let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
16914        assert!(fallback_branch.implies(&fallback_declaration));
16915        assert!(
16916            BooleanGuardExpression::Truthy("FEATURE".to_string())
16917                .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
16918        );
16919        assert!(
16920            BooleanGuardExpression::Undefined("FEATURE".to_string())
16921                .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
16922        );
16923        assert!(
16924            !BooleanGuardExpression::Defined("FEATURE".to_string())
16925                .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
16926        );
16927        assert!(!fallback_declaration.implies(&fallback_branch));
16928    }
16929
16930    #[test]
16931    fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
16932        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";
16933        let mut parser = Parser::new();
16934        parser
16935            .set_language(&tree_sitter_cpp::LANGUAGE.into())
16936            .expect("C++ grammar");
16937        let tree = parser.parse(source, None).expect("fixture tree");
16938        let root = tree.root_node();
16939        let call = |marker: &str| {
16940            let start = source.find(marker).expect("call marker");
16941            let mut node = root
16942                .descendant_for_byte_range(start, start + "helper".len())
16943                .expect("call name node");
16944            loop {
16945                if node.kind() == "call_expression" {
16946                    break node;
16947                }
16948                node = node.parent().expect("call expression ancestor");
16949            }
16950        };
16951        let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
16952        let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
16953        let keyword_call = call("helper(NULL, template); /* bound */");
16954        let keyword_arguments = keyword_call
16955            .child_by_field_name("arguments")
16956            .expect("keyword argument list");
16957        assert_eq!(
16958            recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
16959            1
16960        );
16961        assert_eq!(
16962            recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
16963            0
16964        );
16965
16966        let unbound_call = call("helper(NULL, template); /* unbound */");
16967        let unbound_arguments = unbound_call
16968            .child_by_field_name("arguments")
16969            .expect("unbound argument list");
16970        assert_eq!(
16971            recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
16972            0
16973        );
16974    }
16975
16976    #[test]
16977    fn c_function_declarator_recovery_accepts_invocations_not_binders() {
16978        let source = r#"#define MAKE(type) type *value
16979MAKE(int *);
16980typedef struct Item Item;
16981struct CPUX86State { struct { int ZMM_L(int); } xmm_regs[8]; };
16982void gen_op_movl(void *s, int first, int second) { }
16983const char *strZ(const char *value) { return value; }
16984int body(void *s) {
16985    MAKE(int *);
16986    gen_op_movl(s, offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)),
16987                offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)));
16988    execvp(strZ(value), UNCONSTIFY(char **, args));
16989}
16990STATIC EFI_STATUS Encode () { return 0; }
16991"#;
16992        let tree = parse_cpp(source);
16993        let top_macro_start = source.find("MAKE(int *);").expect("top macro");
16994        let top_macro = tree
16995            .root_node()
16996            .named_descendant_for_byte_range(top_macro_start, top_macro_start + 4)
16997            .expect("top macro node");
16998        let body_macro_start = source
16999            .match_indices("MAKE(int *);")
17000            .nth(1)
17001            .expect("body macro")
17002            .0;
17003        let body_macro = tree
17004            .root_node()
17005            .named_descendant_for_byte_range(body_macro_start, body_macro_start + 4)
17006            .expect("body macro node");
17007        let function_call_start = source
17008            .find("gen_op_movl(s, offsetof(CPUX86State")
17009            .expect("function call");
17010        let function_call = tree
17011            .root_node()
17012            .named_descendant_for_byte_range(function_call_start, function_call_start + 11)
17013            .expect("function call node");
17014        let strz_start = source.find("strZ(value)").expect("nested function call");
17015        let strz = tree
17016            .root_node()
17017            .named_descendant_for_byte_range(strz_start, strz_start + 4)
17018            .expect("nested function call node");
17019        let binder_start = source.find("Encode").expect("binder");
17020        let binder = tree
17021            .root_node()
17022            .named_descendant_for_byte_range(binder_start, binder_start + 6)
17023            .expect("binder node");
17024
17025        assert!(recovered_c_function_declarator_invocation(top_macro));
17026        assert!(recovered_c_function_declarator_invocation(body_macro));
17027        assert!(recovered_c_function_declarator_invocation(function_call));
17028        assert!(recovered_c_function_declarator_invocation(strz));
17029        assert!(!recovered_c_function_declarator_invocation(binder));
17030    }
17031
17032    #[test]
17033    fn c_parenthesized_declarator_recovery_keeps_keyword_argument_and_rejects_siblings() {
17034        let source = r#"typedef int krb5_context;
17035int helper(int first, int second) { return first + second; }
17036static krb5_context ctx;
17037int main(int argc, char **argv) {
17038    int ccinitial;
17039    const char *collection_name, *typename;
17040    typename = helper(ctx, ccinitial);
17041    return 0;
17042}
17043"#;
17044        let tree = parse_cpp(source);
17045        let ctx = tree
17046            .root_node()
17047            .descendant_for_byte_range(
17048                source.find("ctx, ccinitial").expect("ctx argument"),
17049                source.find("ctx, ccinitial").expect("ctx argument") + 3,
17050            )
17051            .expect("ctx node");
17052        let ccinitial_start = source.find("ctx, ccinitial").expect("ctx argument") + 5;
17053        let ccinitial = tree
17054            .root_node()
17055            .descendant_for_byte_range(ccinitial_start, ccinitial_start + "ccinitial".len())
17056            .expect("sibling node");
17057        let typename = named_node_at(&tree, source, "typename = helper");
17058        let helper = named_node_at(&tree, source, "helper(ctx, ccinitial)");
17059
17060        assert_eq!(ctx.kind(), "identifier");
17061        assert!(recovered_c_parenthesized_declarator_reference(ctx));
17062        assert!(!recovered_c_parenthesized_declarator_reference(ccinitial));
17063        assert!(!recovered_c_parenthesized_declarator_reference(typename));
17064        assert!(!recovered_c_parenthesized_declarator_reference(helper));
17065    }
17066
17067    fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
17068        let mut parser = Parser::new();
17069        parser
17070            .set_language(&tree_sitter_cpp::LANGUAGE.into())
17071            .expect("C++ grammar");
17072        let tree = parser.parse(source, None).expect("C++ fixture tree");
17073        let mut stack = vec![tree.root_node()];
17074        while let Some(node) = stack.pop() {
17075            if node.kind() == "enum_specifier" {
17076                return flattened_macro_namespace_components(node, source);
17077            }
17078            let mut cursor = node.walk();
17079            let children = node.named_children(&mut cursor).collect::<Vec<_>>();
17080            stack.extend(children.into_iter().rev());
17081        }
17082        None
17083    }
17084
17085    #[test]
17086    fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
17087        let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
17088namespace detail
17089{
17090enum class value_t { null };
17091}
17092NLOHMANN_JSON_NAMESPACE_END
17093NLOHMANN_JSON_NAMESPACE_BEGIN
17094namespace next
17095{
17096struct next_type {};
17097}
17098NLOHMANN_JSON_NAMESPACE_END
17099"#;
17100        assert_eq!(
17101            first_enum_flattened_namespace(complete),
17102            Some(vec!["detail".to_string()])
17103        );
17104
17105        let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
17106        assert_eq!(
17107            first_enum_flattened_namespace(&stale_end),
17108            Some(vec!["detail".to_string()]),
17109            "a stale end marker before the begin marker must not replace the intended namespace"
17110        );
17111
17112        let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
17113namespace detail
17114{
17115enum class value_t { null };
17116}
17117struct next_type {};
17118"#;
17119        assert_eq!(first_enum_flattened_namespace(incomplete), None);
17120    }
17121}
17122
17123/// Comparator laws for the total C++ lookup order introduced by #1876.
17124///
17125/// `sort_lookup_units` is the single tie-break the C++ resolver applies before
17126/// any "first wins" selection (template families in #1836, the visible
17127/// identifier index, the type-candidate lists). If its comparator is not a
17128/// total order over CodeUnit identity, some pair stays tied and the survivor
17129/// falls back to the order the units arrived in -- which is FxHash iteration
17130/// order over keys whose hash covers the absolute workspace root. That is the
17131/// exact mechanism behind #1836 and the #414 / #432 heisenbug, so the laws are
17132/// checked generatively rather than on one hand-picked list.
17133///
17134/// CodeUnit identity is `source`, `kind`, `fq`, `package_segment_count`,
17135/// `signature` and `synthetic` (see `impl PartialEq for CodeUnit`); a CodeUnit
17136/// carries no range, so declaration ranges are covered by the workspace-level
17137/// property in `tests/suite_analyzers/determinism_properties.rs` instead.
17138#[cfg(test)]
17139mod lookup_order_properties {
17140    use super::*;
17141    use proptest::prelude::*;
17142
17143    /// Segment spellings the C++ extractor and the shared renderer actually
17144    /// produce, including the `$`-joined nested spellings and non-ASCII
17145    /// identifiers that a byte-wise comparison has to keep apart.
17146    const ATOMS: [&str; 9] = ["a", "b", "A", "a$b", "a$", "$a", "ab", "naïve", "識別子"];
17147    const REL_PATHS: [&str; 3] = ["a.cpp", "b.cpp", "sub/a.cpp"];
17148    /// Two roots so the order is pinned across workspaces as well as inside
17149    /// one: the root path is precisely the byte string that used to leak into
17150    /// iteration order.
17151    const ROOT_NAMES: [&str; 2] = ["ws", "ws_much_longer_root_name"];
17152    const SIGNATURES: [Option<&str>; 3] = [None, Some("()"), Some("(int)")];
17153    const KINDS: [CodeUnitType; 6] = [
17154        CodeUnitType::Class,
17155        CodeUnitType::Function,
17156        CodeUnitType::Field,
17157        CodeUnitType::Module,
17158        CodeUnitType::Macro,
17159        CodeUnitType::FileScope,
17160    ];
17161
17162    /// Where one unit sits relative to another under the comparator that
17163    /// `sort_lookup_units` owns.
17164    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
17165    enum ProbedOrder {
17166        Before,
17167        Tied,
17168        After,
17169        /// Both directions reported "strictly first": the comparator is not
17170        /// dual, and no sort over it can be order-independent.
17171        Contradictory,
17172    }
17173
17174    impl ProbedOrder {
17175        fn mirror(self) -> Self {
17176            match self {
17177                ProbedOrder::Before => ProbedOrder::After,
17178                ProbedOrder::After => ProbedOrder::Before,
17179                other => other,
17180            }
17181        }
17182
17183        /// -1 / 0 / +1, so transitivity reads as the `<= 0` law.
17184        fn signum(self) -> i8 {
17185            match self {
17186                ProbedOrder::Before => -1,
17187                ProbedOrder::Tied => 0,
17188                ProbedOrder::After => 1,
17189                ProbedOrder::Contradictory => panic!("probed a non-dual comparator"),
17190            }
17191        }
17192    }
17193
17194    /// Read the comparator through its only caller.
17195    ///
17196    /// `sort_lookup_units` is a stable sort, so for a two-element slice the
17197    /// output says exactly whether the comparator put the second element
17198    /// strictly first. Sorting both arrangements of one pair therefore reports
17199    /// the comparator's verdict in both directions, including the contradictory
17200    /// case a single sort would hide.
17201    fn probe_order(left: &CodeUnit, right: &CodeUnit) -> ProbedOrder {
17202        if left == right {
17203            // A stable sort cannot distinguish two equal values, and `Equal` is
17204            // the only verdict a total order can give them.
17205            return ProbedOrder::Tied;
17206        }
17207        let mut forward = vec![left.clone(), right.clone()];
17208        sort_lookup_units(&mut forward);
17209        let mut backward = vec![right.clone(), left.clone()];
17210        sort_lookup_units(&mut backward);
17211        let left_first = backward[0] == *left;
17212        let right_first = forward[0] == *right;
17213        match (left_first, right_first) {
17214            (true, true) => ProbedOrder::Contradictory,
17215            (true, false) => ProbedOrder::Before,
17216            (false, true) => ProbedOrder::After,
17217            (false, false) => ProbedOrder::Tied,
17218        }
17219    }
17220
17221    /// `(kind, text)` per segment. `CodeUnit`'s own `Debug` prints interned
17222    /// segment IDs, which are process-local and say nothing about a failure.
17223    fn fq_segments(unit: &CodeUnit) -> Vec<(&'static str, &'static str)> {
17224        let interner = segment_interner();
17225        unit.fq()
17226            .segments()
17227            .iter()
17228            .map(|&id| {
17229                let (text, kind) = interner.resolve(id);
17230                (kind.name(), text)
17231            })
17232            .collect()
17233    }
17234
17235    fn code_unit_strategy() -> impl Strategy<Value = CodeUnit> {
17236        (
17237            0..ROOT_NAMES.len(),
17238            0..REL_PATHS.len(),
17239            0..KINDS.len(),
17240            prop::collection::vec((0..ATOMS.len(), 0..SegmentKind::ALL.len()), 1..=3),
17241            0..3usize,
17242            0..SIGNATURES.len(),
17243            any::<bool>(),
17244        )
17245            .prop_map(
17246                |(root, rel_path, kind, segments, package_prefix, signature, synthetic)| {
17247                    let source = ProjectFile::new(
17248                        std::env::temp_dir().join(ROOT_NAMES[root]),
17249                        REL_PATHS[rel_path],
17250                    );
17251                    let interner = segment_interner();
17252                    let mut fq = FqName::new();
17253                    for (atom, segment_kind) in &segments {
17254                        fq.push(interner.intern(ATOMS[*atom], SegmentKind::ALL[*segment_kind]));
17255                    }
17256                    // `from_fq` requires a non-empty declaration tail.
17257                    let package_segment_count = package_prefix % fq.len();
17258                    CodeUnit::from_fq(
17259                        source,
17260                        KINDS[kind],
17261                        fq,
17262                        package_segment_count,
17263                        SIGNATURES[signature].map(str::to_string),
17264                        synthetic,
17265                    )
17266                },
17267            )
17268    }
17269
17270    proptest! {
17271        #![proptest_config(ProptestConfig::with_cases(256))]
17272
17273        /// Reflexivity and duality: a unit ties with itself, and no pair is
17274        /// strictly first in both directions.
17275        #[test]
17276        fn lookup_order_is_reflexive_and_dual(
17277            left in code_unit_strategy(),
17278            right in code_unit_strategy(),
17279        ) {
17280            prop_assert_eq!(
17281                probe_order(&left, &left),
17282                ProbedOrder::Tied,
17283                "a unit must tie with itself: {:?}",
17284                left
17285            );
17286            let forward = probe_order(&left, &right);
17287            prop_assert_ne!(
17288                forward,
17289                ProbedOrder::Contradictory,
17290                "comparator put each of these strictly first: left={:?} right={:?}",
17291                left,
17292                right
17293            );
17294            prop_assert_eq!(
17295                probe_order(&right, &left),
17296                forward.mirror(),
17297                "compare(b, a) must reverse compare(a, b): left={:?} right={:?}",
17298                left,
17299                right
17300            );
17301        }
17302
17303        /// Transitivity: `a <= b` and `b <= c` imply `a <= c`.
17304        #[test]
17305        fn lookup_order_is_transitive(
17306            a in code_unit_strategy(),
17307            b in code_unit_strategy(),
17308            c in code_unit_strategy(),
17309        ) {
17310            let ab = probe_order(&a, &b);
17311            let bc = probe_order(&b, &c);
17312            let ac = probe_order(&a, &c);
17313            for (probed, pair) in [(ab, "a,b"), (bc, "b,c"), (ac, "a,c")] {
17314                prop_assert_ne!(
17315                    probed,
17316                    ProbedOrder::Contradictory,
17317                    "comparator is not dual over {}: a={:?} b={:?} c={:?}",
17318                    pair,
17319                    a,
17320                    b,
17321                    c
17322                );
17323            }
17324            if ab.signum() <= 0 && bc.signum() <= 0 {
17325                prop_assert!(
17326                    ac.signum() <= 0,
17327                    "transitivity broken: a<=b ({:?}) and b<=c ({:?}) but a?c is {:?}; \
17328                     a={:?} b={:?} c={:?}",
17329                    ab,
17330                    bc,
17331                    ac,
17332                    a,
17333                    b,
17334                    c
17335                );
17336            }
17337        }
17338
17339        /// The property #1876 exists for: only identical identities may tie.
17340        /// A tie between distinct units is the residual hash-order dependence.
17341        #[test]
17342        fn lookup_order_separates_distinct_identities(
17343            left in code_unit_strategy(),
17344            right in code_unit_strategy(),
17345        ) {
17346            if probe_order(&left, &right) == ProbedOrder::Tied {
17347                prop_assert_eq!(
17348                    &left,
17349                    &right,
17350                    "distinct identities tied, so their order is whatever order they \
17351                     arrived in: left_segments={:?} right_segments={:?}",
17352                    fq_segments(&left),
17353                    fq_segments(&right)
17354                );
17355            }
17356        }
17357
17358        /// The consequence the resolver relies on: the sorted list is a
17359        /// function of the SET of units, not of the order they were pushed in.
17360        #[test]
17361        fn lookup_sort_is_permutation_invariant(
17362            units in prop::collection::vec(code_unit_strategy(), 1..=8),
17363        ) {
17364            let mut sorted = units.clone();
17365            sort_lookup_units(&mut sorted);
17366            for rotation in 0..units.len() {
17367                for reversed in [false, true] {
17368                    let mut permuted = units.clone();
17369                    permuted.rotate_left(rotation);
17370                    if reversed {
17371                        permuted.reverse();
17372                    }
17373                    sort_lookup_units(&mut permuted);
17374                    prop_assert_eq!(
17375                        &permuted,
17376                        &sorted,
17377                        "sorting a permutation gave a different list \
17378                         (rotation={}, reversed={}): input={:?}",
17379                        rotation,
17380                        reversed,
17381                        units
17382                    );
17383                }
17384            }
17385        }
17386    }
17387}