Skip to main content

brokk_bifrost_cpp/graph/
resolver.rs

1use crate::call_match::{CppArgType, normalize_cpp_type_name};
2use crate::compile_context::CppCompileContext;
3#[cfg(test)]
4use crate::declarations::cpp_displaced_preprocessor_terminator;
5use crate::declarations::{
6    CppComparableNode, CppComparableParameter, CppComparableSlot, CppRecoveredExportClassIndex,
7    cpp_displaced_preprocessor_boundary, cpp_export_macro_token, cpp_template_term, node_text,
8    normalize_cpp_whitespace, recovered_class_body_at, recovered_function_like_field_declarator,
9    recovered_pyobject_head_field,
10};
11use crate::graph::CppGraphSource;
12use crate::graph::extractor::ScanCtx;
13use crate::graph::syntax::{
14    function_macro_replacement_span, normalize_macro_continuations,
15    object_macro_replacement_type_references,
16};
17use crate::graph_support::CppSource;
18use crate::imports::{
19    IncludeTargetIndex, include_path_from_node, resolve_include_targets_with_index,
20};
21use crate::source_context::cpp_guard_set_to_runtime;
22pub use brokk_bifrost_core::analyzer::cpp_facts::{
23    CppClassDeclarationStrength, CppEnumOwnerKind as EnumOwnerKind,
24};
25use brokk_bifrost_core::analyzer::cpp_facts::{
26    CppDeclaredFieldTypeFact as DeclaredFieldTypeFact,
27    CppStructuredAliasTarget as StructuredAliasTarget,
28};
29use brokk_bifrost_core::analyzer::fq_name::{FqName, SegmentKind, segment_interner};
30use brokk_bifrost_core::analyzer::model::{
31    CallableArity, CodeUnitType, CppFieldLinkage, CppTemplateExpression, CppTemplateMetadata,
32    CppTemplateParameterMetadata, CppTemplateTerm, Language, LanguageDialect, StructuredTypeName,
33};
34use brokk_bifrost_core::analyzer::pool_memo::PoolSafeMemo;
35use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
36#[cfg(test)]
37use brokk_bifrost_core::analyzer::prepared_syntax::{PreparedSourceOrigin, PreparedSyntaxSource};
38use brokk_bifrost_core::analyzer::query_token::QueryToken;
39use brokk_bifrost_core::analyzer::structural::adapter_helpers::{field_name_in_parent, node_range};
40use brokk_bifrost_core::analyzer::tree_walk::{
41    ParentIndex, children_iter, named_children_iter, node_for_exact_range,
42    push_named_children_reversed,
43};
44use brokk_bifrost_core::analyzer::usages::common::same_node;
45use brokk_bifrost_core::analyzer::usages::local_inference::LocalInferenceEngine;
46use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
47use brokk_bifrost_core::cancellation::CancellationToken;
48use brokk_bifrost_core::hash::{HashMap, HashSet};
49#[cfg(test)]
50use brokk_bifrost_core::text_utils::compute_line_starts;
51use std::borrow::Cow;
52#[cfg(any(test, feature = "test-support"))]
53use std::cell::Cell;
54use std::cell::OnceCell;
55use std::cmp::Ordering as CmpOrdering;
56use std::collections::BTreeSet;
57use std::hash::Hash;
58use std::sync::atomic::{AtomicUsize, Ordering};
59use std::sync::{Arc, Mutex, OnceLock, RwLock};
60use std::time::{Duration, Instant};
61use tree_sitter::{Node, Parser, Tree};
62
63#[cfg(any(test, feature = "test-support"))]
64thread_local! {
65    static BOUNDED_VISIBILITY_DECLARATION_READ_COUNT: Cell<usize> = const { Cell::new(0) };
66    static BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT: Cell<usize> = const { Cell::new(0) };
67}
68
69#[derive(Clone, Copy, PartialEq, Eq)]
70pub enum TargetKind {
71    Type,
72    Constructor,
73    FreeFunction,
74    Method,
75    GlobalField,
76    MemberField,
77    Macro,
78}
79
80pub enum LexicalTypeResolution {
81    Resolved {
82        unit: CodeUnit,
83        components: Vec<String>,
84        candidates: Vec<CodeUnit>,
85    },
86    Ambiguous,
87    Missing,
88}
89
90#[derive(Clone, Copy)]
91enum TypeCandidateResolution<'a> {
92    Canonical,
93    PreserveAlias,
94    PreserveTarget(&'a CodeUnit),
95}
96
97/// Why a name did not reduce to one indexed type declaration.
98///
99/// The two answers are not interchangeable. `Ambiguous` means the index holds
100/// several declarations and the caller must choose; `Unresolvable` means the
101/// index holds none, which is a boundary the workspace cannot see past. A
102/// `using`/`typedef` alias to a template parameter or to a standard-library
103/// type is unresolvable, and reporting it as ambiguity produced an `ambiguous`
104/// answer with an empty candidate list (#1828).
105#[derive(Clone, Copy, Debug, PartialEq, Eq)]
106enum TypeCandidateFailure {
107    Ambiguous,
108    Unresolvable,
109}
110
111impl TypeCandidateFailure {
112    fn lexical_resolution(self) -> LexicalTypeResolution {
113        match self {
114            Self::Ambiguous => LexicalTypeResolution::Ambiguous,
115            Self::Unresolvable => LexicalTypeResolution::Missing,
116        }
117    }
118}
119
120pub enum LexicalCallableValueResolution {
121    Type(CodeUnit),
122    FreeFunction(CodeUnit),
123    Ambiguous,
124    Missing,
125}
126
127pub enum UsingEnumMemberResolution {
128    Resolved { owner: CodeUnit, member: CodeUnit },
129    Ambiguous,
130    Missing,
131}
132
133pub enum NamespaceValueResolution {
134    Resolved,
135    Ambiguous,
136    Missing,
137}
138
139#[derive(Clone, Debug, PartialEq, Eq)]
140pub enum OrdinaryMacroReferenceResolution {
141    Resolved(CodeUnit),
142    Ambiguous,
143    Missing,
144}
145
146#[derive(Clone, Debug, PartialEq, Eq)]
147pub enum RecoveredCReferenceRanges {
148    Complete(Vec<Range>),
149    LimitExceeded,
150}
151
152pub fn resolve_namespace_value(
153    analyzer: &CppGraphSource<'_>,
154    visibility: &VisibilityIndex<'_>,
155    file: &ProjectFile,
156    namespace: &str,
157    name: &str,
158    before_byte: usize,
159) -> NamespaceValueResolution {
160    let mut matches = Vec::new();
161    for candidate in visibility.visible_identifier_candidates(file, name) {
162        if type_owner_of(analyzer, candidate).is_some()
163            || candidate.package_name() != namespace
164            || (candidate.source() == file
165                && !analyzer
166                    .ranges(candidate)
167                    .iter()
168                    .any(|range| range.start_byte < before_byte))
169            || matches
170                .iter()
171                .any(|existing| same_visible_symbol(existing, candidate))
172        {
173            continue;
174        }
175        matches.push(candidate.clone());
176        if matches.len() > 1 {
177            return NamespaceValueResolution::Ambiguous;
178        }
179    }
180    matches
181        .pop()
182        .map(|_| NamespaceValueResolution::Resolved)
183        .unwrap_or(NamespaceValueResolution::Missing)
184}
185
186pub(crate) struct ScopedUsingEnumOwners {
187    scopes: Vec<Vec<CodeUnit>>,
188}
189
190/// Same-file class and namespace imports collected by the targeted scanner's AST prepass.
191/// Cross-file and inherited class imports are deliberately not inferred without persisted
192/// evidence; a missing imported enumerator therefore remains unproven rather than being
193/// misresolved.
194pub(crate) struct SemanticUsingEnumOwners {
195    class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
196    namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
197}
198
199pub(crate) enum SemanticUsingEnumMemberResolution {
200    Class(UsingEnumMemberResolution),
201    Namespace(UsingEnumMemberResolution),
202    Missing,
203}
204
205impl SemanticUsingEnumOwners {
206    pub(crate) fn new() -> Self {
207        Self {
208            class_imports: HashMap::default(),
209            namespace_imports: HashMap::default(),
210        }
211    }
212
213    pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
214        let imports = self.class_imports.entry(class).or_default();
215        if !imports
216            .iter()
217            .any(|existing| same_visible_symbol(existing, &enum_owner))
218        {
219            imports.push(enum_owner);
220        }
221    }
222
223    pub fn import_namespace(
224        &mut self,
225        namespace: Vec<String>,
226        declaration_byte: usize,
227        enum_owner: CodeUnit,
228    ) {
229        let imports = self.namespace_imports.entry(namespace).or_default();
230        if !imports
231            .iter()
232            .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
233        {
234            imports.push((declaration_byte, enum_owner));
235        }
236    }
237
238    pub fn resolve_member(
239        &self,
240        visibility: &VisibilityIndex<'_>,
241        file: &ProjectFile,
242        class: Option<&CodeUnit>,
243        namespace: &[String],
244        before_byte: usize,
245        name: &str,
246    ) -> SemanticUsingEnumMemberResolution {
247        if let Some(class) = class
248            && let Some((_, imports)) = self
249                .class_imports
250                .iter()
251                .find(|(owner, _)| same_visible_symbol(owner, class))
252        {
253            let resolution =
254                resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
255            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
256                return SemanticUsingEnumMemberResolution::Class(resolution);
257            }
258        }
259        for prefix_len in (0..=namespace.len()).rev() {
260            let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
261                continue;
262            };
263            let owners = imports
264                .iter()
265                .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
266                .map(|(_, owner)| owner);
267            let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
268            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
269                return SemanticUsingEnumMemberResolution::Namespace(resolution);
270            }
271        }
272        SemanticUsingEnumMemberResolution::Missing
273    }
274}
275
276fn resolve_using_enum_member_for_owners<'a>(
277    visibility: &VisibilityIndex<'_>,
278    file: &ProjectFile,
279    owners: impl IntoIterator<Item = &'a CodeUnit>,
280    name: &str,
281) -> UsingEnumMemberResolution {
282    let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
283    for owner in owners {
284        for member in visibility.visible_members_for_owner_name(file, owner, name) {
285            if !member.is_field()
286                || matches.iter().any(|(existing_owner, existing_member)| {
287                    same_visible_symbol(existing_owner, owner)
288                        && same_visible_symbol(existing_member, member)
289                })
290            {
291                continue;
292            }
293            matches.push((owner.clone(), member.clone()));
294        }
295    }
296    match matches.len() {
297        0 => UsingEnumMemberResolution::Missing,
298        1 => {
299            let (owner, member) = matches.pop().expect("one using-enum match");
300            UsingEnumMemberResolution::Resolved { owner, member }
301        }
302        _ => UsingEnumMemberResolution::Ambiguous,
303    }
304}
305
306impl ScopedUsingEnumOwners {
307    pub(crate) fn new() -> Self {
308        Self {
309            scopes: vec![Vec::new()],
310        }
311    }
312
313    pub fn enter_scope(&mut self) {
314        self.scopes.push(Vec::new());
315    }
316
317    pub fn exit_scope(&mut self) {
318        if self.scopes.len() > 1 {
319            self.scopes.pop();
320        }
321    }
322
323    pub fn import(&mut self, owner: CodeUnit) {
324        let scope = self
325            .scopes
326            .last_mut()
327            .expect("using-enum scope stack is never empty");
328        if !scope
329            .iter()
330            .any(|existing| same_visible_symbol(existing, &owner))
331        {
332            scope.push(owner);
333        }
334    }
335
336    pub fn resolve_member(
337        &self,
338        visibility: &VisibilityIndex<'_>,
339        file: &ProjectFile,
340        name: &str,
341    ) -> UsingEnumMemberResolution {
342        for scope in self.scopes.iter().rev() {
343            let resolution =
344                resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
345            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
346                return resolution;
347            }
348        }
349        UsingEnumMemberResolution::Missing
350    }
351}
352
353#[derive(Clone)]
354pub struct TargetSpec {
355    pub target: CodeUnit,
356    pub kind: TargetKind,
357    pub owner: Option<CodeUnit>,
358    pub member_name: String,
359    pub callable_arity: Option<CallableArity>,
360    pub activated_callable_arities: Vec<ActivatedCallableArity>,
361    pub param_types: Option<Vec<String>>,
362    pub enum_owner_kind: EnumOwnerKind,
363    pub owner_is_forward_declaration: bool,
364    pub callable_has_definition_body: bool,
365}
366
367#[derive(Clone, Copy)]
368pub struct ActivatedCallableArity {
369    pub activation_byte: usize,
370    pub arity: CallableArity,
371}
372
373#[derive(Debug, PartialEq, Eq, Hash)]
374pub struct TypeScanKey {
375    target: LogicalSymbolKey,
376    member_name: String,
377}
378
379#[derive(Clone, Debug, PartialEq, Eq, Hash)]
380struct LogicalSymbolKey {
381    kind: CodeUnitType,
382    fq_name: String,
383    signature: Option<String>,
384}
385
386struct ResolvedTypeOwner {
387    unit: CodeUnit,
388    is_forward_declaration: bool,
389}
390
391impl TargetSpec {
392    pub fn type_scan_key(&self) -> Option<TypeScanKey> {
393        (self.kind == TargetKind::Type).then(|| TypeScanKey {
394            target: logical_symbol_key(&self.target),
395            member_name: self.member_name.clone(),
396        })
397    }
398
399    pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
400        if target.is_class() {
401            return Some(Self::new(
402                target.clone(),
403                TargetKind::Type,
404                Some(target.clone()),
405                target.identifier().to_string(),
406                None,
407                None,
408            ));
409        }
410
411        if target.is_field() {
412            // A namespace (module) is not a receiver: a namespace-scoped constant such as
413            // `example::DefaultPrefix` is referenced unqualified from inside the namespace and
414            // qualified from outside, exactly like a global. Treating a module owner as a
415            // member-field owner makes the receiver/owner-context match reject every valid
416            // reference, so resolve it as a global field instead.
417            let owner = type_owner_of(analyzer, target);
418            let kind = if owner.is_some() {
419                TargetKind::MemberField
420            } else {
421                TargetKind::GlobalField
422            };
423            let enum_owner_kind = owner
424                .as_ref()
425                .map(|owner| classify_enum_owner(analyzer, owner))
426                .unwrap_or(EnumOwnerKind::NonEnum);
427            let mut spec = Self::new(
428                target.clone(),
429                kind,
430                owner,
431                target.identifier().to_string(),
432                None,
433                None,
434            );
435            spec.enum_owner_kind = enum_owner_kind;
436            return Some(spec);
437        }
438
439        if target.is_function() {
440            // Free functions declared inside a namespace have a module owner; that namespace is
441            // not a call receiver, so resolve them as free functions rather than methods.
442            let owner_resolution = target_type_owner_resolution(analyzer, target);
443            let owner_is_forward_declaration = owner_resolution
444                .as_ref()
445                .is_some_and(|owner| owner.is_forward_declaration);
446            let owner = owner_resolution.map(|owner| owner.unit);
447            let kind = if owner.as_ref().is_some_and(|owner| {
448                target.identifier() == owner.identifier()
449                    || analyzer
450                        .cpp
451                        .and_then(|cpp| cpp.template_metadata(owner))
452                        .is_some_and(|metadata| metadata.primary_name == target.identifier())
453            }) {
454                TargetKind::Constructor
455            } else if owner.is_some() {
456                TargetKind::Method
457            } else {
458                TargetKind::FreeFunction
459            };
460            let mut spec = Self::new(
461                target.clone(),
462                kind,
463                owner,
464                target.identifier().to_string(),
465                cpp_callable_arity(analyzer, target),
466                cpp_callable_parameter_types(analyzer, target),
467            );
468            spec.owner_is_forward_declaration = owner_is_forward_declaration;
469            spec.callable_has_definition_body =
470                callable_target_has_definition_body(analyzer, target);
471            return Some(spec);
472        }
473
474        if target.is_macro() {
475            return Some(Self::new(
476                target.clone(),
477                TargetKind::Macro,
478                None,
479                target.identifier().to_string(),
480                None,
481                None,
482            ));
483        }
484
485        None
486    }
487
488    pub fn with_visible_callable_arities<'a>(
489        &'a self,
490        analyzer: &CppGraphSource<'_>,
491        cpp: &dyn CppSource,
492        visibility: &VisibilityIndex<'_>,
493        file: &ProjectFile,
494        prepared: &PreparedSyntaxTree,
495    ) -> Cow<'a, Self> {
496        let macro_parameter_arity =
497            visibility.callable_parameter_macro_arity(&self.target, self.param_types.as_deref());
498        let activated_callable_arities =
499            visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
500        if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
501            return Cow::Borrowed(self);
502        }
503        let mut effective = self.clone();
504        if let Some(macro_parameter_arity) = macro_parameter_arity {
505            effective.callable_arity = Some(macro_parameter_arity);
506        }
507        effective.activated_callable_arities = activated_callable_arities;
508        Cow::Owned(effective)
509    }
510
511    pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
512        let base = self.callable_arity?;
513        Some(
514            self.activated_callable_arities
515                .iter()
516                .filter(|candidate| candidate.activation_byte <= byte)
517                .fold(base, |arity, candidate| {
518                    merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
519                }),
520        )
521    }
522
523    pub fn new(
524        target: CodeUnit,
525        kind: TargetKind,
526        owner: Option<CodeUnit>,
527        member_name: String,
528        callable_arity: Option<CallableArity>,
529        param_types: Option<Vec<String>>,
530    ) -> Self {
531        Self {
532            target,
533            kind,
534            owner,
535            member_name,
536            callable_arity,
537            activated_callable_arities: Vec::new(),
538            param_types,
539            enum_owner_kind: EnumOwnerKind::NonEnum,
540            owner_is_forward_declaration: false,
541            callable_has_definition_body: false,
542        }
543    }
544}
545
546fn callable_target_has_definition_body(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> bool {
547    analyzer
548        .signature_metadata(target)
549        .iter()
550        .any(|metadata| !metadata.is_declaration_only())
551}
552
553fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
554    LogicalSymbolKey {
555        kind: unit.kind(),
556        fq_name: unit.fq_name(),
557        signature: unit.signature().map(str::to_string),
558    }
559}
560
561fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
562    let Some(facts) = analyzer.declaration_source_properties(owner) else {
563        return EnumOwnerKind::NonEnum;
564    };
565    let Some(first) = facts.first() else {
566        return EnumOwnerKind::NonEnum;
567    };
568    if facts.iter().all(|fact| fact.enum_kind == first.enum_kind) {
569        first.enum_kind
570    } else {
571        EnumOwnerKind::NonEnum
572    }
573}
574
575#[derive(Clone, PartialEq, Eq, Hash)]
576pub struct CppScanBinding {
577    pub unit: Option<CodeUnit>,
578    pub type_name: Option<String>,
579    pub indirection: i32,
580}
581
582impl CppScanBinding {
583    pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
584        Self {
585            type_name: Some(cpp_name_for(&unit)),
586            unit: Some(unit),
587            indirection,
588        }
589    }
590
591    pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
592        Self {
593            type_name: Some(type_name),
594            unit,
595            indirection,
596        }
597    }
598
599    pub fn as_arg_type(&self) -> Option<CppArgType> {
600        let name = self
601            .type_name
602            .clone()
603            .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
604        Some(CppArgType {
605            name,
606            unit: self.unit.clone(),
607            indirection: self.indirection,
608            pointee_const: false,
609        })
610    }
611}
612
613type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
614pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
615pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
616type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
617type MacroEnvironmentCheckpointCell = Arc<OnceLock<MacroEnvironmentCheckpoints>>;
618type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
619type MacroLexicalTemplateCache =
620    HashMap<(ProjectFile, usize), Option<crate::graph::macro_lexical::MacroTemplate>>;
621
622type MacroLocalBindingTemplateCache =
623    HashMap<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>;
624type MacroReplacementBodyCache = HashMap<(ProjectFile, usize), Option<Arc<ParsedReplacementBody>>>;
625type MacroTypeParameterCache = HashMap<(ProjectFile, usize), Option<Arc<[usize]>>>;
626type StructuredIncludeFactCell = Arc<OnceLock<Arc<[StructuredIncludeFact]>>>;
627
628struct StructuredIncludeFact {
629    start_byte: usize,
630    end_byte: usize,
631    path: String,
632}
633
634#[derive(Clone, Default)]
635pub struct MacroEnvironment {
636    bindings: HashMap<String, MacroBinding>,
637    known_undefined_names: HashSet<String>,
638    /// Names the translation unit's compile command proves defined (#2011):
639    /// the `-D`s that survive command ordering, intersected across every
640    /// configuration naming the TU. Seeded once at TU start. An explicit
641    /// `#undef` seen later lands in `known_undefined_names` and wins.
642    build_proven_defines: HashSet<String>,
643    unknown_names: bool,
644    applied_pragma_once_files: HashSet<ProjectFile>,
645    maybe_applied_pragma_once_files: HashSet<ProjectFile>,
646}
647
648/// How many macro events one checkpoint window may cover.
649///
650/// A request for an environment replays only the events between the nearest
651/// earlier checkpoint and its own frontier, so one file's whole scan costs its
652/// event count (the checkpoint build) plus this many applications per request,
653/// whatever order the requests arrive in. The forward cursor this replaced was
654/// optimal for one worker reading one file in byte order and quadratic for the
655/// inverse, which asks many workers for positions that move backwards (#1496).
656pub const MACRO_ENVIRONMENT_CHECKPOINT_STRIDE: usize = 32;
657
658/// One event prefix of a file whose environment the index keeps.
659struct MacroEnvironmentCheckpoint {
660    /// How many of the file's events this environment has applied.
661    frontier: usize,
662    environment: Arc<MacroEnvironment>,
663}
664
665/// The replay checkpoints for one file's macro events, ascending by frontier
666/// and always starting at frontier zero (the compile-proven defines alone).
667///
668/// Checkpoints use an adaptive stride no greater than
669/// [`MACRO_ENVIRONMENT_CHECKPOINT_STRIDE`], and one lands directly after every
670/// `#include` event. Applying an include event replays the included file's
671/// complete event list, so keeping one there holds that unbounded cost out of
672/// every later replay window.
673struct MacroEnvironmentCheckpoints {
674    checkpoints: Vec<MacroEnvironmentCheckpoint>,
675}
676
677impl MacroEnvironmentCheckpoints {
678    /// The latest checkpoint at or before `frontier`.
679    fn at_or_before(&self, frontier: usize) -> &MacroEnvironmentCheckpoint {
680        let index = self
681            .checkpoints
682            .partition_point(|checkpoint| checkpoint.frontier <= frontier);
683        assert!(
684            index > 0,
685            "a checkpoint vector starts at frontier zero, which precedes every request"
686        );
687        &self.checkpoints[index - 1]
688    }
689}
690
691impl MacroEnvironment {
692    fn binding(&self, name: &str) -> Option<&MacroBinding> {
693        self.bindings.get(name)
694    }
695
696    fn may_bind(&self, name: &str) -> bool {
697        self.bindings.contains_key(name) || self.unknown_names
698    }
699
700    fn insert(&mut self, name: String, binding: MacroBinding) {
701        self.known_undefined_names.remove(&name);
702        self.bindings.insert(name, binding);
703    }
704
705    fn remove(&mut self, name: &str) {
706        self.bindings.remove(name);
707        self.known_undefined_names.insert(name.to_string());
708    }
709
710    fn remove_known_undefined(&mut self, name: &str) {
711        self.known_undefined_names.remove(name);
712    }
713
714    fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
715        for binding in self.bindings.values_mut() {
716            *binding = MacroBinding::uncertain_from(binding, source, byte);
717        }
718        self.known_undefined_names.clear();
719        // An untracked include could `#undef` a command-line define, so the
720        // may-hold filter must stop treating the build facts as decisive from
721        // here on. The additive proof path keeps its facts: they still hold at
722        // the include chain's activation point.
723        self.build_proven_defines.clear();
724        self.unknown_names = true;
725    }
726
727    fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
728        guards.iter().all(|guard| self.guard_may_hold(guard))
729    }
730
731    fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
732        let Some(expression) = guard.as_boolean_expression() else {
733            return true;
734        };
735        self.boolean_guard_may_hold(&expression)
736    }
737
738    fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
739        match expression {
740            BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
741            BooleanGuardExpression::Undefined(name) => {
742                self.bindings
743                    .get(name)
744                    .is_none_or(|binding| !binding.is_exact())
745                    && (!self.build_proven_defines.contains(name)
746                        || self.known_undefined_names.contains(name))
747            }
748            BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
749            BooleanGuardExpression::Opaque(_)
750            | BooleanGuardExpression::NegatedOpaque(_)
751            | BooleanGuardExpression::Constant(true) => true,
752            BooleanGuardExpression::Constant(false) => false,
753            BooleanGuardExpression::All(expressions) => expressions
754                .iter()
755                .all(|expression| self.boolean_guard_may_hold(expression)),
756            BooleanGuardExpression::Any(expressions) => expressions
757                .iter()
758                .any(|expression| self.boolean_guard_may_hold(expression)),
759        }
760    }
761}
762
763#[derive(Clone)]
764pub enum EffectiveUsingTarget {
765    Ordinary {
766        name: String,
767        target_components: Vec<String>,
768        global: bool,
769    },
770    Namespace {
771        namespace_components: Vec<String>,
772        global: bool,
773    },
774}
775
776#[derive(Clone)]
777pub struct OrdinaryTypeImport {
778    pub target: EffectiveUsingTarget,
779    pub source: ProjectFile,
780    pub declaration_byte: usize,
781    pub scope_start: usize,
782    pub scope_end: usize,
783    pub scope_depth: usize,
784    pub block_scope: bool,
785    pub lexical_depth: usize,
786    pub declaration_namespace: Vec<String>,
787    pub namespace_scope: Option<Vec<String>>,
788    pub resolved_target_components: Option<Vec<String>>,
789    pub required_guards: HashSet<PreprocessorGuard>,
790}
791
792#[derive(Clone)]
793pub struct ConditionalIncludeProjection {
794    pub activation_byte: usize,
795    pub required_guards: HashSet<PreprocessorGuard>,
796    /// The subset of `required_guards` a lone `#if` contributes: a branch no
797    /// sibling `#else` completes, so no configuration is obliged to take it.
798    /// [`IncludePathAdmission::Compatible`] still demands those.
799    pub partial_guards: HashSet<PreprocessorGuard>,
800}
801
802#[derive(Default)]
803pub struct SourceUsingIndex {
804    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
805    pub directives: Vec<OrdinaryTypeImport>,
806}
807
808#[derive(Default)]
809pub struct ProjectUsingIndex {
810    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
811    pub directives: Vec<OrdinaryTypeImport>,
812}
813
814type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
815
816pub struct EffectiveUsingIndex {
817    projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
818}
819
820impl EffectiveUsingIndex {
821    fn new(_root: ProjectFile) -> Self {
822        Self {
823            projected_by_name: Mutex::new(HashMap::default()),
824        }
825    }
826
827    pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
828        self.projected_by_name
829            .lock()
830            .expect("C++ effective-using projection cache poisoned")
831            .entry(name.to_string())
832            .or_default()
833            .clone()
834    }
835}
836
837pub enum OrdinaryTypeImportResolution {
838    Resolved {
839        target: CodeUnit,
840        target_components: Vec<String>,
841        lexical_depth: usize,
842        is_direct: bool,
843    },
844    Ambiguous {
845        lexical_depth: usize,
846    },
847    Missing,
848}
849
850type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
851type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
852type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
853type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
854type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
855type ParserAliasTargetMatchCell = Arc<OnceLock<bool>>;
856type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
857type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
858
859/// One callable declaration's inputs to [`VisibilityIndex::same_logical_callable`],
860/// read from its declaration syntax rather than from its persisted signature
861/// string: the comparable shape of each parameter, and the trailing identity
862/// suffix that shape does not carry.
863struct ExtractedComparable {
864    shapes: Vec<CppComparableSlot>,
865    suffix: String,
866}
867
868/// How many alias hops [`VisibilityIndex::same_logical_callable`] follows
869/// before giving up on a written type name. A visited set already stops a
870/// cycle; this stops an adversarially long chain from costing a lookup per hop.
871const MAX_COMPARABLE_ALIAS_HOPS: usize = 32;
872
873/// Which comparison a conditional `#include` path's guards face before the
874/// header it reaches counts as visible at a reference.
875///
876/// `Proven` is the build's rule: `compile_commands.json` covers the
877/// reference's translation unit, so the reference's active guards plus the
878/// defines the build proves must imply every guard on the path, and a
879/// platform macro the build does not prove keeps the header invisible
880/// (#2011).
881///
882/// `Compatible` is the rule for a translation unit no build describes. A
883/// complete `#if`/`#else` family is a case analysis every configuration takes
884/// one branch of, so a branch of one is admitted unless its guards contradict
885/// the reference's own -- the rule the resolver already applies to a
886/// declaration's own guards (#2988). A lone `#if` is a different claim: no
887/// configuration is obliged to take it, so its guards stay strictly required
888/// and an unprovable one still reports `missing_compile_context` (#2011).
889///
890/// Without the first half, libuv's `uv/threadpool.h`, which `uv.h` includes
891/// from both arms of `#if defined(_WIN32)`, and `uv/unix.h`, which it includes
892/// from the `#else` arm, are invisible to every unguarded reference in the
893/// project (#3088).
894#[derive(Clone, Copy, Debug, PartialEq, Eq)]
895enum IncludePathAdmission {
896    Proven,
897    Compatible,
898}
899
900impl IncludePathAdmission {
901    fn admits(
902        self,
903        required: &HashSet<PreprocessorGuard>,
904        partial: &HashSet<PreprocessorGuard>,
905        reference_guards: Option<&HashSet<PreprocessorGuard>>,
906    ) -> bool {
907        match self {
908            Self::Proven => guard_requirements_hold_at_reference(required, reference_guards),
909            Self::Compatible => {
910                guard_requirements_hold_at_reference(partial, reference_guards)
911                    && guards_compatible_at_reference(required, reference_guards)
912            }
913        }
914    }
915}
916
917/// Per-query C++ visibility facts.
918///
919/// The analyzer is *borrowed*, never cloned: `TreeSitterAnalyzer::clone` gives
920/// the clone a fresh, empty `QueryReadCache` on purpose (clones cross
921/// generations and overlays, where another generation's hydrated states would
922/// be wrong). An index that owned a clone would therefore see an inactive read
923/// cache for every `prepared_syntax` call it makes, re-reading and re-parsing
924/// the same source from the store once per candidate instead of once per query
925/// — the #1175 blow-up, where one scan re-parsed a 4.8 MB generated header
926/// tens of thousands of times.
927pub struct VisibilityIndex<'a> {
928    cpp: &'a dyn CppSource,
929    /// Proof that the request scope the index was built under is still open.
930    /// The index is a per-query object whose lifetime is inside the scope's,
931    /// so carrying the token here instead of on ninety method signatures is
932    /// the same guarantee for far less plumbing (issue #2414 step 3).
933    token: QueryToken<'a>,
934    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
935    visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
936    global_field_internal_linkage: HashMap<CodeUnit, bool>,
937    visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
938    alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
939    visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
940    parser_alias_target_matches:
941        RwLock<HashMap<(ProjectFile, String, LogicalSymbolKey), ParserAliasTargetMatchCell>>,
942    ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
943    project_using_index: OnceLock<ProjectUsingIndex>,
944    callable_reference_specs:
945        Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
946    structured_include_fact_cells: Mutex<HashMap<ProjectFile, StructuredIncludeFactCell>>,
947    include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
948    compile_proven_guard_cells: Mutex<HashMap<ProjectFile, Arc<HashSet<PreprocessorGuard>>>>,
949    include_path_admission_cells: Mutex<HashMap<ProjectFile, IncludePathAdmission>>,
950    conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
951    #[cfg(any(test, feature = "test-support"))]
952    conditional_include_projection_index_build_count: AtomicUsize,
953    #[cfg(any(test, feature = "test-support"))]
954    conditional_include_projection_state_count: AtomicUsize,
955    #[cfg(any(test, feature = "test-support"))]
956    conditional_include_target_state_count: AtomicUsize,
957    #[cfg(any(test, feature = "test-support"))]
958    include_activation_build_count: AtomicUsize,
959    #[cfg(any(test, feature = "test-support"))]
960    using_donor_activation_count: AtomicUsize,
961    #[cfg(any(test, feature = "test-support"))]
962    using_namespace_lookup_count: AtomicUsize,
963    #[cfg(any(test, feature = "test-support"))]
964    using_name_candidate_inspection_count: AtomicUsize,
965    #[cfg(any(test, feature = "test-support"))]
966    callable_reference_spec_build_count: AtomicUsize,
967    #[cfg(any(test, feature = "test-support"))]
968    alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
969    #[cfg(any(test, feature = "test-support"))]
970    visible_parser_alias_name_set_build_count: AtomicUsize,
971    parser_alias_fallback_calls: AtomicUsize,
972    parser_alias_fallback_files: AtomicUsize,
973    parser_alias_source_parses: AtomicUsize,
974    parser_alias_fallback_elapsed_micros: AtomicUsize,
975    field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
976    structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
977    callable_comparables: Mutex<HashMap<CodeUnit, Option<Arc<ExtractedComparable>>>>,
978    comparable_name_declarations: Mutex<HashMap<StructuredTypeName, Option<CodeUnit>>>,
979    indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
980    indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
981    precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
982    c_tag_kind_cache: Mutex<HashMap<CodeUnit, Option<CppCTagKind>>>,
983    c_tag_complete_definition_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
984    macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
985    macro_event_name_sets: Mutex<HashMap<ProjectFile, Arc<HashSet<String>>>>,
986    pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
987    // The environment at selected event prefixes of each file, built once and read by every
988    // worker. The authoritative differential shares this index across target workers whose
989    // frontiers interleave arbitrarily and move backwards, so a forward cursor -- per worker or
990    // not -- replayed a file's events once per backward request. Checkpoints answer any position
991    // with a binary search and at most one stride of replay, and being immutable they need no
992    // per-worker copy (#1496).
993    macro_environment_checkpoints: Mutex<HashMap<ProjectFile, MacroEnvironmentCheckpointCell>>,
994    macro_replacements: Mutex<MacroReplacementCache>,
995    macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
996    pub(crate) macro_lexical_templates: Mutex<MacroLexicalTemplateCache>,
997    macro_replacement_bodies: Mutex<MacroReplacementBodyCache>,
998    macro_type_parameters: Mutex<MacroTypeParameterCache>,
999    callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
1000    #[cfg(any(test, feature = "test-support"))]
1001    pub macro_replacement_parse_count: AtomicUsize,
1002    #[cfg(any(test, feature = "test-support"))]
1003    pub macro_event_application_count: AtomicUsize,
1004    /// How many files had their checkpoint vector built. One build per file
1005    /// per query even when workers race for the same file.
1006    #[cfg(any(test, feature = "test-support"))]
1007    pub macro_environment_checkpoint_build_count: AtomicUsize,
1008    /// How many requests landed off a checkpoint and so had to copy one and
1009    /// replay the events after it.
1010    #[cfg(any(test, feature = "test-support"))]
1011    pub macro_environment_copy_count: AtomicUsize,
1012    #[cfg(any(test, feature = "test-support"))]
1013    pub macro_environment_request_count: AtomicUsize,
1014    cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
1015    cpp_template_families: HashMap<String, Vec<CodeUnit>>,
1016    #[cfg(any(test, feature = "test-support"))]
1017    qualified_candidate_inspections: AtomicUsize,
1018    #[cfg(any(test, feature = "test-support"))]
1019    target_preserving_type_resolution_count: AtomicUsize,
1020    #[cfg(any(test, feature = "test-support"))]
1021    visibility_identifier_lookup_count: usize,
1022    #[cfg(any(test, feature = "test-support"))]
1023    visibility_identifier_batch_count: usize,
1024}
1025
1026impl Drop for VisibilityIndex<'_> {
1027    fn drop(&mut self) {
1028        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_none() {
1029            return;
1030        }
1031        #[cfg(any(test, feature = "test-support"))]
1032        eprintln!(
1033            "BIFROST_CPP_MACRO_STATS requests={} copies={} checkpoint_builds={} applications={}",
1034            self.macro_environment_request_count.load(Ordering::Relaxed),
1035            self.macro_environment_copy_count.load(Ordering::Relaxed),
1036            self.macro_environment_checkpoint_build_count
1037                .load(Ordering::Relaxed),
1038            self.macro_event_application_count.load(Ordering::Relaxed),
1039        );
1040        let calls = self.parser_alias_fallback_calls.load(Ordering::Relaxed);
1041        if calls == 0 {
1042            return;
1043        }
1044        eprintln!(
1045            "BIFROST_CPP_ALIAS_FALLBACK_STATS calls={} files={} source_parses={} elapsed_ms={}",
1046            calls,
1047            self.parser_alias_fallback_files.load(Ordering::Relaxed),
1048            self.parser_alias_source_parses.load(Ordering::Relaxed),
1049            self.parser_alias_fallback_elapsed_micros
1050                .load(Ordering::Relaxed)
1051                / 1_000,
1052        );
1053    }
1054}
1055
1056#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1057pub enum PreprocessorGuard {
1058    Defined(String),
1059    Undefined(String),
1060    Boolean(BooleanGuardExpression),
1061    Expression(String),
1062    NegatedExpression(String),
1063    Constant(bool),
1064}
1065
1066#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1067pub enum BooleanGuardExpression {
1068    Defined(String),
1069    Undefined(String),
1070    Truthy(String),
1071    Falsy(String),
1072    Opaque(String),
1073    NegatedOpaque(String),
1074    All(Vec<BooleanGuardExpression>),
1075    Any(Vec<BooleanGuardExpression>),
1076    Constant(bool),
1077}
1078
1079impl BooleanGuardExpression {
1080    fn negated(&self) -> Self {
1081        match self {
1082            Self::Defined(name) => Self::Undefined(name.clone()),
1083            Self::Undefined(name) => Self::Defined(name.clone()),
1084            Self::Truthy(name) => Self::Falsy(name.clone()),
1085            Self::Falsy(name) => Self::Truthy(name.clone()),
1086            Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
1087            Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
1088            Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
1089            Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
1090            Self::Constant(value) => Self::Constant(!value),
1091        }
1092    }
1093
1094    fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
1095        Self::normalized(expressions, true)
1096    }
1097
1098    fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
1099        Self::normalized(expressions, false)
1100    }
1101
1102    fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
1103        let mut normalized = Vec::new();
1104        for expression in expressions {
1105            match expression {
1106                Self::All(nested) if conjunction => normalized.extend(nested),
1107                Self::Any(nested) if !conjunction => normalized.extend(nested),
1108                Self::Constant(value) if value == conjunction => {}
1109                Self::Constant(value) => return Self::Constant(value),
1110                expression => normalized.push(expression),
1111            }
1112        }
1113        normalized.sort_unstable();
1114        normalized.dedup();
1115        match normalized.len() {
1116            0 => Self::Constant(conjunction),
1117            1 => normalized.pop().expect("one Boolean guard expression"),
1118            _ if conjunction => Self::All(normalized),
1119            _ => Self::Any(normalized),
1120        }
1121    }
1122
1123    fn implies(&self, required: &Self) -> bool {
1124        if self == required
1125            || matches!(self, Self::Constant(false))
1126            || matches!(required, Self::Constant(true))
1127        {
1128            return true;
1129        }
1130        if matches!(
1131            (self, required),
1132            (Self::Truthy(active), Self::Defined(required))
1133                | (Self::Undefined(active), Self::Falsy(required))
1134                if active == required
1135        ) {
1136            return true;
1137        }
1138        match self {
1139            Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
1140            Self::All(active) => match required {
1141                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1142                _ => active.iter().any(|expression| expression.implies(required)),
1143            },
1144            _ => match required {
1145                Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
1146                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1147                _ => false,
1148            },
1149        }
1150    }
1151
1152    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1153        match self {
1154            Self::Defined(name)
1155            | Self::Undefined(name)
1156            | Self::Truthy(name)
1157            | Self::Falsy(name) => name == macro_name,
1158            // Opaque expressions have structured conditional ownership but no
1159            // structured macro operands, so any mutation may change them.
1160            Self::Opaque(_) | Self::NegatedOpaque(_) => true,
1161            Self::All(expressions) | Self::Any(expressions) => expressions
1162                .iter()
1163                .any(|expression| expression.may_depend_on_macro(macro_name)),
1164            Self::Constant(_) => false,
1165        }
1166    }
1167
1168    pub fn heap_size(&self) -> usize {
1169        match self {
1170            Self::Defined(value)
1171            | Self::Undefined(value)
1172            | Self::Truthy(value)
1173            | Self::Falsy(value)
1174            | Self::Opaque(value)
1175            | Self::NegatedOpaque(value) => value.len(),
1176            Self::All(expressions) | Self::Any(expressions) => {
1177                expressions
1178                    .iter()
1179                    .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
1180                        size.saturating_add(std::mem::size_of::<Self>())
1181                            .saturating_add(expression.heap_size())
1182                    })
1183            }
1184            Self::Constant(_) => 0,
1185        }
1186    }
1187}
1188
1189impl PreprocessorGuard {
1190    fn as_boolean_expression(&self) -> Option<BooleanGuardExpression> {
1191        match self {
1192            Self::Defined(name) => Some(BooleanGuardExpression::Defined(name.clone())),
1193            Self::Undefined(name) => Some(BooleanGuardExpression::Undefined(name.clone())),
1194            Self::Boolean(expression) => Some(expression.clone()),
1195            Self::Constant(value) => Some(BooleanGuardExpression::Constant(*value)),
1196            Self::Expression(_) | Self::NegatedExpression(_) => None,
1197        }
1198    }
1199
1200    fn negated(&self) -> Self {
1201        match self {
1202            Self::Defined(name) => Self::Undefined(name.clone()),
1203            Self::Undefined(name) => Self::Defined(name.clone()),
1204            Self::Boolean(expression) => Self::Boolean(expression.negated()),
1205            Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1206            Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1207            Self::Constant(value) => Self::Constant(!value),
1208        }
1209    }
1210
1211    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1212        match self {
1213            Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1214            Self::Boolean(expression) => expression.may_depend_on_macro(macro_name),
1215            // These expressions could not be lowered to a Boolean operand
1216            // tree, so their dependencies remain unknown.
1217            Self::Expression(_) | Self::NegatedExpression(_) => true,
1218            Self::Constant(_) => false,
1219        }
1220    }
1221}
1222
1223#[derive(Clone, PartialEq, Eq)]
1224pub enum MacroDefinition {
1225    Object {
1226        replacement: String,
1227    },
1228    Function {
1229        parameters: Vec<String>,
1230        replacement: String,
1231    },
1232    VariadicFunction {
1233        parameters: Vec<String>,
1234        replacement: String,
1235    },
1236    Unsupported,
1237}
1238
1239#[derive(Clone, Debug, PartialEq, Eq)]
1240pub enum MacroIncludeProtection {
1241    MacroGuard(String),
1242    PragmaOnce,
1243    None,
1244}
1245
1246enum ParsedMacroReplacement {
1247    Parsed { source: String, tree: Tree },
1248    Unsupported,
1249}
1250
1251/// The sentinel that gives a function-like macro replacement a parseable
1252/// statement context. The replacement text is copied in verbatim, so the only
1253/// bytes ahead of it are this prefix.
1254const MACRO_BODY_SENTINEL_PREFIX: &str = "void __bifrost_macro_body() { ";
1255
1256/// A function-like macro replacement parsed inside a sentinel function body.
1257///
1258/// Tree-sitter keeps a `#define NAME(a) ...` replacement as one opaque
1259/// `preproc_arg`. Wrapping that exact byte slice in a function body recovers
1260/// its statements, declarations, and member calls as ordinary C++ structure.
1261/// The slice is copied verbatim at [`Self::body_offset`], so a node range in
1262/// [`Self::tree`] maps back onto the defining `preproc_arg` by subtracting
1263/// that offset.
1264pub struct ParsedReplacementBody {
1265    pub source: String,
1266    pub tree: Tree,
1267    pub body_offset: usize,
1268    pub parameters: Vec<String>,
1269    /// For a normal parse this is an identity map. A constrained statement
1270    /// recovery may insert separators after a macro formal; each boundary in
1271    /// this map points back to the corresponding byte in the original
1272    /// replacement, keeping source-backed ranges exact after substitution.
1273    original_offsets: Box<[usize]>,
1274}
1275
1276impl ParsedReplacementBody {
1277    /// The sentinel function body holding the replacement's statements.
1278    pub fn statements(&self) -> Option<Node<'_>> {
1279        first_descendant_of_kind(self.tree.root_node(), "function_definition")?
1280            .child_by_field_name("body")
1281    }
1282
1283    /// The byte range `node` occupies in the file that defines the macro.
1284    ///
1285    /// `replacement_start` is the logical replacement span's start byte.
1286    /// The origin map accounts for continuation whitespace and any synthetic
1287    /// statement separators inserted while parsing the replacement.
1288    pub fn file_range(&self, node: Node<'_>, replacement_start: usize) -> std::ops::Range<usize> {
1289        assert!(
1290            node.start_byte() >= self.body_offset,
1291            "synthetic sentinel node cannot be mapped to a macro replacement"
1292        );
1293        assert!(
1294            node.end_byte() >= self.body_offset,
1295            "synthetic sentinel node cannot be mapped to a macro replacement"
1296        );
1297        let start_offset = node.start_byte() - self.body_offset;
1298        let end_offset = node.end_byte() - self.body_offset;
1299        assert!(start_offset <= end_offset);
1300        let start_origin = *self
1301            .original_offsets
1302            .get(start_offset)
1303            .expect("replacement node start must have a source mapping");
1304        let end_origin = *self
1305            .original_offsets
1306            .get(end_offset)
1307            .expect("replacement node end must have a source mapping");
1308        assert!(start_origin <= end_origin);
1309        let start = replacement_start + start_origin;
1310        let end = replacement_start + end_origin;
1311        start..end
1312    }
1313
1314    /// Whether the replacement names the variadic argument pack.
1315    ///
1316    /// `__VA_ARGS__` parses as an ordinary identifier, so the sentinel tree
1317    /// gives no error for it even though the expansion it stands for is
1318    /// unknown at the definition. Reject it from the parsed tree rather than
1319    /// by scanning the replacement text.
1320    fn expands_variadic_arguments(&self) -> bool {
1321        let mut stack = vec![self.tree.root_node()];
1322        while let Some(node) = stack.pop() {
1323            if matches!(
1324                node.kind(),
1325                "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
1326            ) && node_text(node, &self.source) == "__VA_ARGS__"
1327            {
1328                return true;
1329            }
1330            push_named_children_reversed(node, &mut stack);
1331        }
1332        false
1333    }
1334}
1335
1336fn parse_cpp_integer_literal(text: &str) -> Option<i128> {
1337    let compact = text.chars().filter(|ch| *ch != '\'').collect::<String>();
1338    let (radix, digits_start, digit_matches): (u32, usize, fn(char) -> bool) =
1339        if compact.starts_with("0x") || compact.starts_with("0X") {
1340            (16, 2, |ch| ch.is_ascii_hexdigit())
1341        } else if compact.starts_with("0b") || compact.starts_with("0B") {
1342            (2, 2, |ch| matches!(ch, '0' | '1'))
1343        } else if compact.starts_with('0') && compact.len() > 1 {
1344            (8, 0, |ch| matches!(ch, '0'..='7'))
1345        } else {
1346            (10, 0, |ch| ch.is_ascii_digit())
1347        };
1348    let digit_len = compact[digits_start..]
1349        .chars()
1350        .take_while(|ch| digit_matches(*ch))
1351        .map(char::len_utf8)
1352        .sum::<usize>();
1353    if digit_len == 0 {
1354        return None;
1355    }
1356    let digits_end = digits_start + digit_len;
1357    if !compact[digits_end..]
1358        .chars()
1359        .all(|ch| matches!(ch, 'u' | 'U' | 'l' | 'L' | 'z' | 'Z'))
1360    {
1361        return None;
1362    }
1363    i128::from_str_radix(&compact[digits_start..digits_end], radix).ok()
1364}
1365
1366#[derive(Clone)]
1367enum MacroLocalBindingTypeTemplate {
1368    Parameter(usize),
1369    Fixed(String),
1370}
1371
1372#[derive(Clone)]
1373struct MacroLocalBindingTemplate {
1374    name: String,
1375    declared_type: MacroLocalBindingTypeTemplate,
1376    pointer_depth: i32,
1377}
1378
1379/// A local declaration contributed by one structurally known function-like macro.
1380///
1381/// `type_node` points into the invocation syntax when the replacement's type
1382/// is one of the macro parameters. Consumers can therefore use their normal
1383/// lexical type resolver without parsing replacement text themselves.
1384/// `proven_unit` carries a structured C-tag resolution when the grammar splits
1385/// an explicit `struct` or `union` argument across recovery nodes.
1386pub struct MacroLocalBinding<'tree> {
1387    pub name: String,
1388    pub type_name: String,
1389    pub type_node: Option<Node<'tree>>,
1390    pub pointer_depth: i32,
1391    pub proven_unit: Option<CodeUnit>,
1392}
1393
1394/// The source-backed declaration introduced by a function-like macro. The
1395/// declaration belongs to the macro definition, while the reference range
1396/// returned by [`VisibilityIndex::macro_lexical_references`] belongs to the
1397/// definition body, invocation argument, or caller scope where substitution
1398/// makes the binding visible.
1399#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1400pub enum MacroLexicalBindingKind {
1401    Parameter,
1402    Local,
1403}
1404
1405#[derive(Clone, Debug, PartialEq, Eq)]
1406pub struct MacroLexicalBinding {
1407    pub definition: ProjectFile,
1408    pub kind: MacroLexicalBindingKind,
1409    pub name: String,
1410    pub name_range: std::ops::Range<usize>,
1411    pub declaration_range: std::ops::Range<usize>,
1412}
1413
1414/// Lexical macro references are query-local facts. A cancelled or bounded
1415/// walk is kept distinct from a complete empty enumeration so authoritative
1416/// inverse callers can preserve their completeness status.
1417#[derive(Clone, Debug, Default, PartialEq, Eq)]
1418pub struct MacroLexicalReferences {
1419    pub references: Vec<(std::ops::Range<usize>, MacroLexicalBinding)>,
1420    pub truncated: bool,
1421    pub cancelled: bool,
1422}
1423
1424fn macro_replacement_type_parameters(
1425    body: &ParsedReplacementBody,
1426    parameters: &[String],
1427) -> Option<Vec<usize>> {
1428    let mut found = Vec::new();
1429    let mut stack = vec![body.tree.root_node()];
1430    while let Some(node) = stack.pop() {
1431        let type_position = node.kind() == "type_identifier"
1432            || (node.kind() == "identifier"
1433                && node.parent().is_some_and(|parent| {
1434                    parent.kind() == "type_descriptor"
1435                        && parent.child_by_field_name("type") == Some(node)
1436                }));
1437        let offsetof_type_position = node.kind() == "identifier"
1438            && node
1439                .parent()
1440                .filter(|parent| parent.kind() == "argument_list")
1441                .and_then(|arguments| arguments.parent())
1442                .is_some_and(|call| {
1443                    call.kind() == "call_expression"
1444                        && call
1445                            .child_by_field_name("function")
1446                            .is_some_and(|function| {
1447                                function.kind() == "identifier"
1448                                    && node_text(function, &body.source) == "offsetof"
1449                            })
1450                        && call
1451                            .child_by_field_name("arguments")
1452                            .is_some_and(|arguments| {
1453                                argument_children(arguments).next() == Some(node)
1454                            })
1455                });
1456        if (type_position || offsetof_type_position)
1457            && let Some(index) = parameters
1458                .iter()
1459                .position(|parameter| parameter == node_text(node, &body.source))
1460            && !found.contains(&index)
1461        {
1462            found.push(index);
1463        }
1464        push_named_children_reversed(node, &mut stack);
1465    }
1466    (!found.is_empty()).then_some(found)
1467}
1468
1469fn macro_replacement_type_parameter(
1470    body: &ParsedReplacementBody,
1471    parameters: &[String],
1472) -> Option<usize> {
1473    let mut parameters = macro_replacement_type_parameters(body, parameters)?;
1474    (parameters.len() == 1).then(|| parameters.pop().unwrap())
1475}
1476
1477pub(crate) fn macro_type_argument_node<'tree>(
1478    node: Node<'tree>,
1479    source: &str,
1480) -> Option<Node<'tree>> {
1481    match node.kind() {
1482        "type_descriptor" => {
1483            let type_child = node
1484                .child_by_field_name("type")
1485                .or_else(|| first_type_child(node))?;
1486            for index in (0..node.named_child_count()).rev() {
1487                let child = node.named_child(index)?;
1488                if child != type_child
1489                    && matches!(
1490                        child.kind(),
1491                        "identifier"
1492                            | "type_identifier"
1493                            | "qualified_identifier"
1494                            | "scoped_type_identifier"
1495                    )
1496                {
1497                    return Some(child);
1498                }
1499            }
1500            macro_type_argument_node(type_child, source)
1501        }
1502        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => {
1503            node.child_by_field_name("name")
1504        }
1505        "identifier" if matches!(node_text(node, source), "struct" | "union") => node
1506            .next_named_sibling()
1507            .filter(|sibling| sibling.is_error() && sibling.named_child_count() == 1)
1508            .and_then(|error| error.named_child(0))
1509            .filter(|name| matches!(name.kind(), "identifier" | "type_identifier")),
1510        _ => cpp_name_component_nodes(node).is_some().then_some(node),
1511    }
1512}
1513
1514fn c_function_macro_argument<'tree>(
1515    node: Node<'tree>,
1516) -> Option<(Node<'tree>, usize, Node<'tree>)> {
1517    let mut current = node;
1518    while let Some(parent) = current.parent() {
1519        if parent.kind() == "argument_list" {
1520            let call = parent.parent().filter(|call| {
1521                call.kind() == "call_expression"
1522                    && call.child_by_field_name("arguments") == Some(parent)
1523                    && call
1524                        .child_by_field_name("function")
1525                        .is_some_and(|function| {
1526                            function.kind() == "identifier"
1527                                && !node_range_contains(function, current)
1528                        })
1529            })?;
1530            let mut actuals = argument_children(parent).enumerate();
1531            let (index, argument) = actuals.find(|(_, argument)| {
1532                node_range_contains(*argument, node)
1533                    && (argument.start_byte() == current.start_byte()
1534                        || argument.end_byte() == current.end_byte())
1535            })?;
1536            return Some((call, index, argument));
1537        }
1538        current = parent;
1539    }
1540    None
1541}
1542
1543/// Recover GLib's `g_autoptr(T) name = value` declaration from the CST shape
1544/// produced by tree-sitter-cpp for C source. The grammar retains the macro
1545/// invocation as the assignment's left operand and the declared name as one
1546/// adjacent `ERROR(identifier)` node, so no macro text splitting is needed.
1547fn recognized_c_macro_declarator_binding<'tree>(
1548    statement: Node<'tree>,
1549    source: &str,
1550) -> Option<MacroLocalBinding<'tree>> {
1551    let assignment = match statement.kind() {
1552        "assignment_expression" => statement,
1553        "expression_statement" if statement.named_child_count() == 1 => statement.named_child(0)?,
1554        _ => return None,
1555    };
1556    if assignment.kind() != "assignment_expression" {
1557        return None;
1558    }
1559    let call = assignment.child_by_field_name("left")?;
1560    if call.kind() != "call_expression" {
1561        return None;
1562    }
1563    let function = call.child_by_field_name("function")?;
1564    if function.kind() != "identifier" || node_text(function, source) != "g_autoptr" {
1565        return None;
1566    }
1567    let arguments = call.child_by_field_name("arguments")?;
1568    let mut actuals = argument_children(arguments);
1569    let type_node = actuals.next()?;
1570    if actuals.next().is_some()
1571        || !matches!(
1572            type_node.kind(),
1573            "identifier"
1574                | "type_identifier"
1575                | "qualified_identifier"
1576                | "scoped_type_identifier"
1577                | "template_type"
1578        )
1579    {
1580        return None;
1581    }
1582    let name_node = (0..assignment.named_child_count())
1583        .filter_map(|index| assignment.named_child(index))
1584        .filter(|child| child.kind() == "ERROR")
1585        .filter_map(|error| {
1586            (error.named_child_count() == 1)
1587                .then(|| error.named_child(0))
1588                .flatten()
1589        })
1590        .find(|node| node.kind() == "identifier")?;
1591    let name = node_text(name_node, source).trim();
1592    let type_name = node_text(type_node, source).trim();
1593    if name.is_empty() || type_name.is_empty() {
1594        return None;
1595    }
1596    Some(MacroLocalBinding {
1597        name: name.to_string(),
1598        type_name: type_name.to_string(),
1599        type_node: Some(type_node),
1600        pointer_depth: 1,
1601        proven_unit: None,
1602    })
1603}
1604
1605#[derive(Clone, PartialEq, Eq)]
1606pub struct MacroBinding {
1607    source: ProjectFile,
1608    declaration_byte: usize,
1609    definition: MacroDefinition,
1610    exact: bool,
1611}
1612
1613impl MacroBinding {
1614    fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1615        Self {
1616            source: source.clone(),
1617            declaration_byte,
1618            definition: MacroDefinition::Unsupported,
1619            exact: false,
1620        }
1621    }
1622
1623    fn is_exact(&self) -> bool {
1624        self.exact
1625    }
1626
1627    fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1628        Self {
1629            source: source.clone(),
1630            declaration_byte,
1631            definition: current.definition.clone(),
1632            exact: false,
1633        }
1634    }
1635}
1636
1637/// The preprocessor conditionals whose truth decides whether one macro event
1638/// applies, by the start byte of each conditional node. Empty means the event
1639/// is unconditional.
1640///
1641/// [`VisibilityIndex::macro_event_condition_value`] needs exactly the
1642/// conditional ancestors that structurally contain the event, and deciding
1643/// containment means asking [`cpp_displaced_preprocessor_boundary`] for an
1644/// `#endif` tree-sitter displaced into error recovery -- a walk of the
1645/// conditional's whole subtree. That answer is a fact about the tree alone, so
1646/// it is settled once, when the events are collected, instead of on every
1647/// replay of the event (#1496).
1648type OwningPreprocessorConditionals = Box<[usize]>;
1649
1650#[derive(Clone)]
1651pub enum MacroEvent {
1652    Define {
1653        name: String,
1654        binding: MacroBinding,
1655        byte: usize,
1656        conditionals: OwningPreprocessorConditionals,
1657    },
1658    Undef {
1659        name: String,
1660        byte: usize,
1661        conditionals: OwningPreprocessorConditionals,
1662    },
1663    Include {
1664        targets: Vec<ProjectFile>,
1665        byte: usize,
1666        conditionals: OwningPreprocessorConditionals,
1667    },
1668    Invalidate {
1669        byte: usize,
1670    },
1671}
1672
1673impl MacroEvent {
1674    pub fn byte(&self) -> usize {
1675        match self {
1676            Self::Define { byte, .. }
1677            | Self::Undef { byte, .. }
1678            | Self::Include { byte, .. }
1679            | Self::Invalidate { byte } => *byte,
1680        }
1681    }
1682}
1683
1684#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1685pub enum CallArityEvidence {
1686    Exact(usize),
1687    Unknown,
1688}
1689
1690impl CallArityEvidence {
1691    pub fn exact(self) -> Option<usize> {
1692        match self {
1693            Self::Exact(arity) => Some(arity),
1694            Self::Unknown => None,
1695        }
1696    }
1697
1698    pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1699        self.exact().map(|arity| expected.accepts(arity))
1700    }
1701}
1702
1703struct CppAlias {
1704    name: String,
1705    target: String,
1706    namespace: Option<String>,
1707}
1708
1709type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1710
1711/// Why template-argument resolution failed. Definition diagnostics render
1712/// each mode differently; graph scans only care that the resolution is
1713/// unproven and match `Err(_)`.
1714#[derive(Debug, Clone, PartialEq, Eq)]
1715pub enum CppTemplateResolutionError {
1716    /// A template alias expansion revisited `alias`.
1717    AliasCycle { alias: CodeUnit },
1718    /// The explicit arguments do not bind to the declared template parameters.
1719    ArgumentBinding,
1720    /// Bound arguments do not substitute into the alias target's arguments.
1721    Substitution,
1722    /// No visible primary template declaration could be selected and
1723    /// reconciled for the specialization family.
1724    PrimarySelection,
1725    /// More than one applicable specialization remains and none is strictly
1726    /// more specialized than every other candidate.
1727    AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1728}
1729
1730/// The ambiguity candidates, deduplicated to one representative per visible
1731/// symbol so a diagnostic lists each contender once.
1732fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1733    let mut distinct: Vec<CodeUnit> = Vec::new();
1734    for unit in units {
1735        if !distinct
1736            .iter()
1737            .any(|existing| same_visible_symbol(existing, unit))
1738        {
1739            distinct.push(unit.clone());
1740        }
1741    }
1742    distinct
1743}
1744
1745/// Enumerate macro lexical references while constructing visibility only if
1746/// the bounded source walk finds a definition or identifier call candidate.
1747pub fn macro_lexical_references_with_visibility<'visibility, 'source: 'visibility, Factory>(
1748    visibility: Factory,
1749    file: &ProjectFile,
1750    root: Node<'_>,
1751    source: &str,
1752    max_references: usize,
1753    cancelled: impl FnMut() -> bool,
1754) -> MacroLexicalReferences
1755where
1756    Factory: FnOnce() -> &'visibility VisibilityIndex<'source>,
1757{
1758    crate::graph::macro_lexical::all_references(
1759        visibility,
1760        file,
1761        root,
1762        source,
1763        max_references,
1764        cancelled,
1765    )
1766}
1767
1768impl<'a> VisibilityIndex<'a> {
1769    pub fn cpp(&self) -> &'a dyn CppSource {
1770        self.cpp
1771    }
1772
1773    /// The request-scope proof this index was built with (issue #2414 step 3).
1774    pub fn token(&self) -> QueryToken<'a> {
1775        self.token
1776    }
1777
1778    /// Whether `file` has a structured unresolved include that is active before
1779    /// the reference at `before_byte`.
1780    ///
1781    /// Include facts are collected from the prepared tree once per visibility
1782    /// query. The reference position is still evaluated for each call because
1783    /// preprocessor visibility depends on the reference's own conditional
1784    /// context.
1785    pub fn has_unresolved_include_visible_before(
1786        &self,
1787        file: &ProjectFile,
1788        before_byte: usize,
1789    ) -> bool {
1790        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
1791            return false;
1792        };
1793        let cell = self
1794            .structured_include_fact_cells
1795            .lock()
1796            .expect("C++ structured include-fact cache poisoned")
1797            .entry(file.clone())
1798            .or_default()
1799            .clone();
1800        let facts = cell.get_or_init(|| collect_structured_include_facts(prepared.as_ref()));
1801        has_unresolved_include_visible_before_in_prepared(
1802            file,
1803            prepared.as_ref(),
1804            self.cpp.include_target_index(),
1805            facts,
1806            before_byte,
1807        )
1808    }
1809
1810    /// A [`VisibilityIndex`] over a caller-supplied visible-declaration map,
1811    /// bypassing the include-closure walk [`Self::build`] performs.
1812    ///
1813    /// The resolver's own unit tests drive the type-resolution paths against a
1814    /// hand-written visibility table; they live in `brokk-bifrost-analysis`
1815    /// because they need a real `CppAnalyzer`, so the struct literal they used
1816    /// to write inline is here instead of thirty-three public fields.
1817    #[cfg(any(test, feature = "test-support"))]
1818    pub fn from_visible_files_for_test(
1819        cpp: &'a dyn CppSource,
1820        token: QueryToken<'a>,
1821        visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1822    ) -> Self {
1823        let visible_source_files_by_root = visible_by_file
1824            .iter()
1825            .map(|(file, visible)| {
1826                (
1827                    file.clone(),
1828                    visible
1829                        .iter()
1830                        .map(|unit| unit.source().clone())
1831                        .chain(std::iter::once(file.clone()))
1832                        .collect(),
1833                )
1834            })
1835            .collect();
1836        let mut global_field_internal_linkage = HashMap::default();
1837        Self {
1838            cpp,
1839            token,
1840            visible_by_identifier: build_visible_identifier_index(
1841                &CppGraphSource::from_source(cpp, token),
1842                &visible_by_file,
1843                &visible_source_files_by_root,
1844                &mut global_field_internal_linkage,
1845            ),
1846            global_field_internal_linkage,
1847            visible_by_file,
1848            visible_source_files_by_root,
1849            alias_cells: Mutex::new(HashMap::default()),
1850            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1851            parser_alias_target_matches: RwLock::new(HashMap::default()),
1852            ordinary_type_import_cells: Mutex::new(HashMap::default()),
1853            project_using_index: OnceLock::new(),
1854            callable_reference_specs: Mutex::new(HashMap::default()),
1855            structured_include_fact_cells: Mutex::new(HashMap::default()),
1856            include_activation_cells: Mutex::new(HashMap::default()),
1857            compile_proven_guard_cells: Mutex::new(HashMap::default()),
1858            include_path_admission_cells: Mutex::new(HashMap::default()),
1859            conditional_include_projection_cells: Mutex::new(HashMap::default()),
1860            conditional_include_projection_index_build_count: AtomicUsize::new(0),
1861            conditional_include_projection_state_count: AtomicUsize::new(0),
1862            conditional_include_target_state_count: AtomicUsize::new(0),
1863            include_activation_build_count: AtomicUsize::new(0),
1864            using_donor_activation_count: AtomicUsize::new(0),
1865            using_namespace_lookup_count: AtomicUsize::new(0),
1866            using_name_candidate_inspection_count: AtomicUsize::new(0),
1867            callable_reference_spec_build_count: AtomicUsize::new(0),
1868            alias_source_parse_counts: Mutex::new(HashMap::default()),
1869            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1870            parser_alias_fallback_calls: AtomicUsize::new(0),
1871            parser_alias_fallback_files: AtomicUsize::new(0),
1872            parser_alias_source_parses: AtomicUsize::new(0),
1873            parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
1874            field_type_facts: Mutex::new(HashMap::default()),
1875            structured_alias_targets: Mutex::new(HashMap::default()),
1876            callable_comparables: Mutex::new(HashMap::default()),
1877            comparable_name_declarations: Mutex::new(HashMap::default()),
1878            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1879            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1880            precise_parent_cache: Mutex::new(HashMap::default()),
1881            c_tag_kind_cache: Mutex::new(HashMap::default()),
1882            c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
1883            macro_event_cells: Mutex::new(HashMap::default()),
1884            macro_event_name_sets: Mutex::new(HashMap::default()),
1885            macro_include_protection_cells: Mutex::new(HashMap::default()),
1886            macro_environment_checkpoints: Mutex::new(HashMap::default()),
1887            macro_replacements: Mutex::new(HashMap::default()),
1888            macro_local_binding_templates: Mutex::new(HashMap::default()),
1889            macro_lexical_templates: Mutex::new(HashMap::default()),
1890            macro_replacement_bodies: Mutex::new(HashMap::default()),
1891            macro_type_parameters: Mutex::new(HashMap::default()),
1892            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1893            macro_replacement_parse_count: AtomicUsize::new(0),
1894            macro_event_application_count: AtomicUsize::new(0),
1895            macro_environment_checkpoint_build_count: AtomicUsize::new(0),
1896            macro_environment_copy_count: AtomicUsize::new(0),
1897            macro_environment_request_count: AtomicUsize::new(0),
1898            cpp_template_metadata: HashMap::default(),
1899            cpp_template_families: HashMap::default(),
1900            qualified_candidate_inspections: AtomicUsize::new(0),
1901            target_preserving_type_resolution_count: AtomicUsize::new(0),
1902            visibility_identifier_lookup_count: 0,
1903            visibility_identifier_batch_count: 0,
1904        }
1905    }
1906
1907    /// The index's own C++ source, in the dispatching-analyzer shape.
1908    ///
1909    /// Four resolution paths reach the workspace through the C++ analyzer they
1910    /// already hold rather than through the analyzer the query was issued
1911    /// against; before the move they passed `&CppAnalyzer` straight into a
1912    /// `&dyn IAnalyzer` parameter. See [`CppGraphSource::from_source`].
1913    fn cpp_source(&self) -> CppGraphSource<'a> {
1914        CppGraphSource::from_source(self.cpp, self.token)
1915    }
1916
1917    pub fn build(
1918        cpp: &'a dyn CppSource,
1919        token: QueryToken<'a>,
1920        analyzer: &CppGraphSource<'_>,
1921        roots: &HashSet<ProjectFile>,
1922    ) -> Self {
1923        Self::build_with_cancellation(cpp, token, analyzer, roots, None)
1924    }
1925
1926    pub fn build_with_cancellation(
1927        cpp: &'a dyn CppSource,
1928        token: QueryToken<'a>,
1929        analyzer: &CppGraphSource<'_>,
1930        roots: &HashSet<ProjectFile>,
1931        cancellation: Option<&CancellationToken>,
1932    ) -> Self {
1933        let visibility_started = Instant::now();
1934        let include_targets = cpp.include_target_index();
1935        let includes_started = Instant::now();
1936        let mut include_graph = IncludeGraph::default();
1937        for root in roots {
1938            include_graph.extend_with(root, cancellation, &mut |file| {
1939                cpp.canonical_include_paths(token, file)
1940                    .unwrap_or_default()
1941                    .into_iter()
1942                    .flat_map(|include| {
1943                        resolve_include_targets_with_index(file, &include, include_targets)
1944                    })
1945                    .collect()
1946            });
1947        }
1948        let include_elapsed = includes_started.elapsed();
1949        let include_file_count = include_graph.files().count();
1950        let visible_source_files_by_root = roots
1951            .iter()
1952            .map(|root| {
1953                (
1954                    root.clone(),
1955                    include_graph.reachable_files(root, cancellation),
1956                )
1957            })
1958            .collect::<HashMap<_, _>>();
1959        let mut visibility_stats = BoundedVisibilityStats::default();
1960        let mut visible_by_file = build_bounded_visible_declarations(
1961            cpp,
1962            token,
1963            analyzer,
1964            roots,
1965            &visible_source_files_by_root,
1966            cancellation,
1967            &mut visibility_stats,
1968        );
1969        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
1970            eprintln!(
1971                "BIFROST_CPP_VISIBILITY_STATS total_ms={} include_ms={} include_files={} rounds={} root_names={} identifier_lookups={} identifier_batches={} candidate_units={} candidate_sources={} declaration_reads={} declaration_units={} selected_units={} dependency_source_names={} dependency_names={} lookup_ms={} declaration_ms={} dependency_source_ms={}",
1972                visibility_started.elapsed().as_millis(),
1973                include_elapsed.as_millis(),
1974                include_file_count,
1975                visibility_stats.rounds,
1976                visibility_stats.root_names,
1977                visibility_stats.identifier_lookups,
1978                visibility_stats.identifier_batches,
1979                visibility_stats.candidate_units,
1980                visibility_stats.candidate_sources,
1981                visibility_stats.declaration_reads,
1982                visibility_stats.declaration_units,
1983                visibility_stats.selected_units,
1984                visibility_stats.dependency_source_names,
1985                visibility_stats.dependency_names,
1986                visibility_stats.lookup_elapsed.as_millis(),
1987                visibility_stats.declaration_elapsed.as_millis(),
1988                visibility_stats.dependency_source_elapsed.as_millis(),
1989            );
1990        }
1991        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
1992        let finalize_started = Instant::now();
1993        if report_stats {
1994            eprintln!(
1995                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=started roots={} visible_units={}",
1996                visible_by_file.len(),
1997                visible_by_file.values().map(HashSet::len).sum::<usize>(),
1998            );
1999        }
2000        let owner_started = Instant::now();
2001        if report_stats {
2002            eprintln!("BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=started");
2003        }
2004        let owner_stats = extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
2005        if report_stats {
2006            eprintln!(
2007                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=completed unseen_owners={} definition_lookups={} admitted={} elapsed_ms={}",
2008                owner_stats.unseen_owners,
2009                owner_stats.definition_lookups,
2010                owner_stats.admitted,
2011                owner_started.elapsed().as_millis(),
2012            );
2013        }
2014        let mut global_field_internal_linkage = HashMap::default();
2015        let identifier_started = Instant::now();
2016        if report_stats {
2017            eprintln!(
2018                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=started"
2019            );
2020        }
2021        let visible_by_identifier = build_visible_identifier_index(
2022            analyzer,
2023            &visible_by_file,
2024            &visible_source_files_by_root,
2025            &mut global_field_internal_linkage,
2026        );
2027        if report_stats {
2028            eprintln!(
2029                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=completed roots={} names={} candidates={} elapsed_ms={}",
2030                visible_by_identifier.len(),
2031                visible_by_identifier
2032                    .values()
2033                    .map(HashMap::len)
2034                    .sum::<usize>(),
2035                visible_by_identifier
2036                    .values()
2037                    .flat_map(HashMap::values)
2038                    .map(Vec::len)
2039                    .sum::<usize>(),
2040                identifier_started.elapsed().as_millis(),
2041            );
2042        }
2043        let mut cpp_template_metadata = HashMap::default();
2044        let metadata_started = Instant::now();
2045        let mut template_classes = 0usize;
2046        if report_stats {
2047            eprintln!(
2048                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=started"
2049            );
2050        }
2051        for unit in visible_by_file
2052            .values()
2053            .flatten()
2054            .filter(|unit| unit.is_class())
2055        {
2056            template_classes += 1;
2057            if cpp_template_metadata.contains_key(unit) {
2058                continue;
2059            }
2060            if let Some(metadata) = cpp.template_metadata(unit) {
2061                cpp_template_metadata.insert(unit.clone(), metadata);
2062            }
2063        }
2064        if report_stats {
2065            eprintln!(
2066                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=completed classes={} metadata={} elapsed_ms={}",
2067                template_classes,
2068                cpp_template_metadata.len(),
2069                metadata_started.elapsed().as_millis(),
2070            );
2071        }
2072        let families_started = Instant::now();
2073        if report_stats {
2074            eprintln!(
2075                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=started"
2076            );
2077        }
2078        let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
2079        for (unit, metadata) in &cpp_template_metadata {
2080            cpp_template_families
2081                .entry(metadata.primary_fq_name.clone())
2082                .or_default()
2083                .push(unit.clone());
2084        }
2085        // `cpp_template_metadata` is hash-keyed on `CodeUnit`, so the push
2086        // order above is a function of those hashes. Two mirrored headers can
2087        // declare one specialization; `select_template_specialization` treats
2088        // them as interchangeable and returns the family's first entry, so an
2089        // unsorted family made the reported declaration depend on the
2090        // workspace's absolute path and on unrelated files (#1836). Order the
2091        // family exactly as `build_visible_identifier_index` orders its
2092        // per-identifier candidate lists.
2093        for family in cpp_template_families.values_mut() {
2094            sort_lookup_units(family);
2095        }
2096        if report_stats {
2097            eprintln!(
2098                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=completed families={} members={} elapsed_ms={}",
2099                cpp_template_families.len(),
2100                cpp_template_families.values().map(Vec::len).sum::<usize>(),
2101                families_started.elapsed().as_millis(),
2102            );
2103            eprintln!(
2104                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=completed roots={} visible_units={} elapsed_ms={} total_ms={}",
2105                visible_by_file.len(),
2106                visible_by_file.values().map(HashSet::len).sum::<usize>(),
2107                finalize_started.elapsed().as_millis(),
2108                visibility_started.elapsed().as_millis(),
2109            );
2110        }
2111        Self {
2112            cpp,
2113            token,
2114            visible_by_file,
2115            visible_by_identifier,
2116            global_field_internal_linkage,
2117            visible_source_files_by_root,
2118            alias_cells: Mutex::new(HashMap::default()),
2119            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2120            parser_alias_target_matches: RwLock::new(HashMap::default()),
2121            ordinary_type_import_cells: Mutex::new(HashMap::default()),
2122            project_using_index: OnceLock::new(),
2123            callable_reference_specs: Mutex::new(HashMap::default()),
2124            structured_include_fact_cells: Mutex::new(HashMap::default()),
2125            include_activation_cells: Mutex::new(HashMap::default()),
2126            compile_proven_guard_cells: Mutex::new(HashMap::default()),
2127            include_path_admission_cells: Mutex::new(HashMap::default()),
2128            conditional_include_projection_cells: Mutex::new(HashMap::default()),
2129            #[cfg(any(test, feature = "test-support"))]
2130            conditional_include_projection_index_build_count: AtomicUsize::new(0),
2131            #[cfg(any(test, feature = "test-support"))]
2132            conditional_include_projection_state_count: AtomicUsize::new(0),
2133            #[cfg(any(test, feature = "test-support"))]
2134            conditional_include_target_state_count: AtomicUsize::new(0),
2135            #[cfg(any(test, feature = "test-support"))]
2136            include_activation_build_count: AtomicUsize::new(0),
2137            #[cfg(any(test, feature = "test-support"))]
2138            using_donor_activation_count: AtomicUsize::new(0),
2139            #[cfg(any(test, feature = "test-support"))]
2140            using_namespace_lookup_count: AtomicUsize::new(0),
2141            #[cfg(any(test, feature = "test-support"))]
2142            using_name_candidate_inspection_count: AtomicUsize::new(0),
2143            #[cfg(any(test, feature = "test-support"))]
2144            callable_reference_spec_build_count: AtomicUsize::new(0),
2145            #[cfg(any(test, feature = "test-support"))]
2146            alias_source_parse_counts: Mutex::new(HashMap::default()),
2147            #[cfg(any(test, feature = "test-support"))]
2148            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2149            parser_alias_fallback_calls: AtomicUsize::new(0),
2150            parser_alias_fallback_files: AtomicUsize::new(0),
2151            parser_alias_source_parses: AtomicUsize::new(0),
2152            parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2153            field_type_facts: Mutex::new(HashMap::default()),
2154            structured_alias_targets: Mutex::new(HashMap::default()),
2155            callable_comparables: Mutex::new(HashMap::default()),
2156            comparable_name_declarations: Mutex::new(HashMap::default()),
2157            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2158            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2159            precise_parent_cache: Mutex::new(HashMap::default()),
2160            c_tag_kind_cache: Mutex::new(HashMap::default()),
2161            c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2162            macro_event_cells: Mutex::new(HashMap::default()),
2163            macro_event_name_sets: Mutex::new(HashMap::default()),
2164            macro_include_protection_cells: Mutex::new(HashMap::default()),
2165            macro_environment_checkpoints: Mutex::new(HashMap::default()),
2166            macro_replacements: Mutex::new(HashMap::default()),
2167            macro_local_binding_templates: Mutex::new(HashMap::default()),
2168            macro_lexical_templates: Mutex::new(HashMap::default()),
2169            macro_replacement_bodies: Mutex::new(HashMap::default()),
2170            macro_type_parameters: Mutex::new(HashMap::default()),
2171            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2172            #[cfg(any(test, feature = "test-support"))]
2173            macro_replacement_parse_count: AtomicUsize::new(0),
2174            #[cfg(any(test, feature = "test-support"))]
2175            macro_event_application_count: AtomicUsize::new(0),
2176            #[cfg(any(test, feature = "test-support"))]
2177            macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2178            #[cfg(any(test, feature = "test-support"))]
2179            macro_environment_copy_count: AtomicUsize::new(0),
2180            #[cfg(any(test, feature = "test-support"))]
2181            macro_environment_request_count: AtomicUsize::new(0),
2182            cpp_template_metadata,
2183            cpp_template_families,
2184            #[cfg(any(test, feature = "test-support"))]
2185            qualified_candidate_inspections: AtomicUsize::new(0),
2186            #[cfg(any(test, feature = "test-support"))]
2187            target_preserving_type_resolution_count: AtomicUsize::new(0),
2188            #[cfg(any(test, feature = "test-support"))]
2189            visibility_identifier_lookup_count: visibility_stats.identifier_lookups,
2190            #[cfg(any(test, feature = "test-support"))]
2191            visibility_identifier_batch_count: visibility_stats.identifier_batches,
2192        }
2193    }
2194
2195    pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2196        if file == target.source() {
2197            return true;
2198        }
2199        if self.global_field_has_internal_linkage(target) {
2200            return self
2201                .visible_source_files_by_root
2202                .get(file)
2203                .is_some_and(|sources| sources.contains(target.source()));
2204        }
2205        self.visible_by_file
2206            .get(file)
2207            .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
2208    }
2209
2210    fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
2211        self.global_field_internal_linkage
2212            .get(unit)
2213            .copied()
2214            .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
2215    }
2216
2217    pub fn call_arity_evidence(
2218        &self,
2219        file: &ProjectFile,
2220        call: Node<'_>,
2221        source: &str,
2222    ) -> CallArityEvidence {
2223        self.call_arity_evidence_at(file, call, source, call.start_byte())
2224    }
2225
2226    /// Argument-count evidence for a call whose macro environment is not the
2227    /// one at its own byte offset.
2228    ///
2229    /// A call recovered from a macro replacement lives in a sentinel parse of
2230    /// its own, so its node offsets say nothing about which macros are active.
2231    /// `environment_byte` names the position in `file` whose macro environment
2232    /// governs the call: the macro definition site for a replacement body.
2233    pub fn call_arity_evidence_at(
2234        &self,
2235        file: &ProjectFile,
2236        call: Node<'_>,
2237        source: &str,
2238        environment_byte: usize,
2239    ) -> CallArityEvidence {
2240        let Some(arguments) = call
2241            .child_by_field_name("arguments")
2242            .or_else(|| call.child_by_field_name("parameters"))
2243            .or_else(|| call.child_by_field_name("value"))
2244            .or_else(|| first_named_child_of_kind(call, "argument_list"))
2245            .or_else(|| first_named_child_of_kind(call, "initializer_list"))
2246        else {
2247            return CallArityEvidence::Exact(0);
2248        };
2249        let recovered_c_keyword_arguments =
2250            recovered_c_keyword_argument_count(file, call, arguments, source);
2251        let c_semantics = reference_uses_c_semantics(self.cpp, file);
2252        let arguments = argument_children(arguments)
2253            .flat_map(|argument| {
2254                recovered_c_new_expression_arguments(argument, c_semantics)
2255                    .map(Vec::from)
2256                    .unwrap_or_else(|| vec![argument])
2257            })
2258            .collect::<Vec<_>>();
2259        if arguments
2260            .iter()
2261            .all(|argument| !argument_shape_may_change_arity(*argument))
2262        {
2263            return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
2264        }
2265        let environment = self.macro_environment(file, environment_byte);
2266        let mut stack = Vec::new();
2267        let mut total = recovered_c_keyword_arguments;
2268        for argument in arguments {
2269            if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
2270                return CallArityEvidence::Unknown;
2271            }
2272            let CallArityEvidence::Exact(spread) =
2273                self.argument_arity_evidence(argument, source, &environment, &mut stack)
2274            else {
2275                return CallArityEvidence::Unknown;
2276            };
2277            total += spread;
2278        }
2279        CallArityEvidence::Exact(total)
2280    }
2281
2282    fn argument_arity_evidence(
2283        &self,
2284        argument: Node<'_>,
2285        source: &str,
2286        environment: &MacroEnvironment,
2287        stack: &mut Vec<(ProjectFile, usize)>,
2288    ) -> CallArityEvidence {
2289        let (name, invocation_arguments, function_like) = match argument.kind() {
2290            "identifier" => (node_text(argument, source), None, false),
2291            "call_expression" => {
2292                let Some(function) = argument.child_by_field_name("function") else {
2293                    return CallArityEvidence::Exact(1);
2294                };
2295                if function.kind() != "identifier" {
2296                    return CallArityEvidence::Exact(1);
2297                }
2298                let Some(arguments) = argument.child_by_field_name("arguments") else {
2299                    return CallArityEvidence::Exact(1);
2300                };
2301                (node_text(function, source), Some(arguments), true)
2302            }
2303            _ => return CallArityEvidence::Exact(1),
2304        };
2305        let Some(binding) = environment.binding(name) else {
2306            return if environment.unknown_names {
2307                CallArityEvidence::Unknown
2308            } else {
2309                CallArityEvidence::Exact(1)
2310            };
2311        };
2312        if !binding.is_exact() {
2313            return CallArityEvidence::Unknown;
2314        }
2315        match (&binding.definition, invocation_arguments, function_like) {
2316            (MacroDefinition::Object { replacement }, None, false) => self
2317                .replacement_arity_evidence(
2318                    replacement,
2319                    &[],
2320                    &[],
2321                    source,
2322                    environment,
2323                    stack,
2324                    binding,
2325                ),
2326            (
2327                MacroDefinition::Function {
2328                    parameters,
2329                    replacement,
2330                },
2331                Some(arguments),
2332                true,
2333            ) => {
2334                let actuals = argument_children(arguments).collect::<Vec<_>>();
2335                if actuals.len() != parameters.len() {
2336                    CallArityEvidence::Unknown
2337                } else {
2338                    self.replacement_arity_evidence(
2339                        replacement,
2340                        parameters,
2341                        &actuals,
2342                        source,
2343                        environment,
2344                        stack,
2345                        binding,
2346                    )
2347                }
2348            }
2349            (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
2350            _ => CallArityEvidence::Unknown,
2351        }
2352    }
2353
2354    #[allow(clippy::too_many_arguments)]
2355    fn replacement_arity_evidence(
2356        &self,
2357        replacement: &str,
2358        parameters: &[String],
2359        actuals: &[Node<'_>],
2360        actual_source: &str,
2361        environment: &MacroEnvironment,
2362        stack: &mut Vec<(ProjectFile, usize)>,
2363        binding: &MacroBinding,
2364    ) -> CallArityEvidence {
2365        let identity = (binding.source.clone(), binding.declaration_byte);
2366        if stack.contains(&identity) || replacement.trim().is_empty() {
2367            return CallArityEvidence::Unknown;
2368        }
2369        stack.push(identity);
2370        let parsed = self.parsed_macro_replacement(binding, replacement);
2371        let evidence = (|| {
2372            let ParsedMacroReplacement::Parsed {
2373                source: sentinel,
2374                tree,
2375            } = parsed.as_ref()
2376            else {
2377                return None;
2378            };
2379            let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
2380            let arguments = call.child_by_field_name("arguments")?;
2381            let mut total = 0usize;
2382            for argument in argument_children(arguments) {
2383                if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
2384                    return None;
2385                }
2386                if argument.kind() == "identifier"
2387                    && let Some(parameter_index) = parameters
2388                        .iter()
2389                        .position(|parameter| parameter == node_text(argument, sentinel))
2390                {
2391                    if !macro_expansion_shape_is_safe(
2392                        actuals[parameter_index],
2393                        actual_source,
2394                        &[],
2395                        environment,
2396                    ) {
2397                        return None;
2398                    }
2399                    let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
2400                        actuals[parameter_index],
2401                        actual_source,
2402                        environment,
2403                        stack,
2404                    ) else {
2405                        return None;
2406                    };
2407                    total += spread;
2408                    continue;
2409                }
2410                let CallArityEvidence::Exact(spread) =
2411                    self.argument_arity_evidence(argument, sentinel, environment, stack)
2412                else {
2413                    return None;
2414                };
2415                total += spread;
2416            }
2417            Some(CallArityEvidence::Exact(total))
2418        })()
2419        .unwrap_or(CallArityEvidence::Unknown);
2420        stack.pop();
2421        evidence
2422    }
2423
2424    fn parsed_macro_replacement(
2425        &self,
2426        binding: &MacroBinding,
2427        replacement: &str,
2428    ) -> Arc<ParsedMacroReplacement> {
2429        let key = (binding.source.clone(), binding.declaration_byte);
2430        let mut cache = self
2431            .macro_replacements
2432            .lock()
2433            .expect("C++ macro replacement cache poisoned");
2434        if let Some(parsed) = cache.get(&key) {
2435            return Arc::clone(parsed);
2436        }
2437        #[cfg(any(test, feature = "test-support"))]
2438        self.macro_replacement_parse_count
2439            .fetch_add(1, Ordering::Relaxed);
2440        let source =
2441            format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
2442        let mut parser = Parser::new();
2443        let parsed = parser
2444            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2445            .ok()
2446            .and_then(|()| parser.parse(&source, None))
2447            .filter(|tree| !tree.root_node().has_error())
2448            .map_or(ParsedMacroReplacement::Unsupported, |tree| {
2449                ParsedMacroReplacement::Parsed { source, tree }
2450            });
2451        let parsed = Arc::new(parsed);
2452        cache.insert(key, Arc::clone(&parsed));
2453        parsed
2454    }
2455
2456    /// Recover a typed local declared by an active C function-like macro.
2457    ///
2458    /// This is intentionally narrower than macro expansion. The replacement
2459    /// must parse as one declaration, and the invocation must bind every
2460    /// formal parameter to one structured argument. That is sufficient for
2461    /// declaration macros such as `THIS(StorageAzure)`. An unavailable include
2462    /// can make the binding provisional without erasing its last known
2463    /// definition; an explicit conflicting definition still replaces it with
2464    /// Unsupported. Malformed and statement-producing macros also fail closed.
2465    pub fn function_macro_local_binding<'tree>(
2466        &self,
2467        file: &ProjectFile,
2468        statement: Node<'tree>,
2469        source: &str,
2470    ) -> Option<MacroLocalBinding<'tree>> {
2471        if !is_c_source_file(file) {
2472            return None;
2473        }
2474        if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
2475            return Some(binding);
2476        }
2477        let call = match statement.kind() {
2478            "call_expression" => statement,
2479            "expression_statement" if statement.named_child_count() == 1 => {
2480                statement.named_child(0)?
2481            }
2482            _ => return None,
2483        };
2484        if call.kind() != "call_expression" {
2485            return None;
2486        }
2487        let function = call.child_by_field_name("function")?;
2488        if function.kind() != "identifier" {
2489            return None;
2490        }
2491        let arguments = call.child_by_field_name("arguments")?;
2492        let actuals = argument_children(arguments).collect::<Vec<_>>();
2493        let environment = self.macro_environment(file, call.start_byte());
2494        let function_name = node_text(function, source);
2495        let binding = environment.binding(function_name)?;
2496        let MacroDefinition::Function {
2497            parameters,
2498            replacement,
2499        } = &binding.definition
2500        else {
2501            return None;
2502        };
2503        if actuals.len() != parameters.len() {
2504            return None;
2505        }
2506        let template = self.macro_local_binding_template(binding, parameters, replacement)?;
2507        let (type_name, type_node) = match &template.declared_type {
2508            MacroLocalBindingTypeTemplate::Parameter(index) => {
2509                let actual = *actuals.get(*index)?;
2510                if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
2511                    return None;
2512                }
2513                (node_text(actual, source).trim().to_string(), Some(actual))
2514            }
2515            MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
2516        };
2517        if type_name.is_empty() {
2518            return None;
2519        }
2520        Some(MacroLocalBinding {
2521            name: template.name.clone(),
2522            type_name,
2523            type_node,
2524            pointer_depth: template.pointer_depth,
2525            proven_unit: None,
2526        })
2527    }
2528
2529    /// Recover the typed receiver established by a C container macro
2530    /// assignment, such as `value = container_of(ptr, struct item, link)`.
2531    ///
2532    /// The macro definition's parsed replacement identifies the one formal
2533    /// parameter used in type position.  The invocation supplies the actual
2534    /// type node, which is then resolved through the ordinary visibility
2535    /// index.  Calls with no unique type-position parameter, an unresolved
2536    /// type, or an uncertain macro environment remain unproven.
2537    pub fn function_macro_container_binding<'tree>(
2538        &self,
2539        analyzer: &CppGraphSource<'_>,
2540        file: &ProjectFile,
2541        assignment: Node<'tree>,
2542        source: &str,
2543    ) -> Option<MacroLocalBinding<'tree>> {
2544        if !is_c_source_file(file) || assignment.kind() != "assignment_expression" {
2545            return None;
2546        }
2547        let name_node = assignment.child_by_field_name("left")?;
2548        if name_node.kind() != "identifier" {
2549            return None;
2550        }
2551        let call = assignment.child_by_field_name("right")?;
2552        if call.kind() != "call_expression" {
2553            return None;
2554        }
2555        let function = call.child_by_field_name("function")?;
2556        if function.kind() != "identifier" {
2557            return None;
2558        }
2559        let arguments = call.child_by_field_name("arguments")?;
2560        let actuals = argument_children(arguments).collect::<Vec<_>>();
2561        let function_name = node_text(function, source);
2562        let environment = self.macro_environment(file, call.start_byte());
2563        let binding = environment.binding(function_name)?;
2564        if !binding.is_exact() {
2565            return None;
2566        }
2567        let MacroDefinition::Function {
2568            parameters,
2569            replacement,
2570        } = &binding.definition
2571        else {
2572            return None;
2573        };
2574        if actuals.len() != parameters.len() {
2575            return None;
2576        }
2577        let body = self.parsed_macro_replacement_body(
2578            &(binding.source.clone(), binding.declaration_byte),
2579            parameters,
2580            replacement,
2581        )?;
2582        let type_parameter = macro_replacement_type_parameter(&body, parameters)?;
2583        let type_argument = *actuals.get(type_parameter)?;
2584        let type_node = macro_type_argument_node(type_argument, source)?;
2585        let type_name = node_text(type_node, source).trim().to_string();
2586        if type_name.is_empty() {
2587            return None;
2588        }
2589        let explicit_tag = match node_text(type_argument, source) {
2590            "struct" => Some(CppCTagKind::Struct),
2591            "union" => Some(CppCTagKind::Union),
2592            _ => None,
2593        };
2594        let resolved_type = if let Some(tag) = explicit_tag {
2595            let candidates = self
2596                .visible_identifier_candidates(file, &type_name)
2597                .filter(|candidate| self.cached_c_tag_kind(analyzer, candidate) == Some(tag))
2598                .collect::<Vec<_>>();
2599            self.resolve_type_candidates(
2600                analyzer,
2601                file,
2602                &candidates,
2603                TypeCandidateResolution::Canonical,
2604            )
2605            .ok()
2606        } else {
2607            self.resolve_type_node_result(file, type_node, source)
2608                .ok()
2609                .flatten()
2610        };
2611        let proven_unit = resolved_type?;
2612        Some(MacroLocalBinding {
2613            name: node_text(name_node, source).to_string(),
2614            type_name,
2615            type_node: Some(type_node),
2616            pointer_depth: 1,
2617            proven_unit: Some(proven_unit),
2618        })
2619    }
2620
2621    /// Recover the invocation-specific type of a replacement local visible
2622    /// at the selected source token.
2623    pub fn macro_local_binding_at<'tree>(
2624        &self,
2625        file: &ProjectFile,
2626        root: Node<'tree>,
2627        source: &str,
2628        start_byte: usize,
2629        end_byte: usize,
2630    ) -> Option<MacroLocalBinding<'tree>> {
2631        crate::graph::macro_lexical::typed_binding(self, file, root, source, start_byte, end_byte)
2632    }
2633
2634    /// Resolve the source-backed lexical macro declaration for one source
2635    /// range. The range may be a point range (`start..start + 1`) inside an
2636    /// identifier, which is the form used by goto-definition queries.
2637    pub fn macro_lexical_binding(
2638        &self,
2639        file: &ProjectFile,
2640        root: Node<'_>,
2641        source: &str,
2642        start_byte: usize,
2643        end_byte: usize,
2644    ) -> Option<MacroLexicalBinding> {
2645        crate::graph::macro_lexical::binding(self, file, root, source, start_byte, end_byte)
2646    }
2647
2648    /// Enumerate source ranges whose lexical meaning comes from a
2649    /// function-like macro formal or replacement local. Definition spellings
2650    /// themselves are omitted; callers needing goto-definition on a spelling
2651    /// use [`Self::macro_lexical_binding`].
2652    pub fn macro_lexical_references(
2653        &self,
2654        file: &ProjectFile,
2655        root: Node<'_>,
2656        source: &str,
2657        max_references: usize,
2658        cancelled: impl FnMut() -> bool,
2659    ) -> MacroLexicalReferences {
2660        crate::graph::macro_lexical::all_references(
2661            || self,
2662            file,
2663            root,
2664            source,
2665            max_references,
2666            cancelled,
2667        )
2668    }
2669
2670    /// Return the source identity of the exact active function-like macro
2671    /// binding. The definition AST is hydrated by the lexical helper from
2672    /// this `(ProjectFile, declaration byte)` pair, so no parser node escapes
2673    /// the prepared source borrow.
2674    pub(crate) fn function_macro_binding_at(
2675        &self,
2676        file: &ProjectFile,
2677        name: &str,
2678        before_byte: usize,
2679    ) -> Option<(ProjectFile, usize)> {
2680        let environment = self.macro_environment(file, before_byte);
2681        let binding = environment.binding(name)?;
2682        if binding.is_exact()
2683            && matches!(
2684                binding.definition,
2685                MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2686            )
2687        {
2688            return Some((binding.source.clone(), binding.declaration_byte));
2689        }
2690        // A definition under an unknown condition is still certain at a use
2691        // within that same branch. The checkpoint retains the event that made
2692        // the binding uncertain. Require that exact defining event, so an
2693        // intervening undef, include, or conditional redefinition cannot be
2694        // bypassed by a lexical search for an older macro.
2695        if binding.source != *file {
2696            return None;
2697        }
2698        let prepared = self.cpp.prepared_syntax(self.token, file)?;
2699        let root = prepared.tree().root_node();
2700        let source = prepared.source();
2701        let reference = root.descendant_for_byte_range(
2702            before_byte,
2703            before_byte.saturating_add(1).min(source.len()),
2704        )?;
2705        let reference_conditions = owning_preprocessor_conditionals(root, reference, source);
2706        let cell = self.macro_event_cell(file);
2707        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2708        let event = events
2709            .iter()
2710            .find(|event| event.byte() == binding.declaration_byte)?;
2711        let MacroEvent::Define {
2712            name: defined_name,
2713            binding: definition,
2714            conditionals,
2715            ..
2716        } = event
2717        else {
2718            return None;
2719        };
2720        if defined_name != name
2721            || conditionals.is_empty()
2722            || !conditionals
2723                .iter()
2724                .all(|condition| reference_conditions.contains(condition))
2725            || !matches!(
2726                definition.definition,
2727                MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2728            )
2729        {
2730            return None;
2731        }
2732        Some((definition.source.clone(), definition.declaration_byte))
2733    }
2734
2735    /// Return the type argument at a C function-like macro invocation.
2736    ///
2737    /// Exact local macro definitions identify type formals from their parsed
2738    /// replacement. An unavailable definition is deliberately not inferred:
2739    /// C has no AST distinction between an external macro call and an
2740    /// ordinary call to an unindexed function.
2741    pub fn function_macro_type_argument<'tree>(
2742        &self,
2743        file: &ProjectFile,
2744        node: Node<'tree>,
2745        source: &str,
2746    ) -> Option<Node<'tree>> {
2747        if !is_c_source_file(file) {
2748            return None;
2749        }
2750        let (call, argument_index, argument) = c_function_macro_argument(node)?;
2751        let function = call.child_by_field_name("function")?;
2752        let function_name = node_text(function, source);
2753        if function_name.is_empty() {
2754            return None;
2755        }
2756        if !self.file_defines_macro_name(file, function_name)
2757            && !self
2758                .visible_identifier_candidates(file, function_name)
2759                .any(|candidate| candidate.is_macro())
2760        {
2761            return None;
2762        }
2763        let environment = self.macro_environment(file, call.start_byte());
2764        let binding = environment.binding(function_name)?;
2765        if !binding.is_exact() {
2766            return None;
2767        }
2768        let (parameters, replacement, variadic) = match &binding.definition {
2769            MacroDefinition::Function {
2770                parameters,
2771                replacement,
2772            } => (parameters, replacement, false),
2773            MacroDefinition::VariadicFunction {
2774                parameters,
2775                replacement,
2776            } => (parameters, replacement, true),
2777            MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
2778        };
2779        let actuals = call
2780            .child_by_field_name("arguments")
2781            .map(argument_children)
2782            .into_iter()
2783            .flatten()
2784            .collect::<Vec<_>>();
2785        let arity_matches = if variadic {
2786            actuals.len() >= parameters.len()
2787        } else {
2788            actuals.len() == parameters.len()
2789        };
2790        let type_parameters = self.macro_type_parameter_indices(
2791            &(binding.source.clone(), binding.declaration_byte),
2792            parameters,
2793            replacement,
2794        )?;
2795        if !arity_matches || !type_parameters.contains(&argument_index) {
2796            return None;
2797        }
2798        let type_node = macro_type_argument_node(argument, source)?;
2799        if self.names_a_macro_at(file, node_text(type_node, source), type_node.start_byte()) {
2800            return None;
2801        }
2802        Some(type_node)
2803    }
2804
2805    fn macro_local_binding_template(
2806        &self,
2807        binding: &MacroBinding,
2808        parameters: &[String],
2809        replacement: &str,
2810    ) -> Option<Arc<MacroLocalBindingTemplate>> {
2811        let key = (binding.source.clone(), binding.declaration_byte);
2812        if let Some(template) = self
2813            .macro_local_binding_templates
2814            .lock()
2815            .expect("C++ macro local-binding cache poisoned")
2816            .get(&key)
2817        {
2818            return template.clone();
2819        }
2820        let template = (|| {
2821            let body = self.parsed_macro_replacement_body(&key, parameters, replacement)?;
2822            let sentinel = body.source.as_str();
2823            let statements = body.statements()?;
2824            if statements.named_child_count() != 1 {
2825                return None;
2826            }
2827            let declaration = statements.named_child(0)?;
2828            if declaration.kind() != "declaration" {
2829                return None;
2830            }
2831            let type_node = declaration
2832                .child_by_field_name("type")
2833                .or_else(|| first_type_child(declaration))?;
2834            let declarator = declaration.child_by_field_name("declarator").or_else(|| {
2835                let mut cursor = declaration.walk();
2836                declaration.named_children(&mut cursor).find_map(|child| {
2837                    if child.kind() == "init_declarator" {
2838                        child.child_by_field_name("declarator")
2839                    } else {
2840                        is_declarator_node(child).then_some(child)
2841                    }
2842                })
2843            })?;
2844            let name = extract_variable_name(declarator, sentinel)?;
2845            let pointer_depth =
2846                declared_name_binding(declaration, type_node, &name, sentinel)?.pointer_depth;
2847            let type_text = node_text(type_node, sentinel).trim();
2848            let declared_type = parameters
2849                .iter()
2850                .position(|parameter| parameter == type_text)
2851                .map(MacroLocalBindingTypeTemplate::Parameter)
2852                .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
2853            Some(Arc::new(MacroLocalBindingTemplate {
2854                name,
2855                declared_type,
2856                pointer_depth,
2857            }))
2858        })();
2859        self.macro_local_binding_templates
2860            .lock()
2861            .expect("C++ macro local-binding cache poisoned")
2862            .insert(key, template.clone());
2863        template
2864    }
2865
2866    /// The parsed replacement body of the function-like macro `definition`
2867    /// defines, or `None` when the replacement cannot be recovered exactly.
2868    ///
2869    /// `definition` is the defining `preproc_function_def` node in `file`, so
2870    /// the result describes that definition rather than whichever same-named
2871    /// macro a later reference resolves to.
2872    pub fn function_macro_replacement_body(
2873        &self,
2874        file: &ProjectFile,
2875        definition: Node<'_>,
2876        source: &str,
2877    ) -> Option<Arc<ParsedReplacementBody>> {
2878        debug_assert_eq!(definition.kind(), "preproc_function_def");
2879        let (parameters, replacement) = match Self::decode_macro_definition(definition, source) {
2880            MacroDefinition::Function {
2881                parameters,
2882                replacement,
2883            }
2884            | MacroDefinition::VariadicFunction {
2885                parameters,
2886                replacement,
2887            } => (parameters, replacement),
2888            MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
2889        };
2890        self.parsed_macro_replacement_body(
2891            &(file.clone(), definition.start_byte()),
2892            &parameters,
2893            &replacement,
2894        )
2895    }
2896
2897    /// Parse one function-like macro replacement inside the shared sentinel.
2898    ///
2899    /// The parse fails closed, and the failure is cached, whenever the
2900    /// sentinel tree carries an error or the replacement uses preprocessor
2901    /// syntax that has no C++ meaning. Token pasting and stringizing produce
2902    /// `ERROR` nodes; `__VA_ARGS__` parses as an ordinary identifier and is
2903    /// therefore rejected from the parsed tree instead of the source text.
2904    fn parsed_macro_replacement_body(
2905        &self,
2906        key: &(ProjectFile, usize),
2907        parameters: &[String],
2908        replacement: &str,
2909    ) -> Option<Arc<ParsedReplacementBody>> {
2910        if let Some(body) = self
2911            .macro_replacement_bodies
2912            .lock()
2913            .expect("C++ macro replacement body cache poisoned")
2914            .get(key)
2915        {
2916            return body.clone();
2917        }
2918        let body = (|| {
2919            if replacement.trim().is_empty() {
2920                return None;
2921            }
2922            let (source, tree, original_offsets) =
2923                Self::parse_macro_replacement_body(replacement, parameters)?;
2924            let body = ParsedReplacementBody {
2925                source,
2926                tree,
2927                body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
2928                parameters: parameters.to_vec(),
2929                original_offsets,
2930            };
2931            body.statements()?;
2932            if body.expands_variadic_arguments() {
2933                return None;
2934            }
2935            Some(Arc::new(body))
2936        })();
2937        self.macro_replacement_bodies
2938            .lock()
2939            .expect("C++ macro replacement body cache poisoned")
2940            .insert(key.clone(), body.clone());
2941        body
2942    }
2943
2944    /// Parse a replacement while preserving a map from the parse buffer back
2945    /// to the original logical-line bytes. A block formal can occur where C
2946    /// requires a statement terminator (`if (condition) block`); the caller's
2947    /// actual block supplies that terminator only after macro substitution.
2948    /// Tree-sitter exposes the formal as an identifier inside recovery, so a
2949    /// semicolon is inserted at that AST-derived boundary for the fallback
2950    /// parse. No identifier text is scanned to find the insertion point.
2951    fn parse_macro_replacement_body(
2952        replacement: &str,
2953        parameters: &[String],
2954    ) -> Option<(String, Tree, Box<[usize]>)> {
2955        let normalized = normalize_macro_continuations(replacement);
2956        let parse = |replacement: &str| {
2957            let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
2958            let mut parser = Parser::new();
2959            parser
2960                .set_language(&tree_sitter_cpp::LANGUAGE.into())
2961                .ok()?;
2962            let tree = parser.parse(&source, None)?;
2963            Some((source, tree))
2964        };
2965        let (source, tree) = parse(&normalized)?;
2966        if !tree.root_node().has_error() {
2967            let mut original_offsets = (0..=normalized.len()).collect::<Vec<_>>();
2968            Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
2969            return Some((source, tree, original_offsets.into_boxed_slice()));
2970        }
2971
2972        let body_offset = MACRO_BODY_SENTINEL_PREFIX.len();
2973        let mut insertion_points = Vec::new();
2974        let mut stack = vec![tree.root_node()];
2975        while let Some(node) = stack.pop() {
2976            if matches!(
2977                node.kind(),
2978                "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
2979            ) && parameters
2980                .iter()
2981                .any(|parameter| parameter.as_str() == node_text(node, &source))
2982                && Self::macro_formal_needs_statement_separator(node)
2983            {
2984                let point = node.end_byte().saturating_sub(body_offset);
2985                if point <= normalized.len() && !insertion_points.contains(&point) {
2986                    insertion_points.push(point);
2987                }
2988            }
2989            push_named_children_reversed(node, &mut stack);
2990        }
2991        if insertion_points.is_empty() {
2992            return None;
2993        }
2994        insertion_points.sort_unstable();
2995        let mut recovered = Vec::with_capacity(normalized.len() + insertion_points.len());
2996        let mut original_offsets =
2997            Vec::with_capacity(normalized.len() + insertion_points.len() + 1);
2998        let mut next_insertion = 0;
2999        for (index, byte) in normalized.bytes().enumerate() {
3000            recovered.push(byte);
3001            original_offsets.push(index);
3002            while insertion_points.get(next_insertion).copied() == Some(index + 1) {
3003                recovered.push(b';');
3004                original_offsets.push(index + 1);
3005                next_insertion += 1;
3006            }
3007        }
3008        original_offsets.push(normalized.len());
3009        Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
3010        let recovered = String::from_utf8(recovered).expect("source text remains UTF-8");
3011        let (source, tree) = parse(&recovered)?;
3012        if tree.root_node().has_error() {
3013            return None;
3014        }
3015        Some((source, tree, original_offsets.into_boxed_slice()))
3016    }
3017
3018    /// Extend a replacement-origin map over the `; }` suffix appended by the
3019    /// sentinel parser. Nodes such as a declaration whose source omits its
3020    /// terminator include that synthetic semicolon; every suffix boundary
3021    /// still maps to the replacement's real end.
3022    fn append_sentinel_offsets(offsets: &mut Vec<usize>, replacement_end: usize) {
3023        offsets.extend([replacement_end; 3]);
3024    }
3025
3026    /// A formal is statement-shaped when the recovered tree places it directly
3027    /// at a statement boundary. This is the grammar recovery produced for
3028    /// replacement forms such as `if (condition) block`; an identifier inside
3029    /// an expression or call argument must not receive an artificial `;`.
3030    fn macro_formal_needs_statement_separator(node: Node<'_>) -> bool {
3031        let mut current = node;
3032        let mut crossed_recovery = false;
3033        while let Some(parent) = current.parent() {
3034            if parent.is_error() {
3035                crossed_recovery = true;
3036                current = parent;
3037                continue;
3038            }
3039            if matches!(
3040                parent.kind(),
3041                "call_expression"
3042                    | "argument_list"
3043                    | "field_expression"
3044                    | "binary_expression"
3045                    | "unary_expression"
3046                    | "assignment_expression"
3047                    | "conditional_expression"
3048                    | "parenthesized_expression"
3049                    | "subscript_expression"
3050            ) {
3051                return false;
3052            }
3053            if parent.kind() == "expression_statement" {
3054                return parent.named_child_count() == 1;
3055            }
3056            if parent.kind() == "compound_statement" && current == node {
3057                return true;
3058            }
3059            if matches!(
3060                parent.kind(),
3061                "compound_statement" | "if_statement" | "while_statement" | "do_statement"
3062            ) {
3063                return crossed_recovery
3064                    || parent.child_by_field_name("consequence") == Some(current);
3065            }
3066            if matches!(parent.kind(), "declaration" | "init_declarator") {
3067                return false;
3068            }
3069            current = parent;
3070        }
3071        false
3072    }
3073
3074    /// Identify fixed macro formals that retain an unambiguous type role even
3075    /// when an unrelated part of the replacement needs parser recovery.
3076    ///
3077    /// Variadic production macros commonly contain token pasting elsewhere in
3078    /// the body. The strict shared replacement parser must keep rejecting that
3079    /// body for expansion modeling, but a type declaration such as `TY col`
3080    /// remains usable when the node itself retains a structured type role.
3081    fn macro_type_parameter_indices(
3082        &self,
3083        key: &(ProjectFile, usize),
3084        parameters: &[String],
3085        replacement: &str,
3086    ) -> Option<Arc<[usize]>> {
3087        if let Some(indices) = self
3088            .macro_type_parameters
3089            .lock()
3090            .expect("C++ macro type-parameter cache poisoned")
3091            .get(key)
3092        {
3093            return indices.clone();
3094        }
3095        #[cfg(any(test, feature = "test-support"))]
3096        self.macro_replacement_parse_count
3097            .fetch_add(1, Ordering::Relaxed);
3098        let indices = (|| {
3099            if replacement.trim().is_empty() {
3100                return None;
3101            }
3102            // Keep this parser permissive. Type-role extraction predates the
3103            // replacement-body model and deliberately survives unrelated
3104            // token-pasting or recovery elsewhere in a production macro.
3105            let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
3106            let mut parser = Parser::new();
3107            parser
3108                .set_language(&tree_sitter_cpp::LANGUAGE.into())
3109                .ok()?;
3110            let tree = parser.parse(&source, None)?;
3111            let mut original_offsets = (0..=replacement.len()).collect::<Vec<_>>();
3112            Self::append_sentinel_offsets(&mut original_offsets, replacement.len());
3113            let body = ParsedReplacementBody {
3114                source,
3115                tree,
3116                body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3117                parameters: parameters.to_vec(),
3118                original_offsets: original_offsets.into_boxed_slice(),
3119            };
3120            macro_replacement_type_parameters(&body, parameters).map(Arc::from)
3121        })();
3122        self.macro_type_parameters
3123            .lock()
3124            .expect("C++ macro type-parameter cache poisoned")
3125            .insert(key.clone(), indices.clone());
3126        indices
3127    }
3128
3129    fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
3130        let replacement = if node.kind() == "preproc_function_def" {
3131            function_macro_replacement_span(node, source)
3132                .and_then(|span| source.get(span))
3133                .map(str::to_owned)
3134                .or_else(|| {
3135                    node.child_by_field_name("value")
3136                        .map(|value| node_text(value, source).to_string())
3137                })
3138                .unwrap_or_default()
3139        } else {
3140            node.child_by_field_name("value")
3141                .map(|value| node_text(value, source).to_string())
3142                .unwrap_or_default()
3143        };
3144        if node.kind() == "preproc_def" {
3145            return MacroDefinition::Object { replacement };
3146        }
3147        let Some(parameters) = node.child_by_field_name("parameters") else {
3148            return MacroDefinition::Unsupported;
3149        };
3150        let variadic = (0..parameters.child_count()).any(|index| {
3151            parameters
3152                .child(index)
3153                .is_some_and(|child| child.kind() == "...")
3154        });
3155        let parameters = (0..parameters.named_child_count())
3156            .filter_map(|index| parameters.named_child(index))
3157            .map(|parameter| node_text(parameter, source).to_string())
3158            .collect::<Vec<_>>();
3159        if variadic {
3160            MacroDefinition::VariadicFunction {
3161                parameters,
3162                replacement,
3163            }
3164        } else {
3165            MacroDefinition::Function {
3166                parameters,
3167                replacement,
3168            }
3169        }
3170    }
3171
3172    pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
3173        self.macro_event_cells
3174            .lock()
3175            .expect("C++ macro event cache poisoned")
3176            .entry(file.clone())
3177            .or_default()
3178            .clone()
3179    }
3180
3181    fn file_defines_macro_name(&self, file: &ProjectFile, name: &str) -> bool {
3182        if let Some(names) = self
3183            .macro_event_name_sets
3184            .lock()
3185            .expect("C++ macro event-name cache poisoned")
3186            .get(file)
3187            .cloned()
3188        {
3189            return names.contains(name);
3190        }
3191        let cell = self.macro_event_cell(file);
3192        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3193        let names = Arc::new(
3194            events
3195                .iter()
3196                .filter_map(|event| match event {
3197                    MacroEvent::Define { name, .. } => Some(name.clone()),
3198                    MacroEvent::Undef { .. }
3199                    | MacroEvent::Include { .. }
3200                    | MacroEvent::Invalidate { .. } => None,
3201                })
3202                .collect(),
3203        );
3204        self.macro_event_name_sets
3205            .lock()
3206            .expect("C++ macro event-name cache poisoned")
3207            .insert(file.clone(), Arc::clone(&names));
3208        names.contains(name)
3209    }
3210
3211    fn macro_environment_checkpoint_cell(
3212        &self,
3213        file: &ProjectFile,
3214    ) -> MacroEnvironmentCheckpointCell {
3215        self.macro_environment_checkpoints
3216            .lock()
3217            .expect("C++ macro environment checkpoint cache poisoned")
3218            .entry(file.clone())
3219            .or_default()
3220            .clone()
3221    }
3222
3223    /// The environment of `file`'s macro events applied up to `before_byte`.
3224    pub fn macro_environment(
3225        &self,
3226        file: &ProjectFile,
3227        before_byte: usize,
3228    ) -> Arc<MacroEnvironment> {
3229        #[cfg(any(test, feature = "test-support"))]
3230        self.macro_environment_request_count
3231            .fetch_add(1, Ordering::Relaxed);
3232        let cell = self.macro_event_cell(file);
3233        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3234        let frontier = events.partition_point(|event| event.byte() < before_byte);
3235        let checkpoint_cell = self.macro_environment_checkpoint_cell(file);
3236        let checkpoints =
3237            checkpoint_cell.get_or_init(|| self.build_macro_environment_checkpoints(file, events));
3238        let checkpoint = checkpoints.at_or_before(frontier);
3239        if checkpoint.frontier == frontier {
3240            return Arc::clone(&checkpoint.environment);
3241        }
3242        #[cfg(any(test, feature = "test-support"))]
3243        self.macro_environment_copy_count
3244            .fetch_add(1, Ordering::Relaxed);
3245        let mut environment = checkpoint.environment.as_ref().clone();
3246        let mut include_stack = HashSet::from_iter([file.clone()]);
3247        for event in &events[checkpoint.frontier..frontier] {
3248            self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3249        }
3250        Arc::new(environment)
3251    }
3252
3253    /// Apply `file`'s events once, keeping the environment at the prefixes
3254    /// [`MacroEnvironmentCheckpoints`] describes.
3255    fn build_macro_environment_checkpoints(
3256        &self,
3257        file: &ProjectFile,
3258        events: &[MacroEvent],
3259    ) -> MacroEnvironmentCheckpoints {
3260        #[cfg(any(test, feature = "test-support"))]
3261        self.macro_environment_checkpoint_build_count
3262            .fetch_add(1, Ordering::Relaxed);
3263        // The TU's build-proven defines hold from the first byte (#2011): they
3264        // are facts of the whole compile, so they seed the frontier-zero
3265        // checkpoint. A later explicit #undef event still overrides them
3266        // through `known_undefined_names`.
3267        let mut environment = MacroEnvironment {
3268            build_proven_defines: self
3269                .compile_proven_guards(file)
3270                .iter()
3271                .filter_map(|guard| match guard {
3272                    PreprocessorGuard::Defined(name) => Some(name.clone()),
3273                    _ => None,
3274                })
3275                .collect(),
3276            ..MacroEnvironment::default()
3277        };
3278        let mut checkpoints = vec![MacroEnvironmentCheckpoint {
3279            frontier: 0,
3280            environment: Arc::new(environment.clone()),
3281        }];
3282        // Keep roughly one fixed stride's worth of checkpoints, while taking
3283        // a checkpoint at every event for event sets no larger than the fixed
3284        // stride. Generated C tables commonly have thousands of uses between
3285        // one #define and a trailing #undef; with a fixed stride those
3286        // identical frontier requests would all copy and replay the same
3287        // prefix.
3288        let checkpoint_stride = events
3289            .len()
3290            .div_ceil(MACRO_ENVIRONMENT_CHECKPOINT_STRIDE)
3291            .clamp(1, MACRO_ENVIRONMENT_CHECKPOINT_STRIDE);
3292        let mut include_stack = HashSet::from_iter([file.clone()]);
3293        for (index, event) in events.iter().enumerate() {
3294            self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3295            let frontier = index + 1;
3296            if frontier % checkpoint_stride == 0 || matches!(event, MacroEvent::Include { .. }) {
3297                checkpoints.push(MacroEnvironmentCheckpoint {
3298                    frontier,
3299                    environment: Arc::new(environment.clone()),
3300                });
3301            }
3302        }
3303        MacroEnvironmentCheckpoints { checkpoints }
3304    }
3305
3306    /// Whether `name` is bound as a macro at `before_byte` in `file`,
3307    /// including a binding this environment cannot pin to one replacement
3308    /// (a conditional `#define`, or a function-like macro).
3309    ///
3310    /// [`Self::object_macro_replacement_at`] collapses every such binding to
3311    /// `None`, which is indistinguishable from "not a macro at all". A caller
3312    /// that must not read a macro token as an ordinary type name needs the two
3313    /// apart: an unexpandable macro is an unknown, a plain identifier is not.
3314    pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
3315        self.macro_environment(file, before_byte)
3316            .binding(name)
3317            .is_some()
3318    }
3319
3320    pub fn macro_name_may_be_bound_at(
3321        &self,
3322        file: &ProjectFile,
3323        name: &str,
3324        before_byte: usize,
3325    ) -> bool {
3326        self.macro_environment(file, before_byte).may_bind(name)
3327    }
3328
3329    /// Whether the active macro binding at this reference is the requested
3330    /// indexed definition. Name equality alone is not enough because two
3331    /// headers can define the same macro for different translation units.
3332    pub fn macro_binding_matches_target_at(
3333        &self,
3334        analyzer: &CppGraphSource<'_>,
3335        file: &ProjectFile,
3336        name: &str,
3337        before_byte: usize,
3338        target: &CodeUnit,
3339    ) -> bool {
3340        let ranges = analyzer.ranges(target);
3341        let declaration_bytes = self.macro_declaration_bytes(target, &ranges);
3342        self.macro_binding_matches_target_declaration_at(
3343            file,
3344            name,
3345            before_byte,
3346            target.source(),
3347            &declaration_bytes,
3348        )
3349    }
3350
3351    /// Resolve declaration ranges once for inverse scans that test many call
3352    /// sites against one macro target. Walking a large generated syntax tree
3353    /// back to the same `#define` for every call site is otherwise quadratic
3354    /// in the number of top-level declarations.
3355    pub(crate) fn macro_declaration_bytes(
3356        &self,
3357        target: &CodeUnit,
3358        ranges: &[Range],
3359    ) -> Vec<usize> {
3360        let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
3361            return Vec::new();
3362        };
3363        ranges
3364            .iter()
3365            .filter_map(|range| {
3366                let mut node = node_for_exact_range(prepared.tree().root_node(), range)?;
3367                while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
3368                    node = node.parent()?;
3369                }
3370                Some(node.start_byte())
3371            })
3372            .collect()
3373    }
3374
3375    pub(crate) fn macro_binding_matches_target_declaration_at(
3376        &self,
3377        file: &ProjectFile,
3378        name: &str,
3379        before_byte: usize,
3380        target_source: &ProjectFile,
3381        target_declaration_bytes: &[usize],
3382    ) -> bool {
3383        let environment = self.macro_environment(file, before_byte);
3384        let Some(binding) = environment.binding(name) else {
3385            return false;
3386        };
3387        if binding.definition == MacroDefinition::Unsupported {
3388            return false;
3389        }
3390        // A normal header guard makes the replacement text conditional, but
3391        // it does not erase the definition site's source and byte identity.
3392        // Keep that identity even when expansion details are not exact.
3393        if binding.source != *target_source {
3394            return false;
3395        }
3396        target_declaration_bytes.contains(&binding.declaration_byte)
3397    }
3398
3399    /// Resolve an ordinary expression-position macro token at its exact byte.
3400    ///
3401    /// Calls and preprocessor-condition tokens have separate resolution
3402    /// surfaces. Declaration names, macro parameters, and labels are not
3403    /// references. Keeping that role policy here makes forward and both
3404    /// inverse graph builders consume the same activation verdict (#2093).
3405    pub fn resolve_ordinary_macro_reference(
3406        &self,
3407        analyzer: &CppGraphSource<'_>,
3408        file: &ProjectFile,
3409        node: Node<'_>,
3410        source: &str,
3411    ) -> OrdinaryMacroReferenceResolution {
3412        if !is_ordinary_macro_reference_node(node) {
3413            return OrdinaryMacroReferenceResolution::Missing;
3414        }
3415        let name = node_text(node, source);
3416        if name.is_empty() {
3417            return OrdinaryMacroReferenceResolution::Missing;
3418        }
3419        let visible = self
3420            .visible_identifier_candidates(file, name)
3421            .filter(|candidate| candidate.is_macro())
3422            .cloned()
3423            .collect::<Vec<_>>();
3424        let mut exact = Vec::new();
3425        for candidate in &visible {
3426            if self.macro_binding_matches_target_at(
3427                analyzer,
3428                file,
3429                name,
3430                node.start_byte(),
3431                candidate,
3432            ) && !exact
3433                .iter()
3434                .any(|existing| same_visible_symbol(existing, candidate))
3435            {
3436                exact.push(candidate.clone());
3437            }
3438        }
3439        match exact.len() {
3440            1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
3441            2.. => OrdinaryMacroReferenceResolution::Ambiguous,
3442            0 if !visible.is_empty()
3443                && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
3444            {
3445                OrdinaryMacroReferenceResolution::Ambiguous
3446            }
3447            0 => OrdinaryMacroReferenceResolution::Missing,
3448        }
3449    }
3450
3451    /// Collect reference-capable C tokens beneath tree-sitter recovery nodes.
3452    ///
3453    /// The ordinary census deliberately skips every `ERROR` subtree. This
3454    /// separate, precision-only frontier admits only roles that retain enough
3455    /// structure for the C usage graph to interpret independently (#2089).
3456    /// Macro evidence comes from this visibility index at the exact byte; no
3457    /// source-text parsing or terminal-name fallback is used.
3458    pub fn recovered_c_reference_ranges(
3459        &self,
3460        file: &ProjectFile,
3461        root: Node<'_>,
3462        source: &str,
3463        limit: usize,
3464    ) -> RecoveredCReferenceRanges {
3465        if !is_c_source_file(file) {
3466            return RecoveredCReferenceRanges::Complete(Vec::new());
3467        }
3468        let mut ranges = Vec::new();
3469        let mut seen = HashSet::default();
3470        let mut stack = vec![(root, root.is_error())];
3471        while let Some((node, inside_error)) = stack.pop() {
3472            let inside_error = inside_error || node.is_error();
3473            if node.kind() == "preproc_arg" {
3474                // Tree-sitter keeps an object-like replacement opaque. The
3475                // inverse extractor uses the same parsed replacement helper;
3476                // retain its exact macro/type leaves for precision membership
3477                // even when the preprocessor node is outside ERROR recovery.
3478                let macro_value_kind = node.parent().and_then(|parent| {
3479                    (parent.child_by_field_name("value") == Some(node)).then_some(parent.kind())
3480                });
3481                if matches!(
3482                    macro_value_kind,
3483                    Some("preproc_def" | "preproc_function_def")
3484                ) {
3485                    let name = node_text(node, source);
3486                    if !name.is_empty()
3487                        && self.macro_name_may_be_bound_at(file, name, node.start_byte())
3488                        && !push_recovered_c_range(
3489                            &mut ranges,
3490                            &mut seen,
3491                            node.start_byte(),
3492                            node.end_byte(),
3493                            node,
3494                            limit,
3495                        )
3496                    {
3497                        return RecoveredCReferenceRanges::LimitExceeded;
3498                    }
3499                }
3500                if macro_value_kind == Some("preproc_def") {
3501                    for reference in object_macro_replacement_type_references(node, source) {
3502                        for range in reference.component_ranges {
3503                            let visible = self
3504                                .visible_identifier_candidates(file, &source[range.clone()])
3505                                .any(|candidate| candidate.is_class() || candidate.is_module());
3506                            if visible
3507                                && !push_recovered_c_range(
3508                                    &mut ranges,
3509                                    &mut seen,
3510                                    range.start,
3511                                    range.end,
3512                                    node,
3513                                    limit,
3514                                )
3515                            {
3516                                return RecoveredCReferenceRanges::LimitExceeded;
3517                            }
3518                        }
3519                    }
3520                }
3521            }
3522            if inside_error
3523                && recovered_c_reference_node(self, file, node, source)
3524                && !push_recovered_c_range(
3525                    &mut ranges,
3526                    &mut seen,
3527                    node.start_byte(),
3528                    node.end_byte(),
3529                    node,
3530                    limit,
3531                )
3532            {
3533                return RecoveredCReferenceRanges::LimitExceeded;
3534            }
3535            let mut cursor = node.walk();
3536            for child in node.named_children(&mut cursor) {
3537                stack.push((child, inside_error));
3538            }
3539        }
3540        ranges.sort_unstable();
3541        RecoveredCReferenceRanges::Complete(ranges)
3542    }
3543
3544    /// Whether this target is an indexed macro visible from this file.
3545    ///
3546    /// An unresolved conditional can make more than one same-name macro a
3547    /// possible active binding. Each possible target can keep the site as an
3548    /// unproven hit. A macro in an unrelated translation unit stays excluded.
3549    pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
3550        self.visible_identifier_candidates(file, target.identifier())
3551            .filter(|candidate| candidate.is_macro())
3552            .any(|candidate| {
3553                candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
3554            })
3555    }
3556
3557    pub fn object_macro_replacement_at(
3558        &self,
3559        file: &ProjectFile,
3560        name: &str,
3561        before_byte: usize,
3562    ) -> Option<String> {
3563        let environment = self.macro_environment(file, before_byte);
3564        let binding = environment.binding(name)?;
3565        if !binding.exact {
3566            return None;
3567        }
3568        match &binding.definition {
3569            MacroDefinition::Object { replacement } => Some(replacement.clone()),
3570            MacroDefinition::Function { .. }
3571            | MacroDefinition::VariadicFunction { .. }
3572            | MacroDefinition::Unsupported => None,
3573        }
3574    }
3575
3576    fn apply_macro_events(
3577        &self,
3578        file: &ProjectFile,
3579        before_byte: Option<usize>,
3580        environment: &mut MacroEnvironment,
3581        include_stack: &mut HashSet<ProjectFile>,
3582    ) {
3583        if !include_stack.insert(file.clone()) {
3584            return;
3585        }
3586        if self.cpp.prepared_syntax(self.token, file).is_none() {
3587            environment.mark_unknown_names(file, before_byte.unwrap_or_default());
3588            include_stack.remove(file);
3589            return;
3590        }
3591        match self.macro_include_protection(file) {
3592            MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
3593                Some(binding) if binding.is_exact() => {
3594                    include_stack.remove(file);
3595                    return;
3596                }
3597                Some(_) | None if environment.unknown_names => {
3598                    let mut ambiguous_seen = HashSet::default();
3599                    self.mark_macro_events_ambiguous(
3600                        file,
3601                        environment,
3602                        &mut ambiguous_seen,
3603                        file,
3604                        before_byte.unwrap_or_default(),
3605                    );
3606                    include_stack.remove(file);
3607                    return;
3608                }
3609                Some(_) => {
3610                    let mut ambiguous_seen = HashSet::default();
3611                    self.mark_macro_events_ambiguous(
3612                        file,
3613                        environment,
3614                        &mut ambiguous_seen,
3615                        file,
3616                        before_byte.unwrap_or_default(),
3617                    );
3618                    include_stack.remove(file);
3619                    return;
3620                }
3621                None => {}
3622            },
3623            MacroIncludeProtection::PragmaOnce => {
3624                if !environment.applied_pragma_once_files.insert(file.clone()) {
3625                    include_stack.remove(file);
3626                    return;
3627                }
3628                if environment.maybe_applied_pragma_once_files.remove(file) {
3629                    // A prior conditional include may already have consumed the pragma-once
3630                    // header. This unconditional include guarantees it is consumed now, but
3631                    // cannot prove whether its events occur before or after intervening local
3632                    // macro changes, so preserve the union as ambiguous.
3633                    let mut ambiguous_seen = HashSet::default();
3634                    environment.applied_pragma_once_files.remove(file);
3635                    self.mark_macro_events_ambiguous(
3636                        file,
3637                        environment,
3638                        &mut ambiguous_seen,
3639                        file,
3640                        before_byte.unwrap_or_default(),
3641                    );
3642                    environment.maybe_applied_pragma_once_files.remove(file);
3643                    environment.applied_pragma_once_files.insert(file.clone());
3644                    include_stack.remove(file);
3645                    return;
3646                }
3647            }
3648            MacroIncludeProtection::None => {}
3649        }
3650        let cell = self.macro_event_cell(file);
3651        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3652        for event in events {
3653            if before_byte.is_some_and(|limit| event.byte() >= limit) {
3654                break;
3655            }
3656            self.apply_macro_event(file, event, environment, include_stack);
3657        }
3658        include_stack.remove(file);
3659    }
3660
3661    fn apply_macro_event(
3662        &self,
3663        file: &ProjectFile,
3664        event: &MacroEvent,
3665        environment: &mut MacroEnvironment,
3666        include_stack: &mut HashSet<ProjectFile>,
3667    ) {
3668        #[cfg(any(test, feature = "test-support"))]
3669        self.macro_event_application_count
3670            .fetch_add(1, Ordering::Relaxed);
3671        match event {
3672            MacroEvent::Define {
3673                name,
3674                binding,
3675                conditionals,
3676                byte,
3677            } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3678                Some(true) => environment.insert(name.clone(), binding.clone()),
3679                Some(false) => {}
3680                None => Self::merge_conditional_macro_definition(
3681                    environment,
3682                    name,
3683                    binding,
3684                    file,
3685                    *byte,
3686                ),
3687            },
3688            MacroEvent::Undef {
3689                name,
3690                conditionals,
3691                byte,
3692            } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3693                Some(true) => environment.remove(name),
3694                Some(false) => {}
3695                None => {
3696                    if environment.binding(name).is_some() {
3697                        environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
3698                    }
3699                }
3700            },
3701            MacroEvent::Include {
3702                targets,
3703                conditionals,
3704                byte,
3705            } => {
3706                let condition =
3707                    self.macro_event_condition_value(file, *byte, environment, conditionals);
3708                if condition == Some(false) {
3709                    return;
3710                }
3711                if targets.is_empty() {
3712                    environment.mark_unknown_names(file, *byte);
3713                    return;
3714                }
3715                if condition.is_none() || targets.len() > 1 {
3716                    let mut ambiguous_seen = HashSet::default();
3717                    for target in targets {
3718                        self.mark_macro_events_ambiguous(
3719                            target,
3720                            environment,
3721                            &mut ambiguous_seen,
3722                            file,
3723                            *byte,
3724                        );
3725                    }
3726                } else if let Some(target) = targets.first() {
3727                    self.apply_macro_events(target, None, environment, include_stack);
3728                }
3729            }
3730            MacroEvent::Invalidate { byte } => {
3731                for binding in environment.bindings.values_mut() {
3732                    *binding = MacroBinding::uncertain_from(binding, file, *byte);
3733                }
3734            }
3735        }
3736    }
3737
3738    /// Evaluate the structured conditional path that owns one macro event.
3739    ///
3740    /// `Some(true)` and `Some(false)` are proofs from exact macro bindings at
3741    /// this source byte. `None` preserves the old conditional merge when a
3742    /// build/configuration input or an unsupported expression is involved.
3743    ///
3744    /// `conditionals` is the event's own [`OwningPreprocessorConditionals`]:
3745    /// which ancestors structurally own the event was decided when the events
3746    /// were collected, so all this walk does is find those nodes again and ask
3747    /// the environment what their conditions are worth here.
3748    fn macro_event_condition_value(
3749        &self,
3750        file: &ProjectFile,
3751        event_byte: usize,
3752        environment: &MacroEnvironment,
3753        conditionals: &OwningPreprocessorConditionals,
3754    ) -> Option<bool> {
3755        if conditionals.is_empty() {
3756            return Some(true);
3757        }
3758        let prepared = self.cpp.prepared_syntax(self.token, file)?;
3759        let source = prepared.source();
3760        let root = prepared.tree().root_node();
3761        let descendant = root.descendant_for_byte_range(
3762            event_byte,
3763            event_byte.saturating_add(1).min(source.len()),
3764        )?;
3765        let mut unknown = false;
3766        let mut current = descendant.parent();
3767        while let Some(conditional) = current {
3768            if matches!(
3769                conditional.kind(),
3770                "preproc_if" | "preproc_ifdef" | "preproc_elif"
3771            ) && conditionals.contains(&conditional.start_byte())
3772            {
3773                let mut value = match conditional.kind() {
3774                    "preproc_ifdef" => {
3775                        let name = conditional.child_by_field_name("name")?;
3776                        let defined =
3777                            self.macro_name_defined_value(environment, node_text(name, source));
3778                        match conditional.child(0)?.kind() {
3779                            "#ifdef" => defined,
3780                            "#ifndef" => defined.map(|defined| !defined),
3781                            _ => None,
3782                        }
3783                    }
3784                    "preproc_if" | "preproc_elif" => conditional
3785                        .child_by_field_name("condition")
3786                        .and_then(|condition| {
3787                            self.preprocessor_integer_value(
3788                                condition,
3789                                source,
3790                                environment,
3791                                &mut Vec::new(),
3792                                0,
3793                            )
3794                        })
3795                        .map(|value| value != 0),
3796                    _ => unreachable!(),
3797                };
3798                if conditional
3799                    .child_by_field_name("alternative")
3800                    .is_some_and(|alternative| {
3801                        alternative.start_byte() <= descendant.start_byte()
3802                            && descendant.end_byte() <= alternative.end_byte()
3803                    })
3804                {
3805                    value = value.map(|value| !value);
3806                }
3807                match value {
3808                    Some(true) => {}
3809                    Some(false) => return Some(false),
3810                    None => unknown = true,
3811                }
3812            }
3813            current = conditional.parent();
3814        }
3815        (!unknown).then_some(true)
3816    }
3817
3818    fn macro_name_defined_value(&self, environment: &MacroEnvironment, name: &str) -> Option<bool> {
3819        if environment.known_undefined_names.contains(name) {
3820            return Some(false);
3821        }
3822        if let Some(binding) = environment.binding(name) {
3823            return binding.is_exact().then_some(true);
3824        }
3825        environment
3826            .build_proven_defines
3827            .contains(name)
3828            .then_some(true)
3829    }
3830
3831    fn preprocessor_integer_value(
3832        &self,
3833        expression: Node<'_>,
3834        source: &str,
3835        environment: &MacroEnvironment,
3836        expansion_stack: &mut Vec<(ProjectFile, usize)>,
3837        depth: usize,
3838    ) -> Option<i128> {
3839        // Macro replacement graphs can cycle. This explicit bound makes the
3840        // otherwise recursive AST evaluation stack-safe for hostile input.
3841        if depth >= 64 {
3842            return None;
3843        }
3844        match expression.kind() {
3845            "number_literal" => parse_cpp_integer_literal(node_text(expression, source)),
3846            "identifier" | "type_identifier" => {
3847                let binding = environment.binding(node_text(expression, source))?;
3848                if !binding.is_exact() {
3849                    return None;
3850                }
3851                let MacroDefinition::Object { replacement } = &binding.definition else {
3852                    return None;
3853                };
3854                let identity = (binding.source.clone(), binding.declaration_byte);
3855                if expansion_stack.contains(&identity) {
3856                    return None;
3857                }
3858                expansion_stack.push(identity);
3859                let parsed = self.parsed_macro_replacement(binding, replacement);
3860                let value = match parsed.as_ref() {
3861                    ParsedMacroReplacement::Parsed {
3862                        source: replacement_source,
3863                        tree,
3864                    } => first_descendant_of_kind(tree.root_node(), "call_expression")
3865                        .and_then(|call| call.child_by_field_name("arguments"))
3866                        .and_then(|arguments| argument_children(arguments).next())
3867                        .and_then(|argument| {
3868                            self.preprocessor_integer_value(
3869                                argument,
3870                                replacement_source,
3871                                environment,
3872                                expansion_stack,
3873                                depth + 1,
3874                            )
3875                        }),
3876                    ParsedMacroReplacement::Unsupported => None,
3877                };
3878                expansion_stack.pop();
3879                value
3880            }
3881            "preproc_defined" => {
3882                let mut cursor = expression.walk();
3883                let name = expression
3884                    .named_children(&mut cursor)
3885                    .find(|child| child.kind() == "identifier")?;
3886                self.macro_name_defined_value(environment, node_text(name, source))
3887                    .map(i128::from)
3888            }
3889            "parenthesized_expression" => expression.named_child(0).and_then(|child| {
3890                self.preprocessor_integer_value(
3891                    child,
3892                    source,
3893                    environment,
3894                    expansion_stack,
3895                    depth + 1,
3896                )
3897            }),
3898            "unary_expression" => {
3899                let operator = expression.child_by_field_name("operator")?.kind();
3900                let argument = expression.child_by_field_name("argument")?;
3901                let value = self.preprocessor_integer_value(
3902                    argument,
3903                    source,
3904                    environment,
3905                    expansion_stack,
3906                    depth + 1,
3907                )?;
3908                match operator {
3909                    "+" => Some(value),
3910                    "-" => value.checked_neg(),
3911                    "!" => Some(i128::from(value == 0)),
3912                    "~" => Some(!value),
3913                    _ => None,
3914                }
3915            }
3916            "binary_expression" => {
3917                let left = self.preprocessor_integer_value(
3918                    expression.child_by_field_name("left")?,
3919                    source,
3920                    environment,
3921                    expansion_stack,
3922                    depth + 1,
3923                )?;
3924                let right = self.preprocessor_integer_value(
3925                    expression.child_by_field_name("right")?,
3926                    source,
3927                    environment,
3928                    expansion_stack,
3929                    depth + 1,
3930                )?;
3931                match expression.child_by_field_name("operator")?.kind() {
3932                    "+" => left.checked_add(right),
3933                    "-" => left.checked_sub(right),
3934                    "*" => left.checked_mul(right),
3935                    "/" => left.checked_div(right),
3936                    "%" => left.checked_rem(right),
3937                    "<<" => u32::try_from(right)
3938                        .ok()
3939                        .and_then(|shift| left.checked_shl(shift)),
3940                    ">>" => u32::try_from(right)
3941                        .ok()
3942                        .and_then(|shift| left.checked_shr(shift)),
3943                    "<" => Some(i128::from(left < right)),
3944                    "<=" => Some(i128::from(left <= right)),
3945                    ">" => Some(i128::from(left > right)),
3946                    ">=" => Some(i128::from(left >= right)),
3947                    "==" => Some(i128::from(left == right)),
3948                    "!=" => Some(i128::from(left != right)),
3949                    "&" => Some(left & right),
3950                    "|" => Some(left | right),
3951                    "^" => Some(left ^ right),
3952                    "&&" => Some(i128::from(left != 0 && right != 0)),
3953                    "||" => Some(i128::from(left != 0 || right != 0)),
3954                    _ => None,
3955                }
3956            }
3957            _ => None,
3958        }
3959    }
3960
3961    fn mark_macro_events_ambiguous(
3962        &self,
3963        file: &ProjectFile,
3964        environment: &mut MacroEnvironment,
3965        include_stack: &mut HashSet<ProjectFile>,
3966        conditional_file: &ProjectFile,
3967        conditional_byte: usize,
3968    ) {
3969        if !include_stack.insert(file.clone()) {
3970            return;
3971        }
3972        if self.cpp.prepared_syntax(self.token, file).is_none() {
3973            environment.mark_unknown_names(conditional_file, conditional_byte);
3974            return;
3975        }
3976        match self.macro_include_protection(file) {
3977            MacroIncludeProtection::MacroGuard(guard) => {
3978                if environment
3979                    .binding(&guard)
3980                    .is_some_and(MacroBinding::is_exact)
3981                {
3982                    return;
3983                }
3984            }
3985            MacroIncludeProtection::PragmaOnce => {
3986                if environment.applied_pragma_once_files.contains(file) {
3987                    return;
3988                }
3989                environment
3990                    .maybe_applied_pragma_once_files
3991                    .insert(file.clone());
3992            }
3993            MacroIncludeProtection::None => {}
3994        }
3995        let cell = self.macro_event_cell(file);
3996        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3997        for event in events {
3998            #[cfg(any(test, feature = "test-support"))]
3999            self.macro_event_application_count
4000                .fetch_add(1, Ordering::Relaxed);
4001            match event {
4002                MacroEvent::Define { name, binding, .. } => {
4003                    Self::merge_conditional_macro_definition(
4004                        environment,
4005                        name,
4006                        binding,
4007                        conditional_file,
4008                        conditional_byte,
4009                    );
4010                }
4011                MacroEvent::Undef { name, .. } => {
4012                    if environment.binding(name).is_some() {
4013                        environment.insert(
4014                            name.clone(),
4015                            MacroBinding::ambiguous(conditional_file, conditional_byte),
4016                        );
4017                    } else {
4018                        environment.remove_known_undefined(name);
4019                    }
4020                }
4021                MacroEvent::Include { targets, .. } => {
4022                    if targets.is_empty() {
4023                        environment.mark_unknown_names(conditional_file, conditional_byte);
4024                        continue;
4025                    }
4026                    for target in targets {
4027                        self.mark_macro_events_ambiguous(
4028                            target,
4029                            environment,
4030                            include_stack,
4031                            conditional_file,
4032                            conditional_byte,
4033                        );
4034                    }
4035                }
4036                MacroEvent::Invalidate { .. } => {
4037                    for binding in environment.bindings.values_mut() {
4038                        *binding = MacroBinding::uncertain_from(
4039                            binding,
4040                            conditional_file,
4041                            conditional_byte,
4042                        );
4043                    }
4044                }
4045            }
4046        }
4047    }
4048
4049    fn merge_conditional_macro_definition(
4050        environment: &mut MacroEnvironment,
4051        name: &str,
4052        possible_binding: &MacroBinding,
4053        conditional_file: &ProjectFile,
4054        conditional_byte: usize,
4055    ) {
4056        // A conditional include can revisit an already-active guarded header.
4057        // If the possible branch defines the exact same macro, both outcomes
4058        // leave the binding unchanged; degrading it to Unknown would discard
4059        // proof because of an unrelated unresolved macro name (#2092).
4060        if environment.binding(name).is_some_and(|current| {
4061            current.definition != MacroDefinition::Unsupported
4062                && current.definition == possible_binding.definition
4063        }) {
4064            return;
4065        }
4066        environment.insert(
4067            name.to_string(),
4068            MacroBinding::ambiguous(conditional_file, conditional_byte),
4069        );
4070    }
4071
4072    pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
4073        let cell = self
4074            .macro_include_protection_cells
4075            .lock()
4076            .expect("C++ include protection cache poisoned")
4077            .entry(file.clone())
4078            .or_default()
4079            .clone();
4080        cell.get_or_init(|| {
4081            self.cpp.prepared_syntax(self.token, file).map_or(
4082                MacroIncludeProtection::None,
4083                |prepared| {
4084                    top_level_macro_include_protection(
4085                        prepared.tree().root_node(),
4086                        prepared.source(),
4087                    )
4088                },
4089            )
4090        })
4091        .clone()
4092    }
4093
4094    fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
4095        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4096            return Vec::new();
4097        };
4098        let source = prepared.source();
4099        let mut events = Vec::new();
4100        let root = prepared.tree().root_node();
4101        let mut stack = vec![root];
4102        while let Some(node) = stack.pop() {
4103            match node.kind() {
4104                "preproc_def" | "preproc_function_def" => {
4105                    let Some(name) = node.child_by_field_name("name") else {
4106                        continue;
4107                    };
4108                    let name = node_text(name, source).to_string();
4109                    events.push(MacroEvent::Define {
4110                        name,
4111                        binding: MacroBinding {
4112                            source: file.clone(),
4113                            declaration_byte: node.start_byte(),
4114                            definition: Self::decode_macro_definition(node, source),
4115                            exact: true,
4116                        },
4117                        byte: node.start_byte(),
4118                        conditionals: owning_preprocessor_conditionals(root, node, source),
4119                    });
4120                    continue;
4121                }
4122                "preproc_include" => {
4123                    let Some(path) = node.child_by_field_name("path") else {
4124                        events.push(MacroEvent::Include {
4125                            targets: Vec::new(),
4126                            byte: node.start_byte(),
4127                            conditionals: owning_preprocessor_conditionals(root, node, source),
4128                        });
4129                        continue;
4130                    };
4131                    let include = structured_include_path(path, source);
4132                    let targets = include.map_or_else(Vec::new, |include| {
4133                        resolve_include_targets_with_index(
4134                            file,
4135                            include,
4136                            self.cpp.include_target_index(),
4137                        )
4138                    });
4139                    // An include no indexed file can answer -- its final path component names
4140                    // no analyzable workspace file -- crosses into sources this index does not
4141                    // hold, so it cannot hide indexed macro state and must not poison every
4142                    // later local macro binding. The include's spelling does not decide that:
4143                    // a quoted include falls back to the same search path an angle include
4144                    // uses, and esphome's `core/log.h` reaches every component with
4145                    // `#include "WString.h"`, an Arduino header absent from the tree (#3057).
4146                    // A computed include, and a spelling some indexed file name does match
4147                    // without resolving to one target, may still hide indexed macro state and
4148                    // therefore fail closed.
4149                    if targets.is_empty()
4150                        && include.is_some_and(|include| {
4151                            !self.cpp.include_target_index().names_indexed_file(include)
4152                        })
4153                    {
4154                        continue;
4155                    }
4156                    events.push(MacroEvent::Include {
4157                        targets,
4158                        byte: node.start_byte(),
4159                        conditionals: owning_preprocessor_conditionals(root, node, source),
4160                    });
4161                    continue;
4162                }
4163                "preproc_call" => {
4164                    let Some(directive) = node.child_by_field_name("directive") else {
4165                        continue;
4166                    };
4167                    if node_text(directive, source) != "#undef" {
4168                        continue;
4169                    }
4170                    let name = node
4171                        .child_by_field_name("argument")
4172                        .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
4173                    if let Some(name) = name {
4174                        events.push(MacroEvent::Undef {
4175                            name,
4176                            byte: node.start_byte(),
4177                            conditionals: owning_preprocessor_conditionals(root, node, source),
4178                        });
4179                    } else {
4180                        events.push(MacroEvent::Invalidate {
4181                            byte: node.start_byte(),
4182                        });
4183                    }
4184                    continue;
4185                }
4186                _ => {}
4187            }
4188            push_named_children_reversed(node, &mut stack);
4189        }
4190        events.sort_by_key(MacroEvent::byte);
4191        events
4192    }
4193
4194    pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
4195        self.ordinary_type_import_cells
4196            .lock()
4197            .expect("C++ ordinary type import cache poisoned")
4198            .entry(file.clone())
4199            .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
4200            .clone()
4201    }
4202
4203    pub fn project_using_index(
4204        &self,
4205        build: impl FnOnce() -> ProjectUsingIndex,
4206    ) -> &ProjectUsingIndex {
4207        self.project_using_index.get_or_init(build)
4208    }
4209
4210    pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
4211        let mut files = self
4212            .visible_source_files_by_root
4213            .values()
4214            .flatten()
4215            .cloned()
4216            .collect::<HashSet<_>>()
4217            .into_iter()
4218            .collect::<Vec<_>>();
4219        files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
4220        files
4221    }
4222
4223    pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
4224        self.visible_source_files_by_root
4225            .get(root)
4226            .is_some_and(|files| files.contains(source))
4227    }
4228
4229    fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
4230        let cached = self
4231            .visible_parser_alias_name_sets
4232            .read()
4233            .expect("visible parser alias-name cache poisoned")
4234            .get(file)
4235            .cloned();
4236        let cell = if let Some(cached) = cached {
4237            cached
4238        } else {
4239            let mut cells = self
4240                .visible_parser_alias_name_sets
4241                .write()
4242                .expect("visible parser alias-name cache poisoned");
4243            Arc::clone(
4244                cells
4245                    .entry(file.clone())
4246                    .or_insert_with(|| Arc::new(OnceLock::new())),
4247            )
4248        };
4249        if let Some(names) = cell.get() {
4250            return names.contains(name);
4251        }
4252        let mut names = HashSet::default();
4253        let visible_files = self
4254            .visible_source_files_by_root
4255            .get(file)
4256            .cloned()
4257            .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
4258        for visible_file in visible_files {
4259            let Some(aliases) = self.canonical_alias_cell(self.cpp, &visible_file) else {
4260                return false;
4261            };
4262            for alias in aliases.get().expect("published canonical aliases").iter() {
4263                names.insert(alias.name.clone());
4264            }
4265        }
4266        #[cfg(any(test, feature = "test-support"))]
4267        self.visible_parser_alias_name_set_build_count
4268            .fetch_add(1, Ordering::Relaxed);
4269        cell.get_or_init(|| names).contains(name)
4270    }
4271
4272    pub fn parser_alias_name_may_resolve_to_target(
4273        &self,
4274        file: &ProjectFile,
4275        alias_name: &str,
4276        target: &CodeUnit,
4277    ) -> bool {
4278        let started = std::time::Instant::now();
4279        self.parser_alias_fallback_calls
4280            .fetch_add(1, Ordering::Relaxed);
4281        let key = (
4282            file.clone(),
4283            alias_name.to_string(),
4284            logical_symbol_key(target),
4285        );
4286        let cached = self
4287            .parser_alias_target_matches
4288            .read()
4289            .expect("parser alias target-match cache poisoned")
4290            .get(&key)
4291            .cloned();
4292        let cell = if let Some(cached) = cached {
4293            cached
4294        } else {
4295            let mut cells = self
4296                .parser_alias_target_matches
4297                .write()
4298                .expect("parser alias target-match cache poisoned");
4299            Arc::clone(
4300                cells
4301                    .entry(key)
4302                    .or_insert_with(|| Arc::new(OnceLock::new())),
4303            )
4304        };
4305        let matched = *cell.get_or_init(|| match self.visible_source_files_by_root.get(file) {
4306            None => {
4307                self.parser_alias_fallback_files
4308                    .fetch_add(1, Ordering::Relaxed);
4309                self.file_alias_matches(self.cpp, file, alias_name, target)
4310            }
4311            Some(visible_files) => visible_files.iter().any(|visible_file| {
4312                self.parser_alias_fallback_files
4313                    .fetch_add(1, Ordering::Relaxed);
4314                self.file_alias_matches(self.cpp, visible_file, alias_name, target)
4315            }),
4316        });
4317        self.parser_alias_fallback_elapsed_micros.fetch_add(
4318            started.elapsed().as_micros().min(usize::MAX as u128) as usize,
4319            Ordering::Relaxed,
4320        );
4321        matched
4322    }
4323
4324    fn callable_arities_for_target(
4325        &self,
4326        analyzer: &CppGraphSource<'_>,
4327        cpp: &dyn CppSource,
4328        file: &ProjectFile,
4329        prepared: &PreparedSyntaxTree,
4330        spec: &TargetSpec,
4331    ) -> Vec<ActivatedCallableArity> {
4332        let Some(signature) = spec.target.signature() else {
4333            return Vec::new();
4334        };
4335        let Some(candidates) = self
4336            .visible_by_identifier
4337            .get(file)
4338            .and_then(|by_name| by_name.get(&spec.member_name))
4339        else {
4340            return Vec::new();
4341        };
4342        let differing_candidates = candidates
4343            .iter()
4344            .filter(|candidate| {
4345                candidate.is_function()
4346                    && candidate.fq_name() == spec.target.fq_name()
4347                    && candidate.signature() == Some(signature)
4348            })
4349            .filter_map(|candidate| {
4350                analyzer
4351                    .signature_metadata(candidate)
4352                    .into_iter()
4353                    .find_map(|metadata| metadata.callable_arity())
4354                    .filter(|arity| Some(*arity) != spec.callable_arity)
4355                    .map(|arity| (candidate, arity))
4356            })
4357            .collect::<Vec<_>>();
4358        if differing_candidates.is_empty() {
4359            return Vec::new();
4360        }
4361        let mut arities = Vec::with_capacity(differing_candidates.len());
4362        // The activation ranges here describe the whole file rather than one
4363        // reference, so there is no reference guard environment to consult.
4364        let reference = CallableReferenceContext {
4365            file,
4366            position: None,
4367        };
4368        for (candidate, candidate_arity) in differing_candidates {
4369            let declaration_activation =
4370                callable_declaration_activation_in_file(analyzer, candidate, &reference);
4371            let Some(declaration_activation) = declaration_activation else {
4372                continue;
4373            };
4374            let activation_byte = if candidate.source() == file {
4375                Some(declaration_activation)
4376            } else {
4377                self.include_activation_for_source(cpp, file, prepared, candidate.source())
4378            };
4379            if let Some(activation_byte) = activation_byte {
4380                arities.push(ActivatedCallableArity {
4381                    activation_byte,
4382                    arity: candidate_arity,
4383                });
4384            }
4385        }
4386        arities
4387    }
4388
4389    fn callable_parameter_macro_arity(
4390        &self,
4391        target: &CodeUnit,
4392        parameter_types: Option<&[String]>,
4393    ) -> Option<CallableArity> {
4394        let parameter_types = parameter_types?;
4395        let [macro_name] = parameter_types else {
4396            return None;
4397        };
4398        if macro_name.is_empty()
4399            || !macro_name
4400                .chars()
4401                .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
4402        {
4403            return None;
4404        }
4405        let cache_key = (target.source().clone(), macro_name.clone());
4406        if let Some(cached) = self
4407            .callable_parameter_macro_arities
4408            .lock()
4409            .expect("C++ callable parameter-macro arity cache poisoned")
4410            .get(&cache_key)
4411            .copied()
4412        {
4413            return cached;
4414        }
4415        let mut visible_files = HashSet::default();
4416        collect_include_closure(
4417            &self.cpp_source(),
4418            self.cpp.include_target_index(),
4419            target.source(),
4420            &mut visible_files,
4421            None,
4422        );
4423        let mut arities = Vec::new();
4424        for visible_file in visible_files {
4425            let cell = self.macro_event_cell(&visible_file);
4426            for event in
4427                cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
4428            {
4429                let MacroEvent::Define { name, binding, .. } = event else {
4430                    continue;
4431                };
4432                if name != macro_name {
4433                    continue;
4434                }
4435                let MacroDefinition::Object { replacement } = &binding.definition else {
4436                    continue;
4437                };
4438                let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
4439                    continue;
4440                };
4441                if !arities.contains(&arity) {
4442                    arities.push(arity);
4443                }
4444            }
4445        }
4446        let resolved = (|| {
4447            let required = arities
4448                .iter()
4449                .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
4450                .min()?;
4451            let total = arities.iter().map(|arity| arity.total()).max()?;
4452            let repeated = arities
4453                .iter()
4454                .any(|arity| arity.accepts(arity.total().saturating_add(1)));
4455            // Preprocessor conditions can leave more than one object-like parameter
4456            // bundle active in the target header's include closure. Preserve their
4457            // conservative callable envelope instead of choosing whichever definition
4458            // happened to be visited first.
4459            Some(CallableArity::new(required, total, repeated))
4460        })();
4461        self.callable_parameter_macro_arities
4462            .lock()
4463            .expect("C++ callable parameter-macro arity cache poisoned")
4464            .insert(cache_key, resolved);
4465        resolved
4466    }
4467
4468    pub fn include_activation_for_source(
4469        &self,
4470        cpp: &dyn CppSource,
4471        file: &ProjectFile,
4472        prepared: &PreparedSyntaxTree,
4473        donor_source: &ProjectFile,
4474    ) -> Option<usize> {
4475        let key = (file.clone(), donor_source.clone());
4476        if let Some(cached) = self
4477            .include_activation_cells
4478            .lock()
4479            .expect("C++ include activation cache poisoned")
4480            .get(&key)
4481            .copied()
4482        {
4483            return cached;
4484        }
4485        #[cfg(any(test, feature = "test-support"))]
4486        self.include_activation_build_count
4487            .fetch_add(1, Ordering::Relaxed);
4488        let activation = find_include_activation(cpp, self.token, file, prepared, donor_source);
4489        let mut cells = self
4490            .include_activation_cells
4491            .lock()
4492            .expect("C++ include activation cache poisoned");
4493        *cells.entry(key).or_insert(activation)
4494    }
4495
4496    pub fn conditional_include_projections_for_source(
4497        &self,
4498        file: &ProjectFile,
4499        prepared: &PreparedSyntaxTree,
4500        donor_source: &ProjectFile,
4501    ) -> Arc<[ConditionalIncludeProjection]> {
4502        static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
4503        let cell = self
4504            .conditional_include_projection_cells
4505            .lock()
4506            .expect("C++ conditional include projection cache poisoned")
4507            .entry(file.clone())
4508            .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
4509            .clone();
4510        let index = cell.get_or_build_pool_independent(|| {
4511            #[cfg(any(test, feature = "test-support"))]
4512            self.conditional_include_projection_index_build_count
4513                .fetch_add(1, Ordering::Relaxed);
4514            find_conditional_include_projection_index(self.cpp, self.token, file, prepared, &|| {
4515                #[cfg(any(test, feature = "test-support"))]
4516                self.conditional_include_projection_state_count
4517                    .fetch_add(1, Ordering::Relaxed);
4518            })
4519        });
4520        index
4521            .get(donor_source)
4522            .cloned()
4523            .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
4524    }
4525
4526    #[cfg(any(test, feature = "test-support"))]
4527    pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
4528        (
4529            self.conditional_include_projection_index_build_count
4530                .load(Ordering::Relaxed),
4531            self.conditional_include_projection_state_count
4532                .load(Ordering::Relaxed),
4533        )
4534    }
4535
4536    #[cfg(any(test, feature = "test-support"))]
4537    pub fn conditional_include_target_state_count_for_test(&self) -> usize {
4538        self.conditional_include_target_state_count
4539            .load(Ordering::Relaxed)
4540    }
4541
4542    #[cfg(any(test, feature = "test-support"))]
4543    pub fn include_activation_build_count_for_test(&self) -> usize {
4544        self.include_activation_build_count.load(Ordering::Relaxed)
4545    }
4546
4547    #[cfg(any(test, feature = "test-support"))]
4548    pub fn note_using_donor_activation_for_test(&self) {
4549        self.using_donor_activation_count
4550            .fetch_add(1, Ordering::Relaxed);
4551    }
4552
4553    #[cfg(not(any(test, feature = "test-support")))]
4554    pub fn note_using_donor_activation_for_test(&self) {}
4555
4556    #[cfg(any(test, feature = "test-support"))]
4557    pub fn note_using_namespace_lookup_for_test(&self) {
4558        self.using_namespace_lookup_count
4559            .fetch_add(1, Ordering::Relaxed);
4560    }
4561
4562    #[cfg(not(any(test, feature = "test-support")))]
4563    pub fn note_using_namespace_lookup_for_test(&self) {}
4564
4565    #[cfg(any(test, feature = "test-support"))]
4566    pub fn note_using_name_candidate_inspection_for_test(&self) {
4567        self.using_name_candidate_inspection_count
4568            .fetch_add(1, Ordering::Relaxed);
4569    }
4570
4571    #[cfg(not(any(test, feature = "test-support")))]
4572    pub fn note_using_name_candidate_inspection_for_test(&self) {}
4573
4574    #[cfg(any(test, feature = "test-support"))]
4575    pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
4576        (
4577            self.using_donor_activation_count.load(Ordering::Relaxed),
4578            self.using_namespace_lookup_count.load(Ordering::Relaxed),
4579            self.callable_reference_spec_build_count
4580                .load(Ordering::Relaxed),
4581            self.using_name_candidate_inspection_count
4582                .load(Ordering::Relaxed),
4583        )
4584    }
4585
4586    pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
4587        file == target.source()
4588            || self
4589                .visible_by_file
4590                .get(file)
4591                .is_some_and(|visible| visible.contains(target))
4592    }
4593
4594    /// Whether some declaration of `declaration`'s logical symbol is visible at
4595    /// `reference_byte` in `file`.
4596    ///
4597    /// The question is asked of the *logical* symbol, not of the physical unit:
4598    /// an out-of-line body in a `.cpp` nobody includes is never itself visible,
4599    /// and it does not have to be - what makes the call legal is the header
4600    /// declaration that the reference file does include. Reading that relation
4601    /// through `same_logical_callable` rather than through signature strings is
4602    /// the same #2010 correction the gates make, and it matters here because
4603    /// the body and the declaration are exactly the pair that spells one
4604    /// parameter type two ways.
4605    pub fn declaration_visible_at(
4606        &self,
4607        analyzer: &CppGraphSource<'_>,
4608        file: &ProjectFile,
4609        declaration: &CodeUnit,
4610        reference_byte: usize,
4611    ) -> bool {
4612        let reference_guards = OnceCell::new();
4613        self.visible_identifier_candidates(file, declaration.identifier())
4614            .filter(|candidate| {
4615                self.same_logical_callable(analyzer, candidate, declaration)
4616                    || flattened_macro_namespace_declaration_matches(
4617                        analyzer,
4618                        self.cpp,
4619                        file,
4620                        candidate,
4621                        declaration,
4622                        reference_byte,
4623                    )
4624            })
4625            .any(|candidate| {
4626                self.physical_declaration_visible_at(
4627                    analyzer,
4628                    file,
4629                    candidate,
4630                    reference_byte,
4631                    &reference_guards,
4632                )
4633            })
4634    }
4635
4636    /// Whether a physical declaration is visible at an exact structured
4637    /// reference node. Inverse C field references need the reference's own
4638    /// preprocessor environment before using the callable activation path:
4639    /// callable activation can establish that a field declaration is
4640    /// nameable, but it must not admit the opposite branch of that field's
4641    /// conditional family.
4642    pub fn declaration_visible_at_reference(
4643        &self,
4644        analyzer: &CppGraphSource<'_>,
4645        file: &ProjectFile,
4646        declaration: &CodeUnit,
4647        reference: Node<'_>,
4648    ) -> bool {
4649        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4650            return false;
4651        };
4652        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4653        let declaration_guards = declaration_guard_requirements(analyzer, self.cpp, declaration);
4654        if !declaration_guards.iter().any(|(_, required)| {
4655            guards_compatible_at_reference(required, reference_guards.as_ref())
4656        }) {
4657            return false;
4658        }
4659        if declaration.source() == file
4660            && !analyzer.reference_uses_c_semantics(file)
4661            && (declaration.is_field() || declaration.is_callable())
4662            && type_owner_of(analyzer, declaration).is_some_and(|owner| {
4663                self.indexed_enclosing_owner_scope(analyzer, file, reference)
4664                    .is_some_and(|scope| scope == canonical_cpp_scope_components(&owner))
4665            })
4666        {
4667            // C++ complete-class context makes members visible throughout the
4668            // class. The guard check above still excludes a declaration from
4669            // an incompatible preprocessor branch.
4670            return true;
4671        }
4672        if declaration.is_field() && declaration.source() == file {
4673            let reference_byte = reference.start_byte();
4674            let reference_function =
4675                real_function_definition_ancestor(reference, prepared.source());
4676            let guards = OnceCell::new();
4677            let field_reference = CallableReferenceContext {
4678                file,
4679                position: Some(CallableReferencePosition {
4680                    prepared: prepared.as_ref(),
4681                    byte: reference_byte,
4682                    guards: &guards,
4683                }),
4684            };
4685            let mut has_local_declaration = false;
4686            let mut local_declaration_visible = false;
4687            for declaration in callable_declaration_nodes(analyzer, prepared.as_ref(), declaration)
4688            {
4689                let Some(declaration_function) =
4690                    real_function_definition_ancestor(declaration, prepared.source())
4691                else {
4692                    continue;
4693                };
4694                has_local_declaration = true;
4695                if reference_function.is_some_and(|reference_function| {
4696                    reference_function.start_byte() == declaration_function.start_byte()
4697                        && reference_function.end_byte() == declaration_function.end_byte()
4698                }) && callable_preprocessor_context_is_visible_for_reference(
4699                    declaration,
4700                    prepared.source(),
4701                    &field_reference,
4702                ) && declaration.end_byte() < reference_byte
4703                {
4704                    local_declaration_visible = true;
4705                    break;
4706                }
4707            }
4708            if has_local_declaration {
4709                return local_declaration_visible;
4710            }
4711        }
4712        let guards = OnceCell::new();
4713        self.physical_declaration_visible_at(
4714            analyzer,
4715            file,
4716            declaration,
4717            reference.start_byte(),
4718            &guards,
4719        )
4720    }
4721
4722    /// C forward navigation may bind a call to a later same-file definition.
4723    /// There is no earlier source declaration to activate in that legacy C
4724    /// shape, but the call's preprocessor environment must still imply the
4725    /// definition's requirements. Ordinary C++ and inverse visibility retain
4726    /// the declaration-order rule in [`Self::declaration_visible_at`].
4727    pub fn declaration_visible_for_c_forward_call(
4728        &self,
4729        analyzer: &CppGraphSource<'_>,
4730        file: &ProjectFile,
4731        declaration: &CodeUnit,
4732        reference_byte: usize,
4733    ) -> bool {
4734        if self.declaration_visible_at(analyzer, file, declaration, reference_byte) {
4735            return true;
4736        }
4737        if declaration.source() != file {
4738            return false;
4739        }
4740        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4741            return false;
4742        };
4743        let reference_guards = prepared
4744            .tree()
4745            .root_node()
4746            .descendant_for_byte_range(reference_byte, reference_byte)
4747            .and_then(|node| preprocessor_guard_environment(node, prepared.source()));
4748        declaration_guard_requirements(analyzer, self.cpp, declaration)
4749            .into_iter()
4750            .any(|(_, required)| {
4751                guard_requirements_hold_at_reference(&required, reference_guards.as_ref())
4752            })
4753    }
4754
4755    pub fn callable_arity_at_reference(
4756        &self,
4757        analyzer: &CppGraphSource<'_>,
4758        file: &ProjectFile,
4759        candidate: &CodeUnit,
4760        reference_byte: usize,
4761    ) -> Option<CallableArity> {
4762        let key = (file.clone(), logical_symbol_key(candidate));
4763        let cell = self
4764            .callable_reference_specs
4765            .lock()
4766            .expect("C++ callable reference-spec cache poisoned")
4767            .entry(key)
4768            .or_default()
4769            .clone();
4770        let spec = cell.get_or_init(|| {
4771            let prepared = self.cpp.prepared_syntax(self.token, file)?;
4772            let spec = TargetSpec::from_target(analyzer, candidate)?;
4773            let spec = spec
4774                .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
4775                .into_owned();
4776            #[cfg(any(test, feature = "test-support"))]
4777            self.callable_reference_spec_build_count
4778                .fetch_add(1, Ordering::Relaxed);
4779            Some(spec)
4780        });
4781        spec.as_ref()?.callable_arity_at(reference_byte)
4782    }
4783
4784    fn physical_declaration_visible_at(
4785        &self,
4786        analyzer: &CppGraphSource<'_>,
4787        file: &ProjectFile,
4788        declaration: &CodeUnit,
4789        reference_byte: usize,
4790        reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
4791    ) -> bool {
4792        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4793            return false;
4794        };
4795        let reference = CallableReferenceContext {
4796            file,
4797            position: Some(CallableReferencePosition {
4798                prepared: prepared.as_ref(),
4799                byte: reference_byte,
4800                guards: reference_guards,
4801            }),
4802        };
4803        if declaration.source() == file {
4804            return callable_declaration_activation_in_file(analyzer, declaration, &reference)
4805                .or_else(|| {
4806                    self.exhaustive_guard_family_activation(analyzer, declaration, &reference)
4807                })
4808                .is_some_and(|activation| activation < reference_byte);
4809        }
4810        if self
4811            .foreign_callable_declaration_activation(analyzer, declaration, &reference)
4812            .or_else(|| self.exhaustive_guard_family_activation(analyzer, declaration, &reference))
4813            .is_none()
4814        {
4815            return false;
4816        }
4817        declaration_guard_requirements(analyzer, self.cpp, declaration)
4818            .into_iter()
4819            .any(|(_, declaration_guards)| {
4820                self.foreign_declaration_reachable_at_reference(
4821                    file,
4822                    prepared.as_ref(),
4823                    declaration.source(),
4824                    &declaration_guards,
4825                    reference.guards(),
4826                    reference_byte,
4827                )
4828            })
4829    }
4830
4831    /// The byte at which `declaration` activates inside the foreign file that
4832    /// its canonical source occurrence describes.
4833    ///
4834    /// Which guard rule applies depends on what decides the reference's
4835    /// configuration. When `compile_commands.json` covers the reference's
4836    /// translation unit, the build is the proof source and the declaration's
4837    /// guards must follow from the reference's active guards plus the proven
4838    /// defines; a platform macro the build does not prove leaves the
4839    /// declaration invisible (#2011). With no compile coverage nothing decides
4840    /// a platform macro, so the cross-file rule stands: the donor resolves its
4841    /// own conditionals and only has to stay free of contradiction with the
4842    /// reference. Otherwise a libuv-shaped `#if defined(__linux__)` prototype
4843    /// in a shared header is hidden from every unguarded caller in another
4844    /// translation unit (#2988).
4845    fn foreign_callable_declaration_activation(
4846        &self,
4847        analyzer: &CppGraphSource<'_>,
4848        declaration: &CodeUnit,
4849        reference: &CallableReferenceContext<'_>,
4850    ) -> Option<usize> {
4851        let build_decides = !self.compile_context_is_absent(reference.file);
4852        let proven = self.compile_proven_guards(reference.file);
4853        let augmented;
4854        let active = match reference.guards() {
4855            Some(active) if !proven.is_empty() => {
4856                augmented = active.union(&proven).cloned().collect();
4857                Some(&augmented)
4858            }
4859            other => other,
4860        };
4861        analyzer
4862            .declaration_source_occurrences(declaration)?
4863            .into_iter()
4864            .filter_map(|occurrence| {
4865                let activation = occurrence.fact.callable_activation?;
4866                let mut required =
4867                    cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
4868                if required.iter().any(|guard| match guard {
4869                    PreprocessorGuard::Defined(name) if name == "__cplusplus" => reference.is_c(),
4870                    PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
4871                        !reference.is_c()
4872                    }
4873                    _ => false,
4874                }) {
4875                    return None;
4876                }
4877                required.retain(|guard| {
4878                    !matches!(guard,
4879                    PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
4880                        if name == "__cplusplus")
4881                });
4882                let visible = required.is_empty()
4883                    || if build_decides {
4884                        guard_requirements_hold_at_reference(&required, active)
4885                    } else {
4886                        guards_compatible_at_reference(&required, reference.guards())
4887                    };
4888                visible.then_some(activation)
4889            })
4890            .min()
4891    }
4892
4893    pub fn external_type_candidate_visible_at(
4894        &self,
4895        file: &ProjectFile,
4896        candidate: &CodeUnit,
4897        reference_byte: usize,
4898    ) -> bool {
4899        if candidate.source() == file {
4900            return true;
4901        }
4902        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4903            return false;
4904        };
4905        self.visible_identifier_candidates(file, candidate.identifier())
4906            .filter(|peer| same_logical_symbol(candidate, peer))
4907            .any(|peer| {
4908                peer.source() == file
4909                    || self
4910                        .include_activation_for_source(
4911                            self.cpp,
4912                            file,
4913                            prepared.as_ref(),
4914                            peer.source(),
4915                        )
4916                        .is_some_and(|activation| activation <= reference_byte)
4917            })
4918    }
4919
4920    pub fn external_type_declaration_visible_at(
4921        &self,
4922        file: &ProjectFile,
4923        candidate: &CodeUnit,
4924        reference_byte: usize,
4925    ) -> bool {
4926        if candidate.source() == file {
4927            return true;
4928        }
4929        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4930            return false;
4931        };
4932        self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
4933            .is_some_and(|activation| activation <= reference_byte)
4934    }
4935
4936    /// The preprocessor facts the build proves for a reference sited in
4937    /// `file` (#2011).
4938    ///
4939    /// Every `-D` that survives its command's `-D`/`-U` ordering is a positive
4940    /// `Defined` fact, and a fact holds only when every compile configuration
4941    /// that governs the file agrees on it (intersection). The facts are
4942    /// strictly additive to the reference's active guard set: they can prove a
4943    /// required guard, but the guard check itself is never weakened and no
4944    /// implication is ever inferred from source text.
4945    ///
4946    /// A file with its own database entry answers from that entry alone
4947    /// (phase 1). A header takes its context from the translation units whose
4948    /// include closure reaches it, intersected across all of them (phase 2):
4949    /// the header is compiled once per including TU, so a fact holds for a
4950    /// header-sited reference only when every one of those compilations
4951    /// proves it. A reaching TU the database does not cover proves nothing,
4952    /// which empties the intersection. A file nothing covers or reaches has
4953    /// no facts and every check runs on source structure alone.
4954    pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
4955        if let Some(cached) = self
4956            .compile_proven_guard_cells
4957            .lock()
4958            .expect("C++ compile-proven guard cache poisoned")
4959            .get(file)
4960        {
4961            return Arc::clone(cached);
4962        }
4963        let names = match context_fact_names(self.cpp.compile_contexts_for(file)) {
4964            Some(names) => names,
4965            None => {
4966                let mut translation_units = self.cpp.reaching_translation_units(file).into_iter();
4967                let seed = translation_units.next().and_then(|translation_unit| {
4968                    context_fact_names(self.cpp.compile_contexts_for(&translation_unit))
4969                });
4970                match seed {
4971                    None => HashSet::default(),
4972                    Some(mut names) => {
4973                        for translation_unit in translation_units {
4974                            let Some(reached) = context_fact_names(
4975                                self.cpp.compile_contexts_for(&translation_unit),
4976                            ) else {
4977                                names.clear();
4978                                break;
4979                            };
4980                            names.retain(|name| reached.contains(name));
4981                            if names.is_empty() {
4982                                break;
4983                            }
4984                        }
4985                        names
4986                    }
4987                }
4988            }
4989        };
4990        let proven = Arc::new(
4991            names
4992                .into_iter()
4993                .map(PreprocessorGuard::Defined)
4994                .collect::<HashSet<_>>(),
4995        );
4996        self.compile_proven_guard_cells
4997            .lock()
4998            .expect("C++ compile-proven guard cache poisoned")
4999            .insert(file.clone(), Arc::clone(&proven));
5000        proven
5001    }
5002
5003    /// The rule a conditional `#include` path's guards face on the way to a
5004    /// reference in `file`.
5005    ///
5006    /// This is the include-edge analogue of the choice
5007    /// [`Self::foreign_callable_declaration_activation`] makes for a foreign
5008    /// declaration's own guards (#2988), and it is decided by the same thing:
5009    /// what settles the reference's configuration. Memoized per file because
5010    /// the answer depends on nothing else, and the callers ask it once per
5011    /// candidate.
5012    fn include_path_admission(&self, file: &ProjectFile) -> IncludePathAdmission {
5013        if let Some(cached) = self
5014            .include_path_admission_cells
5015            .lock()
5016            .expect("C++ include-path admission cache poisoned")
5017            .get(file)
5018            .copied()
5019        {
5020            return cached;
5021        }
5022        let admission = if self.compile_context_is_absent(file) {
5023            IncludePathAdmission::Compatible
5024        } else {
5025            IncludePathAdmission::Proven
5026        };
5027        self.include_path_admission_cells
5028            .lock()
5029            .expect("C++ include-path admission cache poisoned")
5030            .insert(file.clone(), admission);
5031        admission
5032    }
5033
5034    /// Whether no compile data covers the compilations of `file`: it has no
5035    /// database entry of its own, and either nothing reaches it or some
5036    /// translation unit that reaches it has no entry. This is the state a
5037    /// regenerated `compile_commands.json` could decide; data that is present
5038    /// for every governing compilation but does not prove a guard is a
5039    /// decided conservative miss, not this state.
5040    fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
5041        if !self.cpp.compile_contexts_for(file).is_empty() {
5042            return false;
5043        }
5044        let translation_units = self.cpp.reaching_translation_units(file);
5045        translation_units.is_empty()
5046            || translation_units
5047                .iter()
5048                .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
5049    }
5050
5051    /// Whether a lookup miss for `identifier` in `file` is explainable by
5052    /// missing compile context (#2011): some same-name declaration is
5053    /// reachable through a conditional include whose required guards neither
5054    /// contradict the reference's active guards nor follow from them, and the
5055    /// translation unit has no compile-commands entry that could decide the
5056    /// question. Callers surface this as an explicit "requires compile
5057    /// context" incompleteness instead of an indistinguishable miss.
5058    ///
5059    /// A structurally disproven declaration (contradicting guards) and a TU
5060    /// whose compile context exists but does not prove the guard both answer
5061    /// `false`: those misses are decided, not incomplete.
5062    pub fn miss_requires_compile_context(
5063        &self,
5064        file: &ProjectFile,
5065        identifier: &str,
5066        reference: Node<'_>,
5067    ) -> bool {
5068        if !self.compile_context_is_absent(file) {
5069            return false;
5070        }
5071        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5072            return false;
5073        };
5074        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5075        let reference_byte = reference.start_byte();
5076        let mut sources = self
5077            .visible_identifier_candidates(file, identifier)
5078            .map(CodeUnit::source)
5079            .filter(|source| *source != file)
5080            .collect::<Vec<_>>();
5081        sources.sort();
5082        sources.dedup();
5083        sources.into_iter().any(|declaration_source| {
5084            self.conditional_include_projections_for_source(
5085                file,
5086                prepared.as_ref(),
5087                declaration_source,
5088            )
5089            .iter()
5090            .any(|projection| {
5091                projection.activation_byte <= reference_byte
5092                    && !guard_requirements_hold_at_reference(
5093                        &projection.required_guards,
5094                        reference_guards.as_ref(),
5095                    )
5096                    && guards_compatible_at_reference(
5097                        &projection.required_guards,
5098                        reference_guards.as_ref(),
5099                    )
5100            })
5101        })
5102    }
5103
5104    /// Decide whether a declaration that lives in another file reaches a
5105    /// reference in `file`.
5106    ///
5107    /// An external header selects its declaration branch before the reference
5108    /// file is parsed. Require compatible reference guards, but do not test
5109    /// the header's guard expression for stability in the reference file: a
5110    /// `.c` translation unit can never satisfy the `#ifdef __cplusplus` that
5111    /// wraps every declaration of a portable C header, and demanding it would
5112    /// hide the whole header. Guards that the reference file imposes on its
5113    /// own `#include` still have to be stable, and must satisfy the rule
5114    /// [`IncludePathAdmission`] names for this reference's translation unit.
5115    fn foreign_declaration_reachable_at_reference(
5116        &self,
5117        file: &ProjectFile,
5118        prepared: &PreparedSyntaxTree,
5119        declaration_source: &ProjectFile,
5120        declaration_guards: &HashSet<PreprocessorGuard>,
5121        reference_guards: Option<&HashSet<PreprocessorGuard>>,
5122        reference_byte: usize,
5123    ) -> bool {
5124        // The translation unit's build-proven defines join the reference's
5125        // active guard set (#2011): a conditional include like the nng
5126        // `NNG_PLATFORM_POSIX` chain is provable only by the compile command.
5127        // A reference whose own environment is unknown stays unknown -- the
5128        // facts extend an environment, they never invent one.
5129        let proven = self.compile_proven_guards(file);
5130        let augmented;
5131        let reference_guards = match reference_guards {
5132            Some(active) if !proven.is_empty() => {
5133                augmented = active.union(&proven).cloned().collect();
5134                Some(&augmented)
5135            }
5136            other => other,
5137        };
5138        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5139            eprintln!(
5140                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=foreign_guard_compatibility declaration_source={} declaration_guards={declaration_guards:?} reference_guards={reference_guards:?}",
5141                declaration_source.rel_path().display(),
5142            );
5143        }
5144        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5145            return false;
5146        }
5147        if self
5148            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5149            .is_some_and(|activation| activation <= reference_byte)
5150        {
5151            return true;
5152        }
5153        let projections =
5154            self.conditional_include_projections_for_source(file, prepared, declaration_source);
5155        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5156            eprintln!(
5157                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=filtered_projection source={} declaration_guards={} proven_guards={} projections={}",
5158                declaration_source.rel_path().display(),
5159                declaration_guards.len(),
5160                proven.len(),
5161                projections.len(),
5162            );
5163        }
5164        let admission = self.include_path_admission(file);
5165        projections.iter().any(|projection| {
5166            projection.activation_byte <= reference_byte
5167                && admission.admits(
5168                    &projection.required_guards,
5169                    &projection.partial_guards,
5170                    reference_guards,
5171                )
5172                && self.preprocessor_guards_stable_between(
5173                    file,
5174                    projection.activation_byte,
5175                    reference_byte,
5176                    &projection.required_guards,
5177                )
5178        })
5179    }
5180
5181    fn foreign_declaration_may_be_reachable_from_raw_guards(
5182        &self,
5183        file: &ProjectFile,
5184        prepared: &PreparedSyntaxTree,
5185        declaration_source: &ProjectFile,
5186        declaration_guards: &HashSet<PreprocessorGuard>,
5187        reference_guards: Option<&HashSet<PreprocessorGuard>>,
5188        reference_byte: usize,
5189    ) -> bool {
5190        let proven = self.compile_proven_guards(file);
5191        let augmented;
5192        let reference_guards = match reference_guards {
5193            Some(active) if !proven.is_empty() => {
5194                augmented = active.union(&proven).cloned().collect();
5195                Some(&augmented)
5196            }
5197            other => other,
5198        };
5199        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5200            return false;
5201        }
5202        if self
5203            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5204            .is_some_and(|activation| activation <= reference_byte)
5205        {
5206            return true;
5207        }
5208        let reachable = find_conditional_include_projection_for_source(
5209            self.cpp,
5210            self.token,
5211            file,
5212            prepared,
5213            declaration_source,
5214            self.include_path_admission(file),
5215            reference_guards,
5216            reference_byte,
5217            &|| {
5218                #[cfg(any(test, feature = "test-support"))]
5219                self.conditional_include_target_state_count
5220                    .fetch_add(1, Ordering::Relaxed);
5221            },
5222        );
5223        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5224            eprintln!(
5225                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_projection source={} declaration_guards={} proven_guards={} raw_guards={} reachable={reachable}",
5226                declaration_source.rel_path().display(),
5227                declaration_guards.len(),
5228                proven.len(),
5229                reference_guards.map_or(0, HashSet::len),
5230            );
5231        }
5232        reachable
5233    }
5234
5235    fn foreign_declaration_reachable_from_compile_proven_guards(
5236        &self,
5237        file: &ProjectFile,
5238        prepared: &PreparedSyntaxTree,
5239        declaration_source: &ProjectFile,
5240        declaration_guards: &HashSet<PreprocessorGuard>,
5241        reference_byte: usize,
5242    ) -> bool {
5243        let proven = self.compile_proven_guards(file);
5244        if proven.is_empty()
5245            || !guards_compatible_at_reference(declaration_guards, Some(proven.as_ref()))
5246        {
5247            return false;
5248        }
5249        let projections =
5250            self.conditional_include_projections_for_source(file, prepared, declaration_source);
5251        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5252            eprintln!(
5253                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=compile_proven_projection source={} declaration_guards={} proven_guards={} projections={}",
5254                declaration_source.rel_path().display(),
5255                declaration_guards.len(),
5256                proven.len(),
5257                projections.len(),
5258            );
5259        }
5260        projections.iter().any(|projection| {
5261            projection.activation_byte <= reference_byte
5262                    && guard_requirements_hold_at_reference(
5263                        &projection.required_guards,
5264                        Some(proven.as_ref()),
5265                    )
5266                    // Build facts hold at translation-unit entry. A source
5267                    // `#undef` or an earlier include may invalidate one before
5268                    // this conditional include is reached; mutations after the
5269                    // include cannot revoke declarations it already supplied.
5270                    && self.preprocessor_guards_stable_between(
5271                        file,
5272                        0,
5273                        projection.activation_byte,
5274                        &projection.required_guards,
5275                    )
5276        })
5277    }
5278
5279    pub fn external_type_candidate_visible_in_context(
5280        &self,
5281        analyzer: &CppGraphSource<'_>,
5282        file: &ProjectFile,
5283        candidate: &CodeUnit,
5284        reference: Node<'_>,
5285    ) -> bool {
5286        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
5287        if report_stats {
5288            eprintln!(
5289                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=started fqn={} candidate_source={} reference_file={} reference_byte={}",
5290                candidate.fq_name(),
5291                candidate.source().rel_path().display(),
5292                file.rel_path().display(),
5293                reference.start_byte(),
5294            );
5295        }
5296        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5297            return false;
5298        };
5299        let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
5300        let reference_guards = OnceCell::new();
5301        let reference_guards_at_site = || {
5302            reference_guards.get_or_init(|| {
5303                let started = Instant::now();
5304                if report_stats {
5305                    eprintln!(
5306                        "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=started file={} reference_byte={} raw_guards={}",
5307                        file.rel_path().display(),
5308                        reference.start_byte(),
5309                        raw_reference_guards.as_ref().map_or(0, HashSet::len),
5310                    );
5311                }
5312                let macro_environment = self.macro_environment(file, reference.start_byte());
5313                let filtered = raw_reference_guards
5314                    .clone()
5315                    .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
5316                if report_stats {
5317                    eprintln!(
5318                        "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=completed retained={} elapsed_ms={}",
5319                        filtered.is_some(),
5320                        started.elapsed().as_millis(),
5321                    );
5322                }
5323                filtered
5324            })
5325        };
5326
5327        let peers = self
5328            .visible_identifier_candidates(file, candidate.identifier())
5329            .filter(|peer| same_logical_symbol(candidate, peer))
5330            .collect::<Vec<_>>();
5331        if report_stats {
5332            let peer_sources = peers
5333                .iter()
5334                .map(|peer| peer.source().rel_path().display().to_string())
5335                .collect::<Vec<_>>();
5336            eprintln!(
5337                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=peers fqn={} sources={peer_sources:?}",
5338                candidate.fq_name(),
5339            );
5340        }
5341        let directly_visible_without_reference_environment = peers.iter().any(|peer| {
5342            declaration_guard_requirements(analyzer, self.cpp, peer)
5343                .into_iter()
5344                .any(|(declaration_byte, declaration_guards)| {
5345                    if peer.source() == file {
5346                        let visible = declaration_byte < reference.start_byte()
5347                            && declaration_guards.is_empty();
5348                        if report_stats {
5349                            eprintln!(
5350                                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=true visible={visible}",
5351                                peer.source().rel_path().display(),
5352                                declaration_guards.len(),
5353                            );
5354                        }
5355                        return visible;
5356                    }
5357                    let direct = declaration_guards.is_empty()
5358                        && self
5359                            .include_activation_for_source(
5360                                self.cpp,
5361                                file,
5362                                prepared.as_ref(),
5363                                peer.source(),
5364                            )
5365                            .is_some_and(|activation| activation <= reference.start_byte());
5366                    let compile_proven = !direct
5367                        && self.foreign_declaration_reachable_from_compile_proven_guards(
5368                            file,
5369                            prepared.as_ref(),
5370                            peer.source(),
5371                            &declaration_guards,
5372                            reference.start_byte(),
5373                        );
5374                    if report_stats {
5375                        eprintln!(
5376                            "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=false direct={direct} compile_proven={compile_proven}",
5377                            peer.source().rel_path().display(),
5378                            declaration_guards.len(),
5379                        );
5380                    }
5381                    direct || compile_proven
5382                })
5383        });
5384        if directly_visible_without_reference_environment {
5385            if report_stats {
5386                eprintln!(
5387                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=direct_or_compile_proven fqn={}",
5388                    candidate.fq_name(),
5389                );
5390            }
5391            return true;
5392        }
5393        let directly_visible = peers.iter().any(|peer| {
5394            declaration_guard_sites(analyzer, self.cpp, peer, Some(reference.start_byte()))
5395                .into_iter()
5396                .any(|site| {
5397                    let declaration_byte = site.byte;
5398                    let declaration_guards = site.guards;
5399                    if peer.source() == file {
5400                        if declaration_byte >= reference.start_byte() {
5401                            return false;
5402                        }
5403                        // The reference's own conditional context must either
5404                        // prove the declaration's requirements, or follow a
5405                        // completed family that encloses the declaration
5406                        // (#3297); either way it must not contradict them.
5407                        let active = |guards: Option<&HashSet<PreprocessorGuard>>| {
5408                            guard_requirements_hold_at_reference(&declaration_guards, guards)
5409                                || (site.completed_family_before_reference
5410                                    && guards_compatible_at_reference(&declaration_guards, guards))
5411                        };
5412                        if !active(raw_reference_guards.as_ref()) {
5413                            return false;
5414                        }
5415                        return active(reference_guards_at_site().as_ref())
5416                            && self.preprocessor_guards_stable_between(
5417                                file,
5418                                declaration_byte,
5419                                reference.start_byte(),
5420                                &declaration_guards,
5421                            );
5422                    }
5423                    let raw_feasible = self.foreign_declaration_may_be_reachable_from_raw_guards(
5424                        file,
5425                        prepared.as_ref(),
5426                        peer.source(),
5427                        &declaration_guards,
5428                        raw_reference_guards.as_ref(),
5429                        reference.start_byte(),
5430                    );
5431                    if report_stats {
5432                        eprintln!(
5433                            "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_feasibility source={} declaration_guards={} feasible={raw_feasible}",
5434                            peer.source().rel_path().display(),
5435                            declaration_guards.len(),
5436                        );
5437                    }
5438                    if !raw_feasible {
5439                        return false;
5440                    }
5441                    self.foreign_declaration_reachable_at_reference(
5442                        file,
5443                        prepared.as_ref(),
5444                        peer.source(),
5445                        &declaration_guards,
5446                        reference_guards_at_site().as_ref(),
5447                        reference.start_byte(),
5448                    )
5449                })
5450        });
5451        if directly_visible {
5452            if report_stats {
5453                eprintln!(
5454                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=filtered_reference fqn={}",
5455                    candidate.fq_name(),
5456                );
5457            }
5458            return true;
5459        }
5460        let complementary = self
5461            .visible_identifier_candidates(file, candidate.identifier())
5462            .filter(|peer| {
5463                peer.kind() == candidate.kind()
5464                    && peer.fq_name() == candidate.fq_name()
5465                    && peer.source() == candidate.source()
5466            })
5467            .collect::<Vec<_>>();
5468        // A completed #if/#else family declares the shared source-level name
5469        // before this reference. A later macro mutation cannot revoke that
5470        // declaration. The family gate below rejects declarations split across
5471        // separate conditional blocks, where mutation can change coverage.
5472        let complementary_family =
5473            self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate);
5474        let raw_candidate_branch_compatible = complementary_family
5475            && raw_reference_guards.as_ref().is_some_and(|active| {
5476                declaration_guard_requirements(analyzer, self.cpp, candidate)
5477                    .iter()
5478                    .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5479            });
5480        if report_stats {
5481            eprintln!(
5482                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=complementary fqn={} candidates={} family={} raw_compatible={}",
5483                candidate.fq_name(),
5484                complementary.len(),
5485                complementary_family,
5486                raw_candidate_branch_compatible,
5487            );
5488        }
5489        let candidate_branch_compatible = raw_candidate_branch_compatible
5490            && reference_guards_at_site().as_ref().is_some_and(|active| {
5491                declaration_guard_requirements(analyzer, self.cpp, candidate)
5492                    .iter()
5493                    .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5494            });
5495        let complementary_visible = candidate_branch_compatible
5496            && if candidate.source() == file {
5497                declaration_guard_requirements(analyzer, self.cpp, candidate)
5498                    .iter()
5499                    .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
5500            } else {
5501                self.include_activation_for_source(
5502                    self.cpp,
5503                    file,
5504                    prepared.as_ref(),
5505                    candidate.source(),
5506                )
5507                .is_some_and(|activation| activation <= reference.start_byte())
5508            };
5509        if report_stats {
5510            eprintln!(
5511                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome={} fqn={}",
5512                if complementary_visible {
5513                    "complementary"
5514                } else {
5515                    "missing"
5516                },
5517                candidate.fq_name(),
5518            );
5519        }
5520        complementary_visible
5521    }
5522
5523    pub fn is_exhaustive_same_fqn_type_declaration_family(
5524        &self,
5525        analyzer: &CppGraphSource<'_>,
5526        file: &ProjectFile,
5527        candidate: &CodeUnit,
5528    ) -> bool {
5529        let candidates = self
5530            .visible_identifier_candidates(file, candidate.identifier())
5531            .filter(|peer| {
5532                peer.kind() == candidate.kind()
5533                    && peer.fq_name() == candidate.fq_name()
5534                    && peer.source() == candidate.source()
5535            })
5536            .collect::<Vec<_>>();
5537        self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
5538    }
5539
5540    /// Prove a nested type alias used as a dependent member-pointer owner when
5541    /// its owning class has mutually-exclusive declarations.  A common C++11
5542    /// compatibility shape provides the owning class in one preprocessor
5543    /// branch and aliases it to a standard-library type in the other branch;
5544    /// the nested fallback alias is therefore not itself active in every
5545    /// branch even though the qualified owner API is.
5546    ///
5547    /// This is deliberately narrower than ordinary type visibility.  The
5548    /// caller has already recovered a member-pointer owner path from the CST;
5549    /// this helper additionally requires the target's structured parent to
5550    /// match that path, physical source visibility, and exact preprocessor
5551    /// guard agreement with the parent declaration.  Only then may the
5552    /// parent's direct/complementary same-FQN visibility stand in for the
5553    /// nested terminal's active-branch check.
5554    pub fn dependent_member_pointer_alias_visible_in_context(
5555        &self,
5556        analyzer: &CppGraphSource<'_>,
5557        file: &ProjectFile,
5558        candidate: &CodeUnit,
5559        owner_components: &[String],
5560        reference: Node<'_>,
5561    ) -> bool {
5562        if !analyzer
5563            .type_alias_provider()
5564            .is_some_and(|provider| provider.is_type_alias(candidate))
5565        {
5566            return false;
5567        }
5568        let Some((terminal, owner_prefix)) = owner_components.split_last() else {
5569            return false;
5570        };
5571        if terminal != candidate.identifier()
5572            || canonical_cpp_scope_components(candidate) != owner_components
5573        {
5574            return false;
5575        }
5576        let Some(expected_parent_fq_name) =
5577            brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
5578        else {
5579            return false;
5580        };
5581        let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
5582            return false;
5583        };
5584        if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
5585            || parent_anchor.source() != candidate.source()
5586            || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
5587        {
5588            return false;
5589        }
5590
5591        // The ordinary path already handles unguarded aliases (and preserves
5592        // same-file declaration ordering).  This fallback is only for a
5593        // physically visible declaration whose guard is the owning branch's
5594        // guard, so reject a same-file declaration that appears after the
5595        // reference before considering guard compatibility.
5596        if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
5597            || candidate.source() == file
5598                && !analyzer
5599                    .ranges(candidate)
5600                    .iter()
5601                    .any(|range| range.start_byte < reference.start_byte())
5602        {
5603            return false;
5604        }
5605
5606        let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
5607        if candidate_guards.is_empty() {
5608            return false;
5609        }
5610        let same_guard_sets =
5611            |left: &[(usize, HashSet<PreprocessorGuard>)],
5612             right: &[(usize, HashSet<PreprocessorGuard>)]| {
5613                left.iter().all(|(_, left_guards)| {
5614                    right
5615                        .iter()
5616                        .any(|(_, right_guards)| left_guards == right_guards)
5617                })
5618            };
5619        let parent_candidates = self
5620            .visible_identifier_candidates(file, parent_anchor.identifier())
5621            .filter(|peer| {
5622                peer.kind() == parent_anchor.kind()
5623                    && peer.fq_name() == expected_parent_fq_name.as_str()
5624                    && peer.source() == parent_anchor.source()
5625                    && canonical_cpp_scope_components(peer) == owner_prefix
5626            })
5627            .filter_map(|peer| {
5628                let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
5629                (candidate_guards.len() == parent_guards.len()
5630                    && same_guard_sets(&candidate_guards, &parent_guards)
5631                    && same_guard_sets(&parent_guards, &candidate_guards))
5632                .then(|| (peer.clone(), parent_guards))
5633            })
5634            .collect::<Vec<_>>();
5635        let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
5636            return false;
5637        };
5638
5639        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5640            return false;
5641        };
5642        let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
5643        else {
5644            return false;
5645        };
5646        // An external header selects its declaration branch before the
5647        // reference file is parsed. Require compatible reference guards, but
5648        // do not test the header's guard expression for stability in the
5649        // reference file. Same-file aliases still require that stability.
5650        if !candidate_guards.iter().any(|(_, target_guards)| {
5651            guards_compatible_at_reference(target_guards, Some(&reference_guards))
5652                && (candidate.source() != file
5653                    || self.preprocessor_guards_stable_between(
5654                        file,
5655                        0,
5656                        reference.start_byte(),
5657                        target_guards,
5658                    ))
5659        }) {
5660            return false;
5661        }
5662
5663        self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
5664    }
5665
5666    /// Check a type candidate's preprocessor/import context without imposing
5667    /// ordinary declaration-before-reference ordering for same-file peers.
5668    ///
5669    /// C++ class scope makes member names visible throughout the complete
5670    /// class, including a trailing return type that appears before the member
5671    /// alias declaration in source order. Callers must first prove that the
5672    /// reference is inside the candidate's indexed class owner; this helper
5673    /// only relaxes the byte-order predicate while retaining guard and include
5674    /// activation checks.
5675    pub fn external_type_candidate_guard_compatible_in_context(
5676        &self,
5677        analyzer: &CppGraphSource<'_>,
5678        file: &ProjectFile,
5679        candidate: &CodeUnit,
5680        reference: Node<'_>,
5681    ) -> bool {
5682        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5683            return false;
5684        };
5685        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5686
5687        self.visible_identifier_candidates(file, candidate.identifier())
5688            .filter(|peer| same_logical_symbol(candidate, peer))
5689            .any(|peer| {
5690                declaration_guard_requirements(analyzer, self.cpp, peer)
5691                    .into_iter()
5692                    .any(|(declaration_byte, declaration_guards)| {
5693                        if peer.source() == file {
5694                            let (start, end) = if declaration_byte <= reference.start_byte() {
5695                                (declaration_byte, reference.start_byte())
5696                            } else {
5697                                (reference.start_byte(), declaration_byte)
5698                            };
5699                            return guard_requirements_hold_at_reference(
5700                                &declaration_guards,
5701                                reference_guards.as_ref(),
5702                            ) && self.preprocessor_guards_stable_between(
5703                                file,
5704                                start,
5705                                end,
5706                                &declaration_guards,
5707                            );
5708                        }
5709                        self.foreign_declaration_reachable_at_reference(
5710                            file,
5711                            prepared.as_ref(),
5712                            peer.source(),
5713                            &declaration_guards,
5714                            reference_guards.as_ref(),
5715                            reference.start_byte(),
5716                        )
5717                    })
5718            })
5719    }
5720
5721    /// Whether a same-file callable declaration is nameable from `reference`
5722    /// after deliberately relaxing declaration-before-reference ordering.
5723    ///
5724    /// Ordinary lookup still requires an earlier declaration. Definition
5725    /// navigation for incomplete C translation units may recover a later
5726    /// definition, but only when it is at file scope and its preprocessor
5727    /// requirements hold at the call (#2404).
5728    pub fn same_file_callable_guard_compatible_ignoring_order(
5729        &self,
5730        analyzer: &CppGraphSource<'_>,
5731        file: &ProjectFile,
5732        candidate: &CodeUnit,
5733        reference: Node<'_>,
5734    ) -> bool {
5735        if candidate.source() != file || !candidate.is_callable() {
5736            return false;
5737        }
5738        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5739            return false;
5740        };
5741        let guards = OnceCell::new();
5742        let context = CallableReferenceContext {
5743            file,
5744            position: Some(CallableReferencePosition {
5745                prepared: prepared.as_ref(),
5746                byte: reference.start_byte(),
5747                guards: &guards,
5748            }),
5749        };
5750        analyzer
5751            .declaration_source_occurrences(candidate)
5752            .into_iter()
5753            .flatten()
5754            .filter_map(|occurrence| {
5755                let guards = cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
5756                Some(callable_guard_requirements_are_visible(
5757                    &guards,
5758                    occurrence.fact.callable_guard_completion_byte,
5759                    &context,
5760                ))
5761            })
5762            .any(|visible| visible)
5763    }
5764
5765    pub fn type_candidate_may_be_visible_before_reference(
5766        &self,
5767        analyzer: &CppGraphSource<'_>,
5768        file: &ProjectFile,
5769        candidate: &CodeUnit,
5770        reference_byte: usize,
5771    ) -> bool {
5772        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5773            return false;
5774        };
5775        let root = prepared.tree().root_node();
5776        let end_byte = reference_byte
5777            .saturating_add(1)
5778            .min(prepared.source().len());
5779        let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
5780            return false;
5781        };
5782        self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
5783    }
5784
5785    pub fn preprocessor_guards_stable_between(
5786        &self,
5787        file: &ProjectFile,
5788        start_byte: usize,
5789        end_byte: usize,
5790        guards: &HashSet<PreprocessorGuard>,
5791    ) -> bool {
5792        if guards.is_empty() || start_byte >= end_byte {
5793            return true;
5794        }
5795        let cell = self.macro_event_cell(file);
5796        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5797        let mut visited = HashSet::from_iter([file.clone()]);
5798        !events.iter().any(|event| {
5799            event.byte() >= start_byte
5800                && event.byte() < end_byte
5801                && self.macro_event_may_mutate_guards(event, guards, &mut visited)
5802        })
5803    }
5804
5805    fn macro_event_may_mutate_guards(
5806        &self,
5807        event: &MacroEvent,
5808        guards: &HashSet<PreprocessorGuard>,
5809        visited: &mut HashSet<ProjectFile>,
5810    ) -> bool {
5811        match event {
5812            MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
5813                guards.iter().any(|guard| guard.may_depend_on_macro(name))
5814            }
5815            MacroEvent::Include { targets, .. } => {
5816                targets.is_empty()
5817                    || targets
5818                        .iter()
5819                        .any(|target| self.source_may_mutate_guards(target, guards, visited))
5820            }
5821            MacroEvent::Invalidate { .. } => true,
5822        }
5823    }
5824
5825    fn source_may_mutate_guards(
5826        &self,
5827        file: &ProjectFile,
5828        guards: &HashSet<PreprocessorGuard>,
5829        visited: &mut HashSet<ProjectFile>,
5830    ) -> bool {
5831        if !visited.insert(file.clone()) {
5832            return false;
5833        }
5834        let cell = self.macro_event_cell(file);
5835        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5836        events
5837            .iter()
5838            .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
5839    }
5840
5841    pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
5842        let normalized = normalize_reference_name(raw_name)?;
5843        self.type_candidates(file, &normalized)
5844            .into_iter()
5845            .next()
5846            .cloned()
5847    }
5848
5849    /// Mirror forward navigation's visible-name fallback for a bare parameter
5850    /// type after lexical owner and inheritance lookup is exhausted.
5851    ///
5852    /// Generated or otherwise unindexed base classes can hide the alias that
5853    /// makes a parameter type valid C++. Accept the fallback only when every
5854    /// include-visible class or alias with that spelling canonicalizes to one
5855    /// logical type. A shadowing local type resolves lexically before this
5856    /// path, while distinct visible types keep the result ambiguous.
5857    pub fn unique_visible_parameter_type_fallback(
5858        &self,
5859        analyzer: &CppGraphSource<'_>,
5860        file: &ProjectFile,
5861        node: Node<'_>,
5862        source: &str,
5863    ) -> Option<CodeUnit> {
5864        if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
5865            return None;
5866        }
5867        let name = node_text(node, source);
5868        let candidates = self
5869            .visible_identifier_candidates(file, name)
5870            .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
5871            .filter(|candidate| {
5872                self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
5873            })
5874            .collect::<Vec<_>>();
5875        self.unique_canonical_type_candidate(analyzer, file, &candidates)
5876    }
5877
5878    pub fn resolve_type_node_result(
5879        &self,
5880        file: &ProjectFile,
5881        node: Node<'_>,
5882        source: &str,
5883    ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
5884        let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
5885            return Ok(None);
5886        };
5887        let Some(arguments) = cpp_template_reference_arguments(node, source) else {
5888            return Ok(Some(primary));
5889        };
5890        self.resolve_template_arguments(file, primary, &arguments)
5891            .map(Some)
5892    }
5893
5894    pub fn resolve_type_node_primary(
5895        &self,
5896        file: &ProjectFile,
5897        node: Node<'_>,
5898        source: &str,
5899    ) -> Option<CodeUnit> {
5900        let components = cpp_type_name_components(node, source)?;
5901        self.resolve_type(file, &components.join("::"))
5902    }
5903
5904    pub fn resolve_template_arguments(
5905        &self,
5906        file: &ProjectFile,
5907        primary: CodeUnit,
5908        arguments: &[CppTemplateExpression],
5909    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
5910        self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
5911    }
5912
5913    fn resolve_template_arguments_inner(
5914        &self,
5915        file: &ProjectFile,
5916        primary: CodeUnit,
5917        arguments: &[CppTemplateExpression],
5918        seen_aliases: &mut HashSet<CodeUnit>,
5919    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
5920        if let Some(metadata) = self.cpp_template_metadata.get(&primary)
5921            && let Some(alias_target) = &metadata.alias_target
5922        {
5923            if !seen_aliases.insert(primary.clone()) {
5924                return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
5925            }
5926            let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
5927                .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
5928            let target_name = alias_target.components.join("::");
5929            let target_primary = if alias_target.global {
5930                unique_logical_type_candidate(self.type_candidates(file, &target_name))
5931            } else {
5932                self.resolve_unique_type_for_declaration(file, &primary, &target_name)
5933            };
5934            let Some(target_primary) = target_primary else {
5935                // A dependent or external RHS cannot be canonicalized from the
5936                // indexed graph. Preserve the alias's direct identity instead
5937                // of inventing a target from its source spelling.
5938                return Ok(primary);
5939            };
5940            let Some(target_arguments) = &alias_target.arguments else {
5941                return Ok(target_primary);
5942            };
5943            let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
5944                .ok_or(CppTemplateResolutionError::Substitution)?;
5945            return self.resolve_template_arguments_inner(
5946                file,
5947                target_primary,
5948                &target_arguments,
5949                seen_aliases,
5950            );
5951        }
5952
5953        let primary_fq_name = self
5954            .cpp_template_metadata
5955            .get(&primary)
5956            .map(|metadata| metadata.primary_fq_name.clone())
5957            .unwrap_or_else(|| primary.fq_name());
5958        let has_specialization_metadata = self
5959            .cpp_template_families
5960            .get(&primary_fq_name)
5961            .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
5962        if !has_specialization_metadata {
5963            return Ok(primary);
5964        }
5965        self.select_template_specialization(file, &primary, arguments)
5966    }
5967
5968    fn select_template_specialization(
5969        &self,
5970        file: &ProjectFile,
5971        resolved: &CodeUnit,
5972        explicit_arguments: &[CppTemplateExpression],
5973    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
5974        let primary_fq_name = self
5975            .cpp_template_metadata
5976            .get(resolved)
5977            .map(|metadata| metadata.primary_fq_name.clone())
5978            .unwrap_or_else(|| resolved.fq_name());
5979        let family = self
5980            .cpp_template_families
5981            .get(&primary_fq_name)
5982            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
5983        let primary_candidates = family
5984            .iter()
5985            .filter_map(|unit| {
5986                let metadata = self.cpp_template_metadata.get(unit)?;
5987                (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
5988            })
5989            .collect::<Vec<_>>();
5990        let primary_unit = primary_candidates
5991            .iter()
5992            .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
5993            .or_else(|| {
5994                primary_candidates
5995                    .iter()
5996                    .map(|(unit, _)| *unit)
5997                    .min_by_key(|unit| {
5998                        (
5999                            unit.source().to_string(),
6000                            unit.signature().unwrap_or_default(),
6001                        )
6002                    })
6003            })
6004            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6005        let primary_parameters =
6006            cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
6007                .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6008        let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
6009            .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6010
6011        let mut applicable = Vec::new();
6012        for unit in family {
6013            let Some(metadata) = self.cpp_template_metadata.get(unit) else {
6014                continue;
6015            };
6016            if metadata.is_primary() || !self.is_visible(file, unit) {
6017                continue;
6018            }
6019            if !cpp_specialization_matches(metadata, &expanded) {
6020                continue;
6021            }
6022            applicable.push((unit, metadata));
6023        }
6024        if applicable.is_empty() {
6025            return Ok(primary_unit.clone());
6026        }
6027
6028        // A scalar constraint count cannot represent C++ partial ordering:
6029        // e.g. `<T*, U>` and `<T, int>` are incomparable for `<int*, int>`.
6030        // Select only a logical candidate whose structural pattern is strictly
6031        // more specialized than every other distinct applicable candidate.
6032        let winners = applicable
6033            .iter()
6034            .filter(|(candidate, candidate_metadata)| {
6035                applicable.iter().all(|(other, other_metadata)| {
6036                    same_visible_symbol(candidate, other)
6037                        || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
6038                })
6039            })
6040            .copied()
6041            .collect::<Vec<_>>();
6042        let Some((selected, _)) = winners.first() else {
6043            // Mutually incomparable applicable candidates: every one of them
6044            // is a live contender.
6045            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6046                candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
6047            });
6048        };
6049        if winners
6050            .iter()
6051            .any(|(unit, _)| !same_visible_symbol(unit, selected))
6052        {
6053            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6054                candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
6055            });
6056        }
6057        Ok((*selected).clone())
6058    }
6059
6060    pub fn resolve_type_components_lexically(
6061        &self,
6062        analyzer: &CppGraphSource<'_>,
6063        file: &ProjectFile,
6064        components: &[String],
6065        global: bool,
6066        lexical_scope: &[String],
6067    ) -> LexicalTypeResolution {
6068        self.resolve_type_components_lexically_inner(
6069            analyzer,
6070            file,
6071            components,
6072            global,
6073            lexical_scope,
6074            TypeCandidateResolution::Canonical,
6075        )
6076    }
6077
6078    pub fn resolve_type_components_lexically_for_forward(
6079        &self,
6080        analyzer: &CppGraphSource<'_>,
6081        file: &ProjectFile,
6082        components: &[String],
6083        global: bool,
6084        lexical_scope: &[String],
6085    ) -> LexicalTypeResolution {
6086        self.resolve_type_components_lexically_inner(
6087            analyzer,
6088            file,
6089            components,
6090            global,
6091            lexical_scope,
6092            TypeCandidateResolution::PreserveAlias,
6093        )
6094    }
6095
6096    pub fn resolve_type_components_lexically_for_target(
6097        &self,
6098        analyzer: &CppGraphSource<'_>,
6099        file: &ProjectFile,
6100        components: &[String],
6101        global: bool,
6102        lexical_scope: &[String],
6103        target: &CodeUnit,
6104    ) -> LexicalTypeResolution {
6105        #[cfg(any(test, feature = "test-support"))]
6106        self.target_preserving_type_resolution_count
6107            .fetch_add(1, Ordering::Relaxed);
6108        self.resolve_type_components_lexically_inner(
6109            analyzer,
6110            file,
6111            components,
6112            global,
6113            lexical_scope,
6114            TypeCandidateResolution::PreserveTarget(target),
6115        )
6116    }
6117
6118    pub fn coarse_unqualified_type_reference_may_resolve(
6119        &self,
6120        file: &ProjectFile,
6121        name: &str,
6122    ) -> bool {
6123        if name.is_empty() {
6124            return true;
6125        }
6126        self.visible_identifier_candidates(file, name)
6127            .any(|candidate| {
6128                candidate.kind() == CodeUnitType::Class
6129                    || declared_type_alias(&self.cpp_source(), candidate)
6130            })
6131            || self.visible_parser_alias_name_is_visible(file, name)
6132    }
6133
6134    #[allow(clippy::too_many_arguments)]
6135    pub fn structured_type_reference_may_resolve_to_target(
6136        &self,
6137        analyzer: &CppGraphSource<'_>,
6138        file: &ProjectFile,
6139        components: &[String],
6140        global: bool,
6141        lexical_scope: &[String],
6142        target: &CodeUnit,
6143    ) -> bool {
6144        if components.is_empty() {
6145            return true;
6146        }
6147        let Some(terminal) = components.last() else {
6148            return true;
6149        };
6150        let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
6151            .map(|qualified| qualified.join("::"))
6152            .collect::<Vec<_>>();
6153        let target_name = cpp_name_for(target);
6154        if qualified_tiers
6155            .iter()
6156            .any(|qualified| qualified == &target_name)
6157        {
6158            return true;
6159        }
6160
6161        let mut saw_shape_candidate = false;
6162        for candidate in self.visible_identifier_candidates(file, terminal) {
6163            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6164            {
6165                continue;
6166            }
6167            let candidate_name = cpp_name_for(candidate);
6168            let shape_matches = if global || components.len() > 1 {
6169                qualified_tiers
6170                    .iter()
6171                    .any(|qualified| qualified == &candidate_name)
6172            } else {
6173                true
6174            };
6175            if !shape_matches {
6176                continue;
6177            }
6178            saw_shape_candidate = true;
6179            if same_visible_symbol(candidate, target)
6180                || self.c_tag_declaration_family_matches_target(
6181                    analyzer,
6182                    file,
6183                    std::slice::from_ref(&candidate),
6184                    target,
6185                )
6186                || self.compatible_primary_template_redeclarations(candidate, target)
6187                || (declared_type_alias(analyzer, candidate)
6188                    && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
6189            {
6190                return true;
6191            }
6192        }
6193
6194        !saw_shape_candidate
6195    }
6196
6197    /// A C tag's forward declaration and complete definition are one logical
6198    /// type even when their indexed signatures and source files differ. Keep
6199    /// this identity narrow: the complete declaration must be a top-level C
6200    /// tag, every visible candidate must be its physical forward declaration,
6201    /// and the parsed tag kind must agree. Different FQNs, competing complete
6202    /// definitions, aliases, and struct/union mismatches remain ambiguous.
6203    fn cached_c_tag_kind(
6204        &self,
6205        analyzer: &CppGraphSource<'_>,
6206        candidate: &CodeUnit,
6207    ) -> Option<CppCTagKind> {
6208        if let Some(kind) = self
6209            .c_tag_kind_cache
6210            .lock()
6211            .expect("C tag kind cache poisoned")
6212            .get(candidate)
6213        {
6214            return *kind;
6215        }
6216        let kind = indexed_c_tag_kind(analyzer, candidate);
6217        self.c_tag_kind_cache
6218            .lock()
6219            .expect("C tag kind cache poisoned")
6220            .insert(candidate.clone(), kind);
6221        kind
6222    }
6223
6224    fn cached_unique_c_tag_complete_definition(
6225        &self,
6226        analyzer: &CppGraphSource<'_>,
6227        target: &CodeUnit,
6228        target_tag: CppCTagKind,
6229    ) -> Option<CodeUnit> {
6230        if let Some(definition) = self
6231            .c_tag_complete_definition_cache
6232            .lock()
6233            .expect("C tag complete-definition cache poisoned")
6234            .get(target)
6235        {
6236            return definition.clone();
6237        }
6238        let complete_definitions = analyzer
6239            .definitions(&target.fq_name())
6240            .filter(|candidate| {
6241                candidate.is_class()
6242                    && !declared_type_alias(analyzer, candidate)
6243                    && is_c_source_file(candidate.source())
6244                    && analyzer.parent_of(candidate).is_none()
6245                    && cpp_class_declaration_strength(analyzer, candidate)
6246                        == CppClassDeclarationStrength::Full
6247                    && self.cached_c_tag_kind(analyzer, candidate) == Some(target_tag)
6248            })
6249            .collect::<HashSet<_>>();
6250        let definition = (complete_definitions.len() == 1)
6251            .then(|| complete_definitions.into_iter().next())
6252            .flatten()
6253            .filter(|candidate| same_visible_symbol(candidate, target));
6254        self.c_tag_complete_definition_cache
6255            .lock()
6256            .expect("C tag complete-definition cache poisoned")
6257            .insert(target.clone(), definition.clone());
6258        definition
6259    }
6260
6261    pub fn c_tag_declaration_family_matches_target(
6262        &self,
6263        analyzer: &CppGraphSource<'_>,
6264        visible_from: &ProjectFile,
6265        candidates: &[&CodeUnit],
6266        target: &CodeUnit,
6267    ) -> bool {
6268        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
6269            let candidate_evidence = candidates
6270                .iter()
6271                .map(|candidate| {
6272                    (
6273                        candidate.fq_name(),
6274                        candidate.source().rel_path().to_path_buf(),
6275                        cpp_class_declaration_strength(analyzer, candidate),
6276                        indexed_c_tag_kind(analyzer, candidate),
6277                        self.is_physically_visible(visible_from, candidate),
6278                    )
6279                })
6280                .collect::<Vec<_>>();
6281            eprintln!(
6282                "BIFROST_CPP_C_TAG_FAMILY_STATS visible_from={} target=({}, {}, {:?}, {:?}) candidates={candidate_evidence:?}",
6283                visible_from.rel_path().display(),
6284                target.fq_name(),
6285                target.source().rel_path().display(),
6286                cpp_class_declaration_strength(analyzer, target),
6287                indexed_c_tag_kind(analyzer, target),
6288            );
6289        }
6290        if candidates.is_empty()
6291            || !target.is_class()
6292            || declared_type_alias(analyzer, target)
6293            || !is_c_source_file(target.source())
6294            || analyzer.parent_of(target).is_some()
6295            || cpp_class_declaration_strength(analyzer, target) != CppClassDeclarationStrength::Full
6296        {
6297            return false;
6298        }
6299        let Some(target_tag) = self.cached_c_tag_kind(analyzer, target) else {
6300            return false;
6301        };
6302        if self
6303            .cached_unique_c_tag_complete_definition(analyzer, target, target_tag)
6304            .is_none()
6305        {
6306            return false;
6307        }
6308        let mut saw_visible_forward = false;
6309        for candidate in candidates.iter().copied() {
6310            if candidate == target {
6311                continue;
6312            }
6313            if !candidate.is_class()
6314                || declared_type_alias(analyzer, candidate)
6315                || candidate.fq_name() != target.fq_name()
6316                || analyzer.parent_of(candidate).is_some()
6317                || cpp_class_declaration_strength(analyzer, candidate)
6318                    != CppClassDeclarationStrength::Forward
6319                || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6320                || !self.is_physically_visible(visible_from, candidate)
6321            {
6322                return false;
6323            }
6324            saw_visible_forward = true;
6325        }
6326        saw_visible_forward
6327    }
6328
6329    /// Collapse one visible complete C tag and its visible forward declarations
6330    /// before ordinary lexical resolution sees their different signatures as
6331    /// competing types. A second complete definition, a different FQN/tag
6332    /// kind, or an unknown declaration shape remains ambiguous.
6333    fn unique_c_tag_declaration_family(
6334        &self,
6335        analyzer: &CppGraphSource<'_>,
6336        visible_from: &ProjectFile,
6337        candidates: &[&CodeUnit],
6338    ) -> Option<CodeUnit> {
6339        let first = candidates.first()?;
6340        let target_fq_name = first.fq_name();
6341        let target_tag = self.cached_c_tag_kind(analyzer, first)?;
6342        let mut full = None;
6343        let mut saw_forward = false;
6344        for candidate in candidates.iter().copied() {
6345            if !candidate.is_class()
6346                || declared_type_alias(analyzer, candidate)
6347                || candidate.fq_name() != target_fq_name
6348                || analyzer.parent_of(candidate).is_some()
6349                || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6350            {
6351                return None;
6352            }
6353            match cpp_class_declaration_strength(analyzer, candidate) {
6354                CppClassDeclarationStrength::Full
6355                    if is_c_source_file(candidate.source())
6356                        && full.replace(candidate.clone()).is_none() => {}
6357                CppClassDeclarationStrength::Forward
6358                    if self.is_physically_visible(visible_from, candidate) =>
6359                {
6360                    saw_forward = true
6361                }
6362                _ => return None,
6363            }
6364        }
6365        if saw_forward { full } else { None }
6366    }
6367
6368    pub fn target_preserving_reference_namespace(
6369        &self,
6370        analyzer: &CppGraphSource<'_>,
6371        file: &ProjectFile,
6372        identifier: &str,
6373        target: &CodeUnit,
6374    ) -> Option<Vec<String>> {
6375        let mut namespace = None;
6376        for candidate in self.visible_identifier_candidates(file, identifier) {
6377            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6378            {
6379                continue;
6380            }
6381            if !(same_visible_symbol(candidate, target)
6382                || self.compatible_primary_template_redeclarations(candidate, target)
6383                || declared_type_alias(analyzer, candidate)
6384                    && self.structured_alias_primary_preserves_target(
6385                        analyzer, file, candidate, target,
6386                    ))
6387            {
6388                continue;
6389            }
6390            if namespace
6391                .as_ref()
6392                .is_some_and(|existing| existing != candidate.package_name())
6393            {
6394                return None;
6395            }
6396            namespace = Some(candidate.package_name().to_string());
6397        }
6398        let namespace = namespace?;
6399        Some(
6400            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6401                brokk_bifrost_core::analyzer::Language::Cpp,
6402                &namespace,
6403            ),
6404        )
6405    }
6406
6407    pub fn resolve_imported_type_candidate(
6408        &self,
6409        analyzer: &CppGraphSource<'_>,
6410        file: &ProjectFile,
6411        target: &CodeUnit,
6412        target_components: &[String],
6413        direct_target: Option<&CodeUnit>,
6414        preserve_alias: bool,
6415    ) -> LexicalTypeResolution {
6416        let candidates = [target];
6417        let resolution = if preserve_alias {
6418            TypeCandidateResolution::PreserveAlias
6419        } else {
6420            direct_target.map_or(
6421                TypeCandidateResolution::Canonical,
6422                TypeCandidateResolution::PreserveTarget,
6423            )
6424        };
6425        // One candidate goes in, so a failure here is never "choose one of
6426        // these": it is the alias chain leaving the index, which must answer
6427        // missing rather than ambiguous (#1828).
6428        match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6429            Ok(unit) => LexicalTypeResolution::Resolved {
6430                unit,
6431                components: target_components.to_vec(),
6432                candidates: vec![target.clone()],
6433            },
6434            Err(failure) => failure.lexical_resolution(),
6435        }
6436    }
6437
6438    fn resolve_type_components_lexically_inner(
6439        &self,
6440        analyzer: &CppGraphSource<'_>,
6441        file: &ProjectFile,
6442        components: &[String],
6443        global: bool,
6444        lexical_scope: &[String],
6445        resolution: TypeCandidateResolution<'_>,
6446    ) -> LexicalTypeResolution {
6447        if components.is_empty() {
6448            return LexicalTypeResolution::Missing;
6449        }
6450        // A C++ class injects its own name into the class scope.  The indexed
6451        // FqName for that declaration is the class path itself (for example,
6452        // `n::raw_hash_set`), not a synthetic child named
6453        // `n::raw_hash_set::raw_hash_set`.  Ordinary lexical tiers append the
6454        // requested identifier to every scope component, so they cannot
6455        // represent that injected binding when the enclosing class is the
6456        // closest scope.  Recover the binding from the structured class path
6457        // before allowing lookup to fall through to an outer same-spelled
6458        // declaration.
6459        let mut injected = self.resolve_injected_class_name(
6460            analyzer,
6461            file,
6462            components,
6463            global,
6464            lexical_scope,
6465            resolution,
6466        );
6467        for qualified in lexical_component_tiers(components, global, lexical_scope) {
6468            let prefix_len = qualified.len().saturating_sub(components.len());
6469            if injected
6470                .as_ref()
6471                .is_some_and(|(owner_len, _)| prefix_len <= *owner_len)
6472            {
6473                return injected
6474                    .take()
6475                    .expect("injected class resolution was just present")
6476                    .1;
6477            }
6478            let qualified_name = qualified.join("::");
6479            let candidates = self
6480                .type_candidates(file, &qualified_name)
6481                .into_iter()
6482                .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6483                .collect::<Vec<_>>();
6484            if candidates.is_empty() {
6485                if !global && components.len() == 1 {
6486                    match self.resolve_inherited_type_for_lexical_scope(
6487                        analyzer,
6488                        file,
6489                        &qualified[..prefix_len],
6490                        &components[0],
6491                        resolution,
6492                    ) {
6493                        LexicalTypeResolution::Missing => {}
6494                        inherited => return inherited,
6495                    }
6496                }
6497                continue;
6498            }
6499            let candidates =
6500                self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6501            let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6502                Ok(unit) => unit,
6503                Err(failure) => return failure.lexical_resolution(),
6504            };
6505            return LexicalTypeResolution::Resolved {
6506                unit,
6507                components: qualified,
6508                candidates: candidates.into_iter().cloned().collect(),
6509            };
6510        }
6511        LexicalTypeResolution::Missing
6512    }
6513
6514    fn resolve_injected_class_name(
6515        &self,
6516        analyzer: &CppGraphSource<'_>,
6517        file: &ProjectFile,
6518        components: &[String],
6519        global: bool,
6520        lexical_scope: &[String],
6521        resolution: TypeCandidateResolution<'_>,
6522    ) -> Option<(usize, LexicalTypeResolution)> {
6523        if global
6524            || components.len() != 1
6525            || file.rel_path().extension().is_some_and(|ext| ext == "c")
6526            || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
6527        {
6528            return None;
6529        }
6530        let name = components.first()?;
6531        let mut matches: Vec<&CodeUnit> = Vec::new();
6532        let mut owner_len = 0;
6533        for candidate in self.visible_identifier_candidates(file, name) {
6534            if !candidate.is_class()
6535                || declared_type_alias(analyzer, candidate)
6536                || candidate.identifier() != name
6537            {
6538                continue;
6539            }
6540            let candidate_scope = canonical_cpp_scope_components(candidate);
6541            if candidate_scope.len() > lexical_scope.len()
6542                || !lexical_scope.starts_with(&candidate_scope)
6543                || candidate_scope.last().is_none_or(|last| last != name)
6544            {
6545                continue;
6546            }
6547            if candidate_scope.len() > owner_len {
6548                owner_len = candidate_scope.len();
6549                matches.clear();
6550            }
6551            if candidate_scope.len() == owner_len
6552                && !matches
6553                    .iter()
6554                    .any(|existing| same_logical_symbol(existing, candidate))
6555            {
6556                matches.push(candidate);
6557            }
6558        }
6559        if matches.is_empty() {
6560            return None;
6561        }
6562        // The exact current class boundary is the most important injected-name
6563        // case. Ordinary lexical lookup appends the requested name and can
6564        // otherwise select a synthetic constructor-shaped child such as
6565        // `Portal::Impl::Impl` instead of the enclosing `Portal::Impl` class.
6566        // The global and single-component gates above keep qualified receiver
6567        // and static-qualifier contexts out of this recovery.
6568        let owner_components = lexical_scope[..owner_len].to_vec();
6569        let matches = self.candidates_for_type_resolution(analyzer, file, &matches, resolution);
6570        let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
6571            Ok(unit) => LexicalTypeResolution::Resolved {
6572                unit,
6573                components: owner_components,
6574                candidates: matches.into_iter().cloned().collect(),
6575            },
6576            Err(failure) => failure.lexical_resolution(),
6577        };
6578        Some((owner_len, resolution))
6579    }
6580
6581    fn resolve_inherited_type_for_lexical_scope(
6582        &self,
6583        analyzer: &CppGraphSource<'_>,
6584        file: &ProjectFile,
6585        lexical_scope: &[String],
6586        name: &str,
6587        resolution: TypeCandidateResolution<'_>,
6588    ) -> LexicalTypeResolution {
6589        let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
6590            return LexicalTypeResolution::Missing;
6591        };
6592        let lexical_owner_name = lexical_scope.join("::");
6593        if lexical_owner_name.is_empty() {
6594            return LexicalTypeResolution::Missing;
6595        }
6596        let owner_candidates = self
6597            .type_candidates(file, &lexical_owner_name)
6598            .into_iter()
6599            .filter(|candidate| {
6600                canonical_cpp_name_matches(candidate, &lexical_owner_name)
6601                    && !declared_type_alias(analyzer, candidate)
6602            })
6603            .collect::<Vec<_>>();
6604        if owner_candidates.is_empty() {
6605            return LexicalTypeResolution::Missing;
6606        }
6607        // A visible forward declaration and the physical class definition share
6608        // one FQN, but only the definition owns hierarchy facts. When lookup is
6609        // physically inside that definition, do not let an earlier header
6610        // forward declaration erase its base edges (#2240).
6611        let physical_owner_candidates = owner_candidates
6612            .iter()
6613            .copied()
6614            .filter(|candidate| candidate.source() == file)
6615            .collect::<Vec<_>>();
6616        let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
6617            owner_candidates
6618        } else {
6619            physical_owner_candidates
6620        };
6621        let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
6622            return LexicalTypeResolution::Ambiguous;
6623        };
6624
6625        let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
6626        let mut visited_owners = HashSet::default();
6627        while !frontier.is_empty() {
6628            let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
6629            let mut next_frontier = Vec::new();
6630            for owner in frontier {
6631                if !visited_owners.insert(owner.fq_name()) {
6632                    continue;
6633                }
6634                let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
6635                let candidates = self
6636                    .type_candidates(file, &qualified_name)
6637                    .into_iter()
6638                    .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6639                    .collect::<Vec<_>>();
6640                if candidates.is_empty() {
6641                    for ancestor in hierarchy.get_direct_ancestors(&owner) {
6642                        if !next_frontier
6643                            .iter()
6644                            .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
6645                        {
6646                            next_frontier.push(ancestor);
6647                        }
6648                    }
6649                    continue;
6650                }
6651                let candidates =
6652                    self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6653                let unit =
6654                    match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6655                        Ok(unit) => unit,
6656                        Err(failure) => return failure.lexical_resolution(),
6657                    };
6658                level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
6659            }
6660            if let Some((unit, candidates)) = level_matches.first().cloned() {
6661                let Some(first_declaration) = candidates.first() else {
6662                    return LexicalTypeResolution::Ambiguous;
6663                };
6664                if !level_matches.iter().all(|(_, declarations)| {
6665                    declarations
6666                        .iter()
6667                        .all(|declaration| same_logical_symbol(first_declaration, declaration))
6668                }) {
6669                    return LexicalTypeResolution::Ambiguous;
6670                }
6671                let mut components = lexical_scope.to_vec();
6672                components.push(name.to_string());
6673                return LexicalTypeResolution::Resolved {
6674                    unit,
6675                    components,
6676                    candidates,
6677                };
6678            }
6679            frontier = next_frontier;
6680        }
6681        LexicalTypeResolution::Missing
6682    }
6683
6684    /// Resolve a base class through its injected class name at the nearest
6685    /// inheritance tier. Distinct same-named bases at that tier are ambiguous.
6686    ///
6687    /// A base whose canonical full definition cannot be pinned from `file` -
6688    /// a forward declaration the include closure completes with two different
6689    /// full definitions, or an alias chain that leaves the index - stops the
6690    /// walk only when that base is spelled `injected_name`. The mem-initializer
6691    /// names a base by that base's own injected class name, so a base spelled
6692    /// differently can never be the one it names, whichever definition it would
6693    /// have turned out to be; aborting the level on its account instead loses
6694    /// the sibling base that *is* named (#2543). A base that is spelled
6695    /// `injected_name` still fails closed, because choosing a deeper same-named
6696    /// ancestor over it would bind the initializer to the wrong constructor.
6697    /// The skipped base carries its own ancestors out of the walk with it: with
6698    /// no canonical unit, the repeated-base accounting below cannot tell one
6699    /// inherited path through it from two.
6700    pub fn inherited_injected_class_owner(
6701        &self,
6702        analyzer: &CppGraphSource<'_>,
6703        file: &ProjectFile,
6704        enclosing_owner: &CodeUnit,
6705        injected_name: &str,
6706    ) -> Option<CodeUnit> {
6707        let hierarchy = analyzer.type_hierarchy_provider()?;
6708        let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
6709        let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
6710        while !frontier.is_empty() {
6711            let mut level_matches = Vec::new();
6712            let mut next_frontier = Vec::new();
6713            for raw_owner in frontier {
6714                let Some(owner) = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
6715                else {
6716                    if raw_owner.identifier() == injected_name {
6717                        return None;
6718                    }
6719                    continue;
6720                };
6721                let propagated = propagated_counts.entry(owner.clone()).or_default();
6722                if *propagated == 2 {
6723                    continue;
6724                }
6725                *propagated += 1;
6726                if owner.identifier() == injected_name {
6727                    level_matches.push(owner.clone());
6728                }
6729                next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
6730            }
6731            match level_matches.as_slice() {
6732                [owner] => return Some(owner.clone()),
6733                [_, ..] => return None,
6734                [] => {}
6735            }
6736            frontier = next_frontier;
6737        }
6738        None
6739    }
6740
6741    /// The one type the candidates name under `resolution`, or why they do not
6742    /// name one. The two preserving modes only ever reject candidates that
6743    /// disagree with each other, which is ambiguity; canonicalization can also
6744    /// fail because the alias chain leaves the index (#1828).
6745    fn resolve_type_candidates(
6746        &self,
6747        analyzer: &CppGraphSource<'_>,
6748        file: &ProjectFile,
6749        candidates: &[&CodeUnit],
6750        resolution: TypeCandidateResolution<'_>,
6751    ) -> Result<CodeUnit, TypeCandidateFailure> {
6752        if !matches!(resolution, TypeCandidateResolution::PreserveTarget(_))
6753            && let Some(unit) = self.unique_c_tag_declaration_family(analyzer, file, candidates)
6754        {
6755            return Ok(unit);
6756        }
6757        match resolution {
6758            TypeCandidateResolution::Canonical => {
6759                self.canonical_type_candidate_resolution(analyzer, file, candidates)
6760            }
6761            TypeCandidateResolution::PreserveAlias => {
6762                // A generated index can retain identical alias spellings from
6763                // mutually exclusive headers. When the reference file
6764                // physically reaches exactly one of those source declarations,
6765                // include closure is the structured evidence that selects it;
6766                // treating the two source spellings as an overload set makes a
6767                // reachable alias appear ambiguous (#1844).
6768                let same_fqn_alias_family = candidates.len() > 1
6769                    && candidates.iter().all(|candidate| {
6770                        declared_type_alias(analyzer, candidate)
6771                            && same_logical_symbol(candidates[0], candidate)
6772                    })
6773                    && candidates
6774                        .iter()
6775                        .any(|candidate| candidate.source() != candidates[0].source());
6776                if same_fqn_alias_family {
6777                    let physically_visible = candidates
6778                        .iter()
6779                        .copied()
6780                        .filter(|candidate| self.is_physically_visible(file, candidate))
6781                        .collect::<Vec<_>>();
6782                    // The family is one logical declaration only when the
6783                    // reachable spellings agree. Two same-FQN aliases whose
6784                    // written targets differ (`using Choice = Canonical;` in
6785                    // one header, `using Choice = ::Canonical;` in another)
6786                    // are a genuine conflict, and choosing the first indexed
6787                    // one silently binds the reference to an arbitrary owner
6788                    // (#2398). Collapse only a single reachable declaration
6789                    // or reachable declarations with one structured target;
6790                    // everything else stays ambiguous below.
6791                    let one_structured_target = physically_visible.len() > 1
6792                        && physically_visible.iter().skip(1).all(|candidate| {
6793                            let target = self.structured_alias_target(analyzer, candidate);
6794                            target.is_some()
6795                                && target
6796                                    == self.structured_alias_target(analyzer, physically_visible[0])
6797                        });
6798                    if physically_visible.len() == 1 || one_structured_target {
6799                        return Ok(physically_visible[0].clone());
6800                    }
6801                }
6802                unique_type_candidate_preserving_alias(analyzer, file, candidates)
6803                    .ok_or(TypeCandidateFailure::Ambiguous)
6804            }
6805            TypeCandidateResolution::PreserveTarget(target) => self
6806                .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
6807                .ok_or(TypeCandidateFailure::Ambiguous),
6808        }
6809    }
6810
6811    fn candidates_for_type_resolution<'b>(
6812        &self,
6813        analyzer: &CppGraphSource<'_>,
6814        file: &ProjectFile,
6815        candidates: &[&'b CodeUnit],
6816        resolution: TypeCandidateResolution<'_>,
6817    ) -> Vec<&'b CodeUnit> {
6818        if matches!(resolution, TypeCandidateResolution::PreserveAlias) && candidates.len() > 1 {
6819            let compile_proven = self.compile_proven_type_candidates(analyzer, file, candidates);
6820            if compile_proven.len() == 1 {
6821                return compile_proven;
6822            }
6823        }
6824        candidates.to_vec()
6825    }
6826
6827    /// Narrow a same-name forward lookup to the declaration selected by the
6828    /// translation unit's compile command. The lexical resolver intentionally
6829    /// does not receive a reference node, so this is the only compile-context
6830    /// evidence available at that stage. A single selected candidate is safe:
6831    /// the caller still checks include activation and the reference's own
6832    /// guards before reporting the result as visible.
6833    fn compile_proven_type_candidates<'b>(
6834        &self,
6835        analyzer: &CppGraphSource<'_>,
6836        file: &ProjectFile,
6837        candidates: &[&'b CodeUnit],
6838    ) -> Vec<&'b CodeUnit> {
6839        let proven = self.compile_proven_guards(file);
6840        if proven.is_empty() {
6841            return Vec::new();
6842        }
6843        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6844            return Vec::new();
6845        };
6846        candidates
6847            .iter()
6848            .copied()
6849            .filter(|candidate| {
6850                let declaration_guards =
6851                    declaration_guard_requirements(analyzer, self.cpp, candidate);
6852                if declaration_guards.is_empty() {
6853                    return false;
6854                }
6855                if candidate.source() == file {
6856                    return declaration_guards.iter().any(|(_, required)| {
6857                        guard_requirements_hold_at_reference(required, Some(proven.as_ref()))
6858                    });
6859                }
6860                declaration_guards.iter().any(|(_, required)| {
6861                    self.foreign_declaration_reachable_from_compile_proven_guards(
6862                        file,
6863                        prepared.as_ref(),
6864                        candidate.source(),
6865                        required,
6866                        usize::MAX,
6867                    )
6868                })
6869            })
6870            .collect()
6871    }
6872
6873    pub fn resolve_callable_value_components_lexically(
6874        &self,
6875        analyzer: &CppGraphSource<'_>,
6876        file: &ProjectFile,
6877        owner_components: &[String],
6878        member_name: &str,
6879        global: bool,
6880        lexical_scope: &[String],
6881    ) -> LexicalCallableValueResolution {
6882        if owner_components.is_empty() || member_name.is_empty() {
6883            return LexicalCallableValueResolution::Missing;
6884        }
6885        for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
6886            let owner_name = qualified_owner.join("::");
6887            let type_candidates = self
6888                .type_candidates(file, &owner_name)
6889                .into_iter()
6890                .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
6891                .collect::<Vec<_>>();
6892            let resolved_type = if type_candidates.is_empty() {
6893                None
6894            } else {
6895                let Some(unit) =
6896                    self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
6897                else {
6898                    return LexicalCallableValueResolution::Ambiguous;
6899                };
6900                Some(unit)
6901            };
6902
6903            let mut qualified_callable = qualified_owner;
6904            qualified_callable.push(member_name.to_string());
6905            let callable_name = qualified_callable.join("::");
6906            let free_function = self
6907                .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
6908                .into_iter()
6909                .find(|candidate| {
6910                    canonical_cpp_name_matches(candidate, &callable_name)
6911                        && type_owner_of(analyzer, candidate).is_none()
6912                })
6913                .cloned();
6914
6915            match (resolved_type, free_function) {
6916                (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
6917                (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
6918                (None, Some(function)) => {
6919                    return LexicalCallableValueResolution::FreeFunction(function);
6920                }
6921                (None, None) => {}
6922            }
6923        }
6924        LexicalCallableValueResolution::Missing
6925    }
6926
6927    fn resolve_type_for_declaration(
6928        &self,
6929        visible_from: &ProjectFile,
6930        declaration: &CodeUnit,
6931        raw_name: &str,
6932    ) -> Option<CodeUnit> {
6933        let normalized = normalize_reference_name(raw_name)?;
6934        if !normalized.contains("::")
6935            && let Some(namespace) = cpp_namespace_for(declaration)
6936        {
6937            for prefix in namespace_prefixes(&namespace) {
6938                let qualified = format!("{prefix}::{normalized}");
6939                if let Some(unit) = self
6940                    .type_candidates(visible_from, &qualified)
6941                    .into_iter()
6942                    .next()
6943                {
6944                    return Some(unit.clone());
6945                }
6946            }
6947        }
6948        self.resolve_type(visible_from, raw_name)
6949    }
6950
6951    fn resolve_unique_canonical_type_for_declaration(
6952        &self,
6953        analyzer: &CppGraphSource<'_>,
6954        visible_from: &ProjectFile,
6955        declaration: &CodeUnit,
6956        raw_name: &str,
6957    ) -> Option<CodeUnit> {
6958        let mut current = self.resolve_defining_type_for_declaration(
6959            analyzer,
6960            visible_from,
6961            declaration,
6962            raw_name,
6963        )?;
6964        let mut seen_aliases = HashSet::default();
6965        loop {
6966            let Some(target) = self.structured_alias_target(analyzer, &current) else {
6967                return current.is_class().then_some(current);
6968            };
6969            if matches!(target, StructuredAliasTarget::Builtin) {
6970                return current.is_class().then_some(current);
6971            }
6972            if !seen_aliases.insert(current.clone()) {
6973                return None;
6974            }
6975            current = self.resolve_structured_alias_target(visible_from, &current, &target)?;
6976        }
6977    }
6978
6979    pub fn canonical_type_unit(
6980        &self,
6981        analyzer: &CppGraphSource<'_>,
6982        visible_from: &ProjectFile,
6983        unit: &CodeUnit,
6984    ) -> Option<CodeUnit> {
6985        self.canonical_type_resolution(analyzer, visible_from, unit)
6986            .ok()
6987    }
6988
6989    /// Follow an alias only when it is visible at `reference`.
6990    ///
6991    /// The consumer need not spell the alias target. In particular, a
6992    /// conditional include can make `PublicPtr` visible at the reference while
6993    /// ordinary physical-include visibility is false. Prove the public alias
6994    /// with the reference's guard environment, then use the existing structured
6995    /// alias-chain resolver over the consumer's bounded declaration index.
6996    pub fn canonical_type_unit_in_context(
6997        &self,
6998        analyzer: &CppGraphSource<'_>,
6999        visible_from: &ProjectFile,
7000        reference: Node<'_>,
7001        unit: &CodeUnit,
7002    ) -> Option<CodeUnit> {
7003        if !self.external_type_candidate_visible_in_context(analyzer, visible_from, unit, reference)
7004        {
7005            return None;
7006        }
7007        self.canonical_type_resolution(analyzer, visible_from, unit)
7008            .ok()
7009    }
7010
7011    /// Follow `unit`'s alias chain to the class it names, or report why the
7012    /// chain does not end at one indexed class.
7013    ///
7014    /// A chain that leaves the index - an alias to a template parameter, to a
7015    /// standard-library type, or to any other declaration the workspace does
7016    /// not hold - is `Unresolvable`, not `Ambiguous` (#1828). So is a cycle:
7017    /// there is still nothing to choose between.
7018    fn canonical_type_resolution(
7019        &self,
7020        analyzer: &CppGraphSource<'_>,
7021        visible_from: &ProjectFile,
7022        unit: &CodeUnit,
7023    ) -> Result<CodeUnit, TypeCandidateFailure> {
7024        let mut current = unit.clone();
7025        let mut seen_aliases = HashSet::default();
7026        loop {
7027            let Some(target) = self.structured_alias_target(analyzer, &current) else {
7028                return current
7029                    .is_class()
7030                    .then_some(current)
7031                    .ok_or(TypeCandidateFailure::Unresolvable);
7032            };
7033            if matches!(target, StructuredAliasTarget::Builtin) {
7034                return current
7035                    .is_class()
7036                    .then_some(current)
7037                    .ok_or(TypeCandidateFailure::Unresolvable);
7038            }
7039            if !seen_aliases.insert(current.clone()) {
7040                return Err(TypeCandidateFailure::Unresolvable);
7041            }
7042            current = self.structured_alias_target_resolution(visible_from, &current, &target)?;
7043        }
7044    }
7045
7046    pub fn canonical_visible_full_type_unit(
7047        &self,
7048        analyzer: &CppGraphSource<'_>,
7049        visible_from: &ProjectFile,
7050        unit: &CodeUnit,
7051    ) -> Option<CodeUnit> {
7052        let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
7053        if cpp_class_declaration_strength(analyzer, &canonical)
7054            != CppClassDeclarationStrength::Forward
7055        {
7056            return Some(canonical);
7057        }
7058        let mut full = Vec::new();
7059        for candidate in self
7060            .visible_identifier_candidates(visible_from, canonical.identifier())
7061            .filter(|candidate| {
7062                candidate.is_class()
7063                    && candidate.fq_name() == canonical.fq_name()
7064                    && cpp_class_declaration_strength(analyzer, candidate)
7065                        == CppClassDeclarationStrength::Full
7066            })
7067        {
7068            if !full.iter().any(|existing| same_symbol(existing, candidate)) {
7069                full.push(candidate.clone());
7070            }
7071        }
7072        match full.len() {
7073            0 => Some(canonical),
7074            1 => full.pop(),
7075            _ => None,
7076        }
7077    }
7078
7079    fn resolve_structured_alias_target(
7080        &self,
7081        visible_from: &ProjectFile,
7082        declaration: &CodeUnit,
7083        target: &StructuredAliasTarget,
7084    ) -> Option<CodeUnit> {
7085        self.structured_alias_target_resolution(visible_from, declaration, target)
7086            .ok()
7087    }
7088
7089    fn structured_alias_target_resolution(
7090        &self,
7091        visible_from: &ProjectFile,
7092        declaration: &CodeUnit,
7093        target: &StructuredAliasTarget,
7094    ) -> Result<CodeUnit, TypeCandidateFailure> {
7095        let primary =
7096            self.structured_alias_primary_resolution(visible_from, declaration, target)?;
7097        let StructuredAliasTarget::Named { arguments, .. } = target else {
7098            return Err(TypeCandidateFailure::Unresolvable);
7099        };
7100        match arguments {
7101            Some(arguments) => self
7102                .resolve_template_arguments(visible_from, primary, arguments)
7103                .map_err(|error| match error {
7104                    CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
7105                        TypeCandidateFailure::Ambiguous
7106                    }
7107                    _ => TypeCandidateFailure::Unresolvable,
7108                }),
7109            None => Ok(primary),
7110        }
7111    }
7112
7113    fn resolve_structured_alias_primary(
7114        &self,
7115        visible_from: &ProjectFile,
7116        declaration: &CodeUnit,
7117        target: &StructuredAliasTarget,
7118    ) -> Option<CodeUnit> {
7119        self.structured_alias_primary_resolution(visible_from, declaration, target)
7120            .ok()
7121    }
7122
7123    fn structured_alias_primary_resolution(
7124        &self,
7125        visible_from: &ProjectFile,
7126        declaration: &CodeUnit,
7127        target: &StructuredAliasTarget,
7128    ) -> Result<CodeUnit, TypeCandidateFailure> {
7129        let StructuredAliasTarget::Named {
7130            components, global, ..
7131        } = target
7132        else {
7133            return Err(TypeCandidateFailure::Unresolvable);
7134        };
7135        let qualified = components.join("::");
7136        let candidates = if *global {
7137            // `::A::B` anchors at the root scope, so a candidate whose
7138            // canonical path merely ends with the spelled components does not
7139            // qualify. Without this filter a global `::Canonical` target also
7140            // collects `alpha::Canonical`, the lookup reports a false
7141            // ambiguity, and the alias arm silently drops out of its
7142            // conflicting family instead of proving the conflict (#2398).
7143            let mut candidates = self.type_candidates(visible_from, &qualified);
7144            candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
7145            candidates
7146        } else {
7147            self.type_candidates_for_declaration(visible_from, declaration, &qualified)
7148        };
7149        logical_type_candidate(candidates)
7150    }
7151
7152    pub fn structured_alias_primary_preserves_target(
7153        &self,
7154        analyzer: &CppGraphSource<'_>,
7155        visible_from: &ProjectFile,
7156        candidate: &CodeUnit,
7157        target: &CodeUnit,
7158    ) -> bool {
7159        let mut current = candidate.clone();
7160        let mut seen = HashSet::default();
7161        let mut matched_target = false;
7162        loop {
7163            if same_visible_symbol(&current, target)
7164                || self.compatible_primary_template_redeclarations(&current, target)
7165            {
7166                matched_target = true;
7167            }
7168            if !seen.insert(current.clone()) {
7169                return false;
7170            }
7171            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
7172                return matched_target;
7173            };
7174            if matches!(alias_target, StructuredAliasTarget::Builtin) {
7175                return matched_target;
7176            };
7177            let Some(primary) =
7178                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
7179            else {
7180                // A dependent member target such as `Detector<T>::type`
7181                // cannot be reduced to an indexed primary, but a preceding
7182                // structured alias hop may already have proven the requested
7183                // alias identity. Cycles still resolve a primary and are
7184                // rejected by `seen` above.
7185                return matched_target;
7186            };
7187            current = primary;
7188        }
7189    }
7190
7191    pub fn structured_class_alias_resolves_to_target(
7192        &self,
7193        analyzer: &CppGraphSource<'_>,
7194        visible_from: &ProjectFile,
7195        alias: &CodeUnit,
7196        target: &CodeUnit,
7197    ) -> bool {
7198        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7199            return false;
7200        };
7201        let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
7202            return false;
7203        };
7204        let StructuredAliasTarget::Named {
7205            components, global, ..
7206        } = &alias_target
7207        else {
7208            return false;
7209        };
7210        let lexical_scope = canonical_cpp_scope_components(&owner);
7211        match self.resolve_type_components_lexically_for_target(
7212            analyzer,
7213            visible_from,
7214            components,
7215            *global,
7216            &lexical_scope,
7217            target,
7218        ) {
7219            LexicalTypeResolution::Resolved {
7220                unit, candidates, ..
7221            } => {
7222                same_visible_symbol(&unit, target)
7223                    || self.same_template_member_identity(analyzer, &unit, target)
7224                    || candidates.iter().any(|candidate| {
7225                        same_visible_symbol(candidate, target)
7226                            || self.same_template_member_identity(analyzer, candidate, target)
7227                    })
7228            }
7229            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7230                self.structured_alias_primary_preserves_target(
7231                    analyzer,
7232                    visible_from,
7233                    alias,
7234                    target,
7235                ) || self.flattened_macro_namespace_alias_target_matches(
7236                    analyzer,
7237                    visible_from,
7238                    alias,
7239                    &alias_target,
7240                    target,
7241                )
7242            }
7243        }
7244    }
7245
7246    /// Return true when a class-owned alias names the requested type as one
7247    /// structured qualifier in its target path.
7248    ///
7249    /// A dependent target such as `Primary<T>::Type` cannot resolve to one
7250    /// indexed class. Forward lookup can still retain `Primary` as its bounded
7251    /// canonical identity. Inverse lookup needs the same evidence when later
7252    /// references use only the alias spelling.
7253    pub fn structured_class_alias_path_preserves_target(
7254        &self,
7255        analyzer: &CppGraphSource<'_>,
7256        visible_from: &ProjectFile,
7257        alias: &CodeUnit,
7258        target: &CodeUnit,
7259    ) -> bool {
7260        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7261            return false;
7262        };
7263        let Some(StructuredAliasTarget::Named {
7264            components, global, ..
7265        }) = self.structured_alias_target(analyzer, alias)
7266        else {
7267            return false;
7268        };
7269        let lexical_scope = canonical_cpp_scope_components(&owner);
7270        (1..components.len()).rev().any(|component_count| {
7271            matches!(
7272                self.resolve_type_components_lexically_for_target(
7273                    analyzer,
7274                    visible_from,
7275                    &components[..component_count],
7276                    global,
7277                    &lexical_scope,
7278                    target,
7279                ),
7280                LexicalTypeResolution::Resolved {
7281                    ref unit,
7282                    ref candidates,
7283                    ..
7284                } if same_visible_symbol(unit, target)
7285                    || self.same_template_member_identity(analyzer, unit, target)
7286                    || candidates.iter().any(|candidate| {
7287                        same_visible_symbol(candidate, target)
7288                            || self.same_template_member_identity(analyzer, candidate, target)
7289                    })
7290            )
7291        })
7292    }
7293
7294    fn flattened_macro_namespace_alias_target_matches(
7295        &self,
7296        analyzer: &CppGraphSource<'_>,
7297        visible_from: &ProjectFile,
7298        alias: &CodeUnit,
7299        alias_target: &StructuredAliasTarget,
7300        target: &CodeUnit,
7301    ) -> bool {
7302        let StructuredAliasTarget::Named {
7303            components,
7304            global: false,
7305            arguments: None,
7306        } = alias_target
7307        else {
7308            return false;
7309        };
7310        let Some((target_name, namespace_components)) = components.split_last() else {
7311            return false;
7312        };
7313        if namespace_components.is_empty()
7314            || target_name != target.identifier()
7315            || alias.source() != target.source()
7316            || alias.source() != visible_from
7317            || !target.is_class()
7318            || declared_type_alias(analyzer, target)
7319        {
7320            return false;
7321        }
7322        if self
7323            .resolve_structured_alias_target(visible_from, alias, alias_target)
7324            .is_some()
7325        {
7326            return false;
7327        }
7328
7329        let Some(alias_start) = analyzer
7330            .declaration_source_occurrences(alias)
7331            .into_iter()
7332            .flatten()
7333            .map(|occurrence| occurrence.range.start_byte)
7334            .min()
7335        else {
7336            return false;
7337        };
7338        let has_matching_declaration = analyzer
7339            .declaration_source_occurrences(target)
7340            .into_iter()
7341            .flatten()
7342            .any(|occurrence| {
7343                occurrence.range.end_byte <= alias_start
7344                    && occurrence.fact.flattened_macro_namespace.as_deref()
7345                        == Some(namespace_components)
7346            });
7347        if !has_matching_declaration {
7348            return false;
7349        }
7350
7351        let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
7352        let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
7353        guard_requirement_sets_match(&alias_guards, &target_guards)
7354    }
7355
7356    pub fn template_alias_arguments_preserve_target(
7357        &self,
7358        analyzer: &CppGraphSource<'_>,
7359        visible_from: &ProjectFile,
7360        alias: &CodeUnit,
7361        arguments: &[CppTemplateExpression],
7362        target: &CodeUnit,
7363    ) -> bool {
7364        let Some(metadata) = self.cpp_template_metadata.get(alias) else {
7365            return false;
7366        };
7367        if metadata.alias_target.is_none()
7368            || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
7369        {
7370            return false;
7371        }
7372        self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
7373    }
7374
7375    pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
7376        self.cpp_template_metadata
7377            .get(unit)
7378            .is_some_and(CppTemplateMetadata::is_primary)
7379    }
7380
7381    pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
7382        self.cpp_template_metadata
7383            .get(unit)
7384            .is_some_and(CppTemplateMetadata::is_specialization)
7385    }
7386
7387    pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
7388        same_visible_symbol(left, right)
7389            || self.compatible_primary_template_redeclarations(left, right)
7390    }
7391
7392    pub fn same_template_member_identity(
7393        &self,
7394        analyzer: &CppGraphSource<'_>,
7395        left: &CodeUnit,
7396        right: &CodeUnit,
7397    ) -> bool {
7398        if same_visible_symbol(left, right) {
7399            return true;
7400        }
7401        if left.kind() != right.kind()
7402            || left.identifier() != right.identifier()
7403            || left.signature() != right.signature()
7404        {
7405            return false;
7406        }
7407        let (Some(left_owner), Some(right_owner)) =
7408            (analyzer.parent_of(left), analyzer.parent_of(right))
7409        else {
7410            return false;
7411        };
7412        left_owner.is_class()
7413            && right_owner.is_class()
7414            && self.same_template_owner_identity(&left_owner, &right_owner)
7415    }
7416
7417    fn unique_canonical_type_candidate(
7418        &self,
7419        analyzer: &CppGraphSource<'_>,
7420        visible_from: &ProjectFile,
7421        candidates: &[&CodeUnit],
7422    ) -> Option<CodeUnit> {
7423        self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
7424            .ok()
7425    }
7426
7427    fn canonical_type_candidate_resolution(
7428        &self,
7429        analyzer: &CppGraphSource<'_>,
7430        visible_from: &ProjectFile,
7431        candidates: &[&CodeUnit],
7432    ) -> Result<CodeUnit, TypeCandidateFailure> {
7433        let mut canonical = Vec::new();
7434        for candidate in candidates {
7435            let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
7436            if canonical
7437                .iter()
7438                .any(|existing| same_visible_symbol(existing, &resolved))
7439            {
7440                continue;
7441            }
7442            if let Some(existing) = canonical.iter_mut().find(|existing| {
7443                self.compatible_primary_template_redeclarations(existing, &resolved)
7444            }) {
7445                // A forward declaration and its full primary-template
7446                // definition are one C++ type even when they live in
7447                // different headers and alpha-rename their parameters. The
7448                // target-preserving path already reconciles this family; do
7449                // the same for ordinary canonical lookup so an out-of-line
7450                // member's lexical owner is not made ambiguous by its own
7451                // forward declaration. Retain the strongest physical
7452                // declaration for later owner/range queries.
7453                if matches!(
7454                    (
7455                        cpp_class_declaration_strength(analyzer, existing),
7456                        cpp_class_declaration_strength(analyzer, &resolved),
7457                    ),
7458                    (
7459                        CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
7460                        CppClassDeclarationStrength::Full,
7461                    ) | (
7462                        CppClassDeclarationStrength::Unknown,
7463                        CppClassDeclarationStrength::Forward,
7464                    )
7465                ) {
7466                    *existing = resolved;
7467                }
7468                continue;
7469            }
7470            canonical.push(resolved);
7471            if canonical.len() > 1 {
7472                return Err(TypeCandidateFailure::Ambiguous);
7473            }
7474        }
7475        canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
7476    }
7477
7478    pub fn unique_type_candidate_preserving_target(
7479        &self,
7480        analyzer: &CppGraphSource<'_>,
7481        visible_from: &ProjectFile,
7482        candidates: &[&CodeUnit],
7483        target: &CodeUnit,
7484    ) -> Option<CodeUnit> {
7485        // C++ headers often expose one logical type through mutually exclusive
7486        // physical declarations, for example a class in the fallback branch
7487        // and a `using` alias to the standard-library type in the configured
7488        // branch. The index intentionally retains both declarations so forward
7489        // lookup can report each target. Preserve the requested target when
7490        // that is the only ambiguity: every candidate has the same type kind,
7491        // exact canonical FQN, and source file, and the requested declaration
7492        // itself is one of the physical candidates. Do not merge same-named
7493        // declarations from different files or namespaces; those remain
7494        // ambiguous and fail closed below.
7495        if self.c_tag_declaration_family_matches_target(analyzer, visible_from, candidates, target)
7496            || self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7497        {
7498            return Some(target.clone());
7499        }
7500        let mut resolved_candidates = Vec::new();
7501        for candidate in candidates {
7502            // An ifdef branch that aliases an unindexed system type (for
7503            // example `typedef pthread_mutex_t k5_os_mutex`) cannot be
7504            // canonicalized. That branch does not name `target`. Dropping it
7505            // keeps the branch that does. Failing the whole family here would
7506            // deny every usage of the reachable spelling (#2368).
7507            let Some(resolved) =
7508                self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7509            else {
7510                continue;
7511            };
7512            if resolved_candidates
7513                .iter()
7514                .any(|existing| same_visible_symbol(existing, &resolved))
7515            {
7516                continue;
7517            }
7518            resolved_candidates.push(resolved);
7519        }
7520        match resolved_candidates.as_slice() {
7521            [] => None,
7522            [single] => Some(single.clone()),
7523            // The branches disagree about what the name aliases. When they are
7524            // spellings of one entity (#1845) that disagreement is a build
7525            // configuration, not a choice between types, so it must not deny
7526            // the requested target its reference.
7527            _ => self
7528                .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
7529                .map(|_| target.clone()),
7530        }
7531    }
7532
7533    /// The declaration a same-file same-FQN family stands for when a reference
7534    /// names `target`, or `None` when the candidates are not one family or the
7535    /// family does not name `target`.
7536    ///
7537    /// A translation unit cannot hold two different types under one qualified
7538    /// name, so several same-kind declarations of one FQN in one file are
7539    /// alternate spellings of one entity - the configuration branches of an
7540    /// `#if` family, for example log4cxx's `logchar`, which aliases `char` in
7541    /// the UTF-8 branch and `UniChar` in the unichar branch. Their alias
7542    /// targets differ; canonicalizing each branch on its own and then demanding
7543    /// agreement reports an ambiguity that denies every declaration in the
7544    /// family its usages (#1845). The family names `target` when it declares
7545    /// it, or when one branch's alias chain reaches it.
7546    ///
7547    /// Declarations in different files or namespaces are distinct entities and
7548    /// are deliberately excluded: their disagreement is a real ambiguity.
7549    pub fn same_fqn_type_spelling_for_target<'b>(
7550        &self,
7551        analyzer: &CppGraphSource<'_>,
7552        visible_from: &ProjectFile,
7553        candidates: &[&'b CodeUnit],
7554        target: &CodeUnit,
7555    ) -> Option<&'b CodeUnit> {
7556        let [first, rest @ ..] = candidates else {
7557            return None;
7558        };
7559        if rest.is_empty()
7560            || !rest.iter().all(|candidate| {
7561                candidate.kind() == first.kind()
7562                    && candidate.fq_name() == first.fq_name()
7563                    && candidate.source() == first.source()
7564            })
7565        {
7566            return None;
7567        }
7568        candidates
7569            .iter()
7570            .copied()
7571            .find(|candidate| same_symbol(candidate, target))
7572            .or_else(|| {
7573                candidates.iter().copied().find(|candidate| {
7574                    self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7575                        .is_some_and(|resolved| same_visible_symbol(&resolved, target))
7576                })
7577            })
7578    }
7579
7580    pub fn alternate_same_fqn_type_declarations(
7581        &self,
7582        analyzer: &CppGraphSource<'_>,
7583        candidates: &[&CodeUnit],
7584        target: &CodeUnit,
7585    ) -> bool {
7586        let Some(first) = candidates.first() else {
7587            return false;
7588        };
7589        let same_api = first.kind() == target.kind()
7590            && first.fq_name() == target.fq_name()
7591            && first.source() == target.source()
7592            && candidates.iter().all(|candidate| {
7593                candidate.kind() == target.kind()
7594                    && candidate.fq_name() == target.fq_name()
7595                    && candidate.source() == target.source()
7596            })
7597            && candidates
7598                .iter()
7599                .any(|candidate| same_symbol(candidate, target))
7600            && candidates
7601                .iter()
7602                .any(|candidate| !same_logical_symbol(candidate, target));
7603        if !same_api {
7604            return false;
7605        }
7606
7607        let requirements = candidates
7608            .iter()
7609            .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7610            .collect::<Vec<_>>();
7611        requirements.len() > 1
7612            && requirements
7613                .iter()
7614                .all(|requirement| !requirement.is_empty())
7615            && requirements.iter().enumerate().all(|(index, left)| {
7616                requirements[index + 1..].iter().all(|right| {
7617                    left.iter().all(|(_, left_guards)| {
7618                        right.iter().all(|(_, right_guards)| {
7619                            merge_preprocessor_guards(left_guards, right_guards).is_none()
7620                        })
7621                    })
7622                })
7623            })
7624    }
7625
7626    fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
7627        let mut pending = vec![terms.to_vec()];
7628        while let Some(branch_terms) = pending.pop() {
7629            let mut normalized = Vec::new();
7630            let mut covers_branch = false;
7631            for term in branch_terms {
7632                if term.iter().any(|guard| term.contains(&guard.negated())) {
7633                    continue;
7634                }
7635                if term.is_empty() {
7636                    covers_branch = true;
7637                    break;
7638                }
7639                if !normalized.iter().any(|existing| existing == &term) {
7640                    normalized.push(term);
7641                }
7642            }
7643            if covers_branch {
7644                continue;
7645            }
7646            let Some(split_guard) = normalized
7647                .iter()
7648                .flat_map(|term| term.iter())
7649                .next()
7650                .cloned()
7651            else {
7652                return false;
7653            };
7654            let negated_guard = split_guard.negated();
7655            let mut when_defined = Vec::new();
7656            let mut when_undefined = Vec::new();
7657            for term in normalized {
7658                if term.contains(&negated_guard) {
7659                    // This term cannot hold when `split_guard` is true.
7660                } else if term.contains(&split_guard) {
7661                    let mut reduced = term.clone();
7662                    reduced.remove(&split_guard);
7663                    when_defined.push(reduced);
7664                } else {
7665                    when_defined.push(term.clone());
7666                }
7667                if term.contains(&split_guard) {
7668                    // This term cannot hold when `split_guard` is false.
7669                } else if term.contains(&negated_guard) {
7670                    let mut reduced = term;
7671                    reduced.remove(&negated_guard);
7672                    when_undefined.push(reduced);
7673                } else {
7674                    when_undefined.push(term);
7675                }
7676            }
7677            pending.push(when_defined);
7678            pending.push(when_undefined);
7679        }
7680        true
7681    }
7682
7683    /// The byte range of the one `#if` family with a terminal `#else` that holds
7684    /// every physical declaration of every candidate, or `None` when they do not
7685    /// share one such family.
7686    ///
7687    /// Guard terms alone cannot distinguish one `#if` family from separate blocks
7688    /// whose macros changed between declarations. Require every physical range to
7689    /// belong to one syntax-tree family with a terminal `#else` before the terms
7690    /// can prove branch coverage.
7691    fn declarations_share_exhaustive_conditional_family(
7692        &self,
7693        analyzer: &CppGraphSource<'_>,
7694        candidates: &[&CodeUnit],
7695    ) -> Option<(usize, usize)> {
7696        let mut family_range = None;
7697        for candidate in candidates {
7698            let mut candidate_family = None;
7699            for occurrence in analyzer.declaration_source_occurrences(candidate)? {
7700                let key = occurrence.exhaustive_family?;
7701                if candidate_family.is_some_and(|existing| existing != key) {
7702                    return None;
7703                }
7704                candidate_family = Some(key);
7705            }
7706            let candidate_family = candidate_family?;
7707            if family_range.is_some_and(|existing| existing != candidate_family) {
7708                return None;
7709            }
7710            family_range = Some(candidate_family);
7711        }
7712        family_range
7713    }
7714
7715    pub fn complementary_same_fqn_type_declarations(
7716        &self,
7717        analyzer: &CppGraphSource<'_>,
7718        candidates: &[&CodeUnit],
7719        target: &CodeUnit,
7720    ) -> bool {
7721        if candidates.len() < 2
7722            || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7723            || self
7724                .declarations_share_exhaustive_conditional_family(analyzer, candidates)
7725                .is_none()
7726        {
7727            return false;
7728        }
7729        Self::preprocessor_guard_terms_cover_all_paths(
7730            &self.declaration_family_guard_terms(analyzer, candidates),
7731        )
7732    }
7733
7734    fn declaration_family_guard_terms(
7735        &self,
7736        analyzer: &CppGraphSource<'_>,
7737        candidates: &[&CodeUnit],
7738    ) -> Vec<HashSet<PreprocessorGuard>> {
7739        candidates
7740            .iter()
7741            .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7742            .map(|(_, guards)| guards)
7743            .collect()
7744    }
7745
7746    /// A callable name declared on every branch of one completed `#if`/`#else`
7747    /// family is declared on every configuration path, so a reference below the
7748    /// whole family sees one of the branches whatever the preprocessor decides.
7749    /// Answer the family's end byte: only past `#endif` is every branch's
7750    /// declaration behind the reference.
7751    ///
7752    /// This is the callable analogue of `complementary_same_fqn_type_declarations`
7753    /// and shares both of its primitives. It does not require two distinct
7754    /// `CodeUnit`s: branches that declare the same signature can collapse into
7755    /// one unit carrying one physical range per branch.
7756    ///
7757    /// The branches are alternate spellings of one declaration, never competing
7758    /// declarations, so only the first branch stands for the family. Reporting
7759    /// every branch as visible would turn a name the source declares exactly
7760    /// once into an ambiguity between build configurations.
7761    fn exhaustive_guard_family_activation(
7762        &self,
7763        analyzer: &CppGraphSource<'_>,
7764        candidate: &CodeUnit,
7765        reference: &CallableReferenceContext<'_>,
7766    ) -> Option<usize> {
7767        // Branch coverage says nothing about scope: a block-local declaration
7768        // stays invisible however many branches declare it.
7769        if !analyzer
7770            .declaration_source_occurrences(candidate)?
7771            .iter()
7772            .any(|occurrence| occurrence.fact.callable_activation.is_some())
7773        {
7774            return None;
7775        }
7776        let family = self
7777            .visible_identifier_candidates(candidate.source(), candidate.identifier())
7778            .filter(|peer| {
7779                peer.kind() == candidate.kind()
7780                    && peer.fq_name() == candidate.fq_name()
7781                    && peer.source() == candidate.source()
7782            })
7783            .collect::<Vec<_>>();
7784        let (_, family_end) =
7785            self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
7786        if !Self::preprocessor_guard_terms_cover_all_paths(
7787            &self.declaration_family_guard_terms(analyzer, &family),
7788        ) {
7789            return None;
7790        }
7791        // A reference whose own guards pick one branch already reaches that
7792        // branch through the ordinary same-guard path; the family must not
7793        // resurrect the branch the reference contradicts.
7794        if !declaration_guard_requirements(analyzer, self.cpp, candidate)
7795            .iter()
7796            .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
7797        {
7798            return None;
7799        }
7800        (first_declaration_byte(analyzer, candidate)?
7801            == family
7802                .iter()
7803                .filter_map(|peer| first_declaration_byte(analyzer, peer))
7804                .min()?)
7805        .then_some(family_end)
7806    }
7807
7808    fn type_candidate_preserving_target(
7809        &self,
7810        analyzer: &CppGraphSource<'_>,
7811        visible_from: &ProjectFile,
7812        candidate: &CodeUnit,
7813        target: &CodeUnit,
7814    ) -> Option<CodeUnit> {
7815        let mut current = candidate.clone();
7816        let mut matched_target = same_visible_symbol(&current, target)
7817            || self.compatible_primary_template_redeclarations(&current, target);
7818        let mut seen = HashSet::default();
7819        loop {
7820            if !seen.insert(current.clone()) {
7821                return None;
7822            }
7823            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
7824                return matched_target
7825                    .then(|| target.clone())
7826                    .or_else(|| current.is_class().then_some(current));
7827            };
7828            if self.flattened_macro_namespace_alias_target_matches(
7829                analyzer,
7830                visible_from,
7831                &current,
7832                &alias_target,
7833                target,
7834            ) {
7835                return Some(target.clone());
7836            }
7837            if matches!(alias_target, StructuredAliasTarget::Builtin) {
7838                return matched_target
7839                    .then(|| target.clone())
7840                    .or_else(|| current.is_class().then_some(current));
7841            }
7842            // A non-template alias can name a template alias with explicit
7843            // arguments (for example, `using Result = Expected<int>`).  When
7844            // the requested target is that alias's primary declaration, keep
7845            // the primary identity before expanding the RHS arguments.  The
7846            // expansion would otherwise canonicalize through the underlying
7847            // implementation type and lose the target spelling used by the
7848            // forward resolver.
7849            if !self.cpp_template_metadata.contains_key(&current)
7850                && let Some(primary) =
7851                    self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
7852                && (same_visible_symbol(&primary, target)
7853                    || self.compatible_primary_template_redeclarations(&primary, target))
7854            {
7855                return Some(target.clone());
7856            }
7857            if same_visible_symbol(&current, target) {
7858                return Some(target.clone());
7859            }
7860            if self.cpp_template_metadata.contains_key(&current) {
7861                return None;
7862            }
7863            let Some(next) =
7864                self.resolve_structured_alias_target(visible_from, &current, &alias_target)
7865            else {
7866                return matched_target.then(|| target.clone());
7867            };
7868            current = next;
7869            matched_target |= same_visible_symbol(&current, target)
7870                || self.compatible_primary_template_redeclarations(&current, target);
7871        }
7872    }
7873
7874    fn compatible_primary_template_redeclarations(
7875        &self,
7876        left: &CodeUnit,
7877        right: &CodeUnit,
7878    ) -> bool {
7879        let (Some(left_metadata), Some(right_metadata)) = (
7880            self.cpp_template_metadata.get(left),
7881            self.cpp_template_metadata.get(right),
7882        ) else {
7883            return false;
7884        };
7885        left_metadata.primary_fq_name == right_metadata.primary_fq_name
7886            && left_metadata.is_primary()
7887            && right_metadata.is_primary()
7888            && cpp_reconcile_primary_template_parameters(
7889                &[(left, left_metadata), (right, right_metadata)],
7890                right,
7891            )
7892            .is_some()
7893    }
7894
7895    fn alias_candidate_may_preserve_target(
7896        &self,
7897        analyzer: &CppGraphSource<'_>,
7898        visible_from: &ProjectFile,
7899        candidate: &CodeUnit,
7900        target: &CodeUnit,
7901    ) -> bool {
7902        let mut current = candidate.clone();
7903        let mut seen = HashSet::default();
7904        loop {
7905            if same_visible_symbol(&current, target)
7906                || self.compatible_primary_template_redeclarations(&current, target)
7907            {
7908                return true;
7909            }
7910            if self.cpp_template_metadata.contains_key(&current) {
7911                return true;
7912            }
7913            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
7914                return false;
7915            };
7916            let StructuredAliasTarget::Named {
7917                components,
7918                global,
7919                arguments,
7920            } = alias_target
7921            else {
7922                return false;
7923            };
7924            if arguments.is_some() || !seen.insert(current.clone()) {
7925                return true;
7926            }
7927            let qualified = components.join("::");
7928            let next = if global {
7929                unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
7930            } else {
7931                self.resolve_unique_type_for_declaration(visible_from, &current, &qualified)
7932            };
7933            let Some(next) = next else {
7934                return true;
7935            };
7936            current = next;
7937        }
7938    }
7939
7940    fn alias_candidate_structurally_reaches_target(
7941        &self,
7942        analyzer: &CppGraphSource<'_>,
7943        visible_from: &ProjectFile,
7944        candidate: &CodeUnit,
7945        target: &CodeUnit,
7946    ) -> bool {
7947        let mut current = candidate.clone();
7948        let mut seen = HashSet::default();
7949        loop {
7950            if same_visible_symbol(&current, target)
7951                || self.compatible_primary_template_redeclarations(&current, target)
7952                || self
7953                    .cpp_template_metadata
7954                    .get(&current)
7955                    .zip(self.cpp_template_metadata.get(target))
7956                    .is_some_and(|(current, target)| {
7957                        current.primary_fq_name == target.primary_fq_name
7958                    })
7959            {
7960                return true;
7961            }
7962            if !seen.insert(current.clone()) {
7963                return false;
7964            }
7965            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
7966                return false;
7967            };
7968            if let StructuredAliasTarget::Named { components, .. } = &alias_target
7969                && components[..components.len().saturating_sub(1)]
7970                    .iter()
7971                    .any(|component| {
7972                        self.type_reference_component_directly_names_target(component, target)
7973                    })
7974                && self.structured_class_alias_path_preserves_target(
7975                    analyzer,
7976                    visible_from,
7977                    &current,
7978                    target,
7979                )
7980            {
7981                return true;
7982            }
7983            if matches!(alias_target, StructuredAliasTarget::Builtin) {
7984                return false;
7985            }
7986            let Some(primary) =
7987                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
7988            else {
7989                return false;
7990            };
7991            current = primary;
7992        }
7993    }
7994
7995    /// Whether one indexed alias component in a qualified type reference has
7996    /// a structured path to `target`.
7997    ///
7998    /// Alias terminal names such as `type` are deliberately common. A
7999    /// file-wide name set cannot distinguish `Gen<T>::type`, whose indexed RHS
8000    /// names the target, from thousands of unrelated `Traits<T>::type`
8001    /// references. Match every qualified prefix against the alias's canonical
8002    /// scope before following its structured RHS. An unresolved dependent RHS
8003    /// fails this coarse gate only when no other component names the target;
8004    /// references such as `Identity<Target>::type` are admitted by that target
8005    /// component before this helper is called.
8006    pub fn qualified_alias_reference_may_reach_target(
8007        &self,
8008        analyzer: &CppGraphSource<'_>,
8009        file: &ProjectFile,
8010        components: &[String],
8011        global: bool,
8012        target: &CodeUnit,
8013    ) -> bool {
8014        for end in 0..components.len() {
8015            let spelled = &components[..=end];
8016            for candidate in self
8017                .visible_identifier_candidates(file, &components[end])
8018                .filter(|candidate| declared_type_alias(analyzer, candidate))
8019            {
8020                let candidate_components = canonical_cpp_scope_components(candidate);
8021                let shape_matches = if global {
8022                    candidate_components == spelled
8023                } else {
8024                    candidate_components.ends_with(spelled)
8025                };
8026                if shape_matches
8027                    && self.alias_candidate_structurally_reaches_target(
8028                        analyzer, file, candidate, target,
8029                    )
8030                {
8031                    return true;
8032                }
8033            }
8034        }
8035        false
8036    }
8037
8038    /// Every indexed type declaration `raw_name` names when it is written in
8039    /// `declaration`'s namespace: the innermost enclosing namespace that holds
8040    /// the name wins, otherwise the name is looked up unqualified.
8041    fn type_candidates_for_declaration<'b>(
8042        &'b self,
8043        visible_from: &ProjectFile,
8044        declaration: &CodeUnit,
8045        raw_name: &str,
8046    ) -> Vec<&'b CodeUnit> {
8047        let Some(normalized) = normalize_reference_name(raw_name) else {
8048            return Vec::new();
8049        };
8050        if let Some(namespace) = cpp_namespace_for(declaration) {
8051            for prefix in namespace_prefixes(&namespace) {
8052                let qualified = format!("{prefix}::{normalized}");
8053                let candidates = self.type_candidates(visible_from, &qualified);
8054                if !candidates.is_empty() {
8055                    return candidates;
8056                }
8057            }
8058        }
8059        self.type_candidates(visible_from, &normalized)
8060    }
8061
8062    fn resolve_unique_type_for_declaration(
8063        &self,
8064        visible_from: &ProjectFile,
8065        declaration: &CodeUnit,
8066        raw_name: &str,
8067    ) -> Option<CodeUnit> {
8068        unique_logical_type_candidate(self.type_candidates_for_declaration(
8069            visible_from,
8070            declaration,
8071            raw_name,
8072        ))
8073    }
8074
8075    /// The type `raw_name` names at `declaration`, answered with the
8076    /// declaration that defines it.
8077    ///
8078    /// C spells one type twice. `typedef struct T T;` in the header that
8079    /// publishes the handle indexes an incomplete `struct T` beside the
8080    /// complete `struct T { ... }` in the header that declares the members, and
8081    /// both carry the same kind and fully qualified name, so
8082    /// [`logical_type_candidate`] is free to answer with either. mbedtls writes
8083    /// exactly that: `include/mbedtls/ssl.h` forward-declares
8084    /// `mbedtls_ssl_handshake_params` for the pointer member
8085    /// `mbedtls_ssl_handshake_params *MBEDTLS_PRIVATE(handshake)` while
8086    /// `library/ssl_misc.h` defines its fields. A receiver typed from a field
8087    /// declaration is asked for its members on the next link of the chain, and
8088    /// only the definition owns them (#2982).
8089    ///
8090    /// Competing definitions leave nothing to choose between, so keep the
8091    /// logical answer rather than picking one of them.
8092    fn resolve_defining_type_for_declaration(
8093        &self,
8094        analyzer: &CppGraphSource<'_>,
8095        visible_from: &ProjectFile,
8096        declaration: &CodeUnit,
8097        raw_name: &str,
8098    ) -> Option<CodeUnit> {
8099        let candidates = self.type_candidates_for_declaration(visible_from, declaration, raw_name);
8100        let logical = unique_logical_type_candidate(candidates.clone())?;
8101        let mut defining = candidates.into_iter().filter(|candidate| {
8102            candidate.is_class()
8103                && cpp_class_declaration_strength(analyzer, candidate)
8104                    == CppClassDeclarationStrength::Full
8105        });
8106        match (defining.next(), defining.next()) {
8107            (Some(unique_definition), None) => Some(unique_definition.clone()),
8108            _ => Some(logical),
8109        }
8110    }
8111
8112    pub fn resolves_to_type(
8113        &self,
8114        analyzer: &CppGraphSource<'_>,
8115        file: &ProjectFile,
8116        raw_name: &str,
8117        target: &CodeUnit,
8118    ) -> bool {
8119        let Some(normalized) = normalize_reference_name(raw_name) else {
8120            return false;
8121        };
8122        let candidates = self.type_candidates(file, &normalized);
8123        if candidates.is_empty() {
8124            return self.parser_alias_resolves_to_type(file, raw_name, target);
8125        }
8126        let Some(resolved) =
8127            self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
8128        else {
8129            return false;
8130        };
8131        same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
8132    }
8133
8134    pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
8135        let mut current = alias.clone();
8136        let mut visited = HashSet::default();
8137        while visited.insert(current.clone()) {
8138            let raw_target = cpp_canonical_alias_target_text(&self.cpp_source(), &current)?;
8139            let resolved =
8140                self.resolve_type_for_declaration(current.source(), &current, &raw_target)?;
8141            if resolved.is_class() {
8142                return Some(resolved);
8143            }
8144            if !declared_type_alias(&self.cpp_source(), &resolved) {
8145                return None;
8146            }
8147            current = resolved;
8148        }
8149        None
8150    }
8151
8152    /// Whether two callable declarations declare one function.
8153    ///
8154    /// [`same_logical_symbol`] compares the persisted signature strings, which
8155    /// embed each parameter type exactly as it was spelled. A header
8156    /// declaration written inside `namespace zmq { class dist_t { ... } }` says
8157    /// `send_to_matching(msg_t *)` while its out-of-line body at file scope
8158    /// says `zmq::msg_t *`, so the string comparison reports two symbols where
8159    /// C++ ([basic.def], [dcl.fct]) sees one declaration and one definition.
8160    /// This resolves the written parameter names before comparing them and
8161    /// reports the same answer the language does for the cases it can prove.
8162    ///
8163    /// Everything it cannot prove stays two symbols: a template declaration, a
8164    /// parameter with no comparable shape, a name that resolves on one side
8165    /// only, and an alias chain it cannot follow safely (#2010).
8166    pub fn same_logical_callable(
8167        &self,
8168        analyzer: &CppGraphSource<'_>,
8169        left: &CodeUnit,
8170        right: &CodeUnit,
8171    ) -> bool {
8172        if same_logical_symbol(left, right) {
8173            return true;
8174        }
8175        if left.kind() != right.kind()
8176            || !left.is_callable()
8177            || !right.is_callable()
8178            || left.fq_name() != right.fq_name()
8179        {
8180            return false;
8181        }
8182        // A template declaration and its out-of-line body can also diverge
8183        // outside the parameter list - `template <class T>` against
8184        // `template <typename T>` - and the template head is part of the
8185        // persisted signature. Deciding template-head equivalence is a
8186        // separate question, so templates keep string identity.
8187        if self.callable_is_template_declaration(analyzer, left)
8188            || self.callable_is_template_declaration(analyzer, right)
8189        {
8190            return false;
8191        }
8192        let (Some(left_comparable), Some(right_comparable)) = (
8193            self.callable_comparable(analyzer, left),
8194            self.callable_comparable(analyzer, right),
8195        ) else {
8196            return false;
8197        };
8198        // The trailing member `const`, ref-qualifier, `noexcept`, trailing
8199        // return type and requires-clause are part of C++ callable identity and
8200        // an out-of-line definition repeats them verbatim, so they must agree
8201        // as written.
8202        if left_comparable.suffix != right_comparable.suffix
8203            || left_comparable.shapes.len() != right_comparable.shapes.len()
8204        {
8205            return false;
8206        }
8207        left_comparable
8208            .shapes
8209            .iter()
8210            .zip(right_comparable.shapes.iter())
8211            .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
8212                (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
8213                (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
8214                    self.comparable_shapes_agree(analyzer, left_shape, right_shape)
8215                }
8216                // An unstructured parameter records that the reduction failed,
8217                // not that the two spellings mean the same type, so it agrees
8218                // with nothing - including another unstructured parameter.
8219                _ => false,
8220            })
8221    }
8222
8223    /// Compare two parameter shapes node by node with an explicit paired stack.
8224    ///
8225    /// Shape variants and cv-qualifiers must agree exactly at every level; only
8226    /// the named leaves may be spelled differently, and they agree when they
8227    /// resolve to one type declaration.
8228    fn comparable_shapes_agree(
8229        &self,
8230        analyzer: &CppGraphSource<'_>,
8231        left: &CppComparableParameter,
8232        right: &CppComparableParameter,
8233    ) -> bool {
8234        let mut stack = vec![(left.root(), right.root())];
8235        while let Some((left_index, right_index)) = stack.pop() {
8236            match (left.node(left_index), right.node(right_index)) {
8237                (
8238                    CppComparableNode::Named {
8239                        name: left_name,
8240                        primitive: left_primitive,
8241                        konst: left_konst,
8242                        volatil: left_volatil,
8243                    },
8244                    CppComparableNode::Named {
8245                        name: right_name,
8246                        primitive: right_primitive,
8247                        konst: right_konst,
8248                        volatil: right_volatil,
8249                    },
8250                ) => {
8251                    if left_konst != right_konst
8252                        || left_volatil != right_volatil
8253                        || left_primitive != right_primitive
8254                        || !self.comparable_names_agree(
8255                            analyzer,
8256                            left_name,
8257                            right_name,
8258                            *left_primitive,
8259                        )
8260                    {
8261                        return false;
8262                    }
8263                }
8264                (
8265                    CppComparableNode::Pointer {
8266                        inner: left_inner,
8267                        konst: left_konst,
8268                        volatil: left_volatil,
8269                    },
8270                    CppComparableNode::Pointer {
8271                        inner: right_inner,
8272                        konst: right_konst,
8273                        volatil: right_volatil,
8274                    },
8275                ) => {
8276                    if left_konst != right_konst || left_volatil != right_volatil {
8277                        return false;
8278                    }
8279                    stack.push((*left_inner, *right_inner));
8280                }
8281                (
8282                    CppComparableNode::Reference { inner: left_inner },
8283                    CppComparableNode::Reference { inner: right_inner },
8284                )
8285                | (
8286                    CppComparableNode::Array { inner: left_inner },
8287                    CppComparableNode::Array { inner: right_inner },
8288                ) => stack.push((*left_inner, *right_inner)),
8289                (
8290                    CppComparableNode::Generic {
8291                        base: left_base,
8292                        arguments: left_arguments,
8293                    },
8294                    CppComparableNode::Generic {
8295                        base: right_base,
8296                        arguments: right_arguments,
8297                    },
8298                ) => {
8299                    if left_arguments.len() != right_arguments.len() {
8300                        return false;
8301                    }
8302                    stack.push((*left_base, *right_base));
8303                    stack.extend(
8304                        left_arguments.iter().zip(right_arguments.iter()).map(
8305                            |(left_argument, right_argument)| (*left_argument, *right_argument),
8306                        ),
8307                    );
8308                }
8309                _ => return false,
8310            }
8311        }
8312        true
8313    }
8314
8315    /// Whether two written type names denote one type.
8316    ///
8317    /// A primitive denotes the same type in every scope, so its recorded
8318    /// lexical scope is noise and its spelling decides. A nominal name is
8319    /// resolved on each side independently: two resolved names agree when they
8320    /// reach one type declaration, and two unresolved names agree only on
8321    /// exact agreement of what was written, which is no weaker than the
8322    /// whole-signature string equality this comparison replaces. Resolution on
8323    /// one side only is evidence of difference, never of agreement.
8324    fn comparable_names_agree(
8325        &self,
8326        analyzer: &CppGraphSource<'_>,
8327        left: &StructuredTypeName,
8328        right: &StructuredTypeName,
8329        primitive: bool,
8330    ) -> bool {
8331        if primitive {
8332            return left.path() == right.path();
8333        }
8334        match (
8335            self.comparable_name_terminal(analyzer, left),
8336            self.comparable_name_terminal(analyzer, right),
8337        ) {
8338            (Some(left_terminal), Some(right_terminal)) => {
8339                same_logical_symbol(&left_terminal, &right_terminal)
8340            }
8341            (None, None) => {
8342                left.path() == right.path() && left.is_absolute() == right.is_absolute()
8343            }
8344            _ => false,
8345        }
8346    }
8347
8348    /// The class declaration a written type name denotes, or `None` when the
8349    /// workspace cannot prove one.
8350    ///
8351    /// The lookup is a closure-independent lexical-scope prefix walk over the
8352    /// stored C++ identifier index rather than a visibility lookup: a body's
8353    /// `.cpp` is almost never in the reference file's include closure. Any name
8354    /// this walk resolves is one an enclosing-scope lookup could resolve, so it
8355    /// cannot invent a type the compiler could not see; `using`-directives are
8356    /// not modelled, and a name that needs one stays unresolved.
8357    fn comparable_name_terminal(
8358        &self,
8359        analyzer: &CppGraphSource<'_>,
8360        name: &StructuredTypeName,
8361    ) -> Option<CodeUnit> {
8362        let mut current = self.comparable_name_declaration(analyzer, name)?;
8363        let mut visited = HashSet::default();
8364        for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
8365            // The alias question is asked before the class question, and
8366            // through `declared_type_alias` rather than `is_type_alias`,
8367            // because extraction records `using A8 = A7;` as a *Class* unit
8368            // whose signature is the alias declaration. Reading the kind first
8369            // would end the chase on the alias itself and report an alias
8370            // spelling and its underlying class as two types (#2010).
8371            if !declared_type_alias(analyzer, &current) {
8372                return current.is_class().then_some(current);
8373            }
8374            if !visited.insert(current.clone()) {
8375                return None;
8376            }
8377            let facts = analyzer.declaration_source_properties(&current)?;
8378            if facts.iter().any(|fact| fact.adds_indirection) {
8379                return None;
8380            }
8381            let raw_target = cpp_canonical_alias_target_text(analyzer, &current)?;
8382            current = self.comparable_alias_target(analyzer, &current, &raw_target)?;
8383        }
8384        None
8385    }
8386
8387    /// The declaration one alias hop lands on: the type `raw_target` names,
8388    /// looked up from the alias declaration's own enclosing namespace.
8389    ///
8390    /// The hop takes the same closure-independent prefix walk the first lookup
8391    /// took, and deliberately not `resolve_type_for_declaration`: that one
8392    /// answers out of the `VisibilityIndex`, which is rooted at the reference
8393    /// file, while the alias declaration this hop starts from is reached
8394    /// through the workspace definition index and its file need not be in that
8395    /// root's include closure - where the visibility lookup answers nothing and
8396    /// the chase would stop on the alias itself (#2010).
8397    fn comparable_alias_target(
8398        &self,
8399        analyzer: &CppGraphSource<'_>,
8400        alias: &CodeUnit,
8401        raw_target: &str,
8402    ) -> Option<CodeUnit> {
8403        // `raw_target` is the alias declaration's written type text, so it is a
8404        // plain `::`-joined qualified-id: the same domain the shared symbol-path
8405        // parser reads, and the same leading `::` that marks an absolute name
8406        // everywhere else this crate normalizes a reference.
8407        let absolute = raw_target.trim_start().starts_with("::");
8408        let normalized = normalize_reference_name(raw_target)?;
8409        let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8410            brokk_bifrost_core::analyzer::Language::Cpp,
8411            &normalized,
8412        );
8413        let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
8414            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8415                brokk_bifrost_core::analyzer::Language::Cpp,
8416                &namespace,
8417            )
8418        });
8419        let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
8420        self.comparable_name_declaration(analyzer, &name)
8421    }
8422
8423    /// The one type declaration `name` names, by enclosing scope, innermost
8424    /// first.
8425    ///
8426    /// The first prefix depth that names anything decides: an inner scope hides
8427    /// an outer one, so a match there is the answer even when an outer scope
8428    /// also declares the name. Several logically distinct declarations at that
8429    /// depth are an ambiguity this comparison must not guess at.
8430    fn comparable_name_declaration(
8431        &self,
8432        analyzer: &CppGraphSource<'_>,
8433        name: &StructuredTypeName,
8434    ) -> Option<CodeUnit> {
8435        // The same parameter-name pair is commonly compared once for every
8436        // physical declaration in a candidate set. Keep the index lookup under
8437        // the cache lock so concurrent workers cannot all miss the same key
8438        // and repeat the workspace candidate filter. `None` is a meaningful
8439        // result too: unresolved and ambiguous names must remain unresolved on
8440        // every later comparison.
8441        let mut cache = self
8442            .comparable_name_declarations
8443            .lock()
8444            .expect("C++ comparable name declaration cache poisoned");
8445        if let Some(cached) = cache.get(name) {
8446            return cached.clone();
8447        }
8448        let interner = segment_interner();
8449        let identifier = name.path().last()?;
8450        let candidates_by_identifier = self.cpp.visibility_identifier_candidates(identifier);
8451        let first_depth = if name.is_absolute() {
8452            0
8453        } else {
8454            name.lexical_scope().len()
8455        };
8456        let mut resolved = None;
8457        for depth in (0..=first_depth).rev() {
8458            let mut structured = FqName::new();
8459            for component in name.lexical_scope()[..depth].iter().chain(name.path()) {
8460                structured.push(interner.intern(component, SegmentKind::Unknown));
8461            }
8462            let mut candidates = candidates_by_identifier
8463                .iter()
8464                .filter(|unit| unit.fq().same_segment_texts(&structured))
8465                .filter(|unit| {
8466                    unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
8467                })
8468                .cloned();
8469            let Some(first) = candidates.next() else {
8470                continue;
8471            };
8472            resolved = candidates
8473                .all(|unit| same_logical_symbol(&unit, &first))
8474                .then_some(first);
8475            break;
8476        }
8477        cache.insert(name.clone(), resolved.clone());
8478        resolved
8479    }
8480
8481    /// The comparison inputs of one callable declaration, extracted once.
8482    ///
8483    /// The comparison itself runs only when two candidates share kind and fully
8484    /// qualified name but not signature, which is rare; re-reading the same
8485    /// declaration for every pair in a candidate set is not.
8486    fn callable_comparable(
8487        &self,
8488        analyzer: &CppGraphSource<'_>,
8489        unit: &CodeUnit,
8490    ) -> Option<Arc<ExtractedComparable>> {
8491        if let Some(cached) = self
8492            .callable_comparables
8493            .lock()
8494            .expect("C++ callable comparable cache poisoned")
8495            .get(unit)
8496            .cloned()
8497        {
8498            return cached;
8499        }
8500        let extracted = self
8501            .extract_callable_comparable(analyzer, unit)
8502            .map(Arc::new);
8503        self.callable_comparables
8504            .lock()
8505            .expect("C++ callable comparable cache poisoned")
8506            .insert(unit.clone(), extracted.clone());
8507        extracted
8508    }
8509
8510    fn extract_callable_comparable(
8511        &self,
8512        analyzer: &CppGraphSource<'_>,
8513        unit: &CodeUnit,
8514    ) -> Option<ExtractedComparable> {
8515        let fact = analyzer
8516            .declaration_source_properties(unit)?
8517            .into_iter()
8518            .find(|fact| fact.callable_comparable_shapes.is_some())?;
8519        Some(ExtractedComparable {
8520            shapes: fact.callable_comparable_shapes?,
8521            suffix: fact.callable_identity_suffix?,
8522        })
8523    }
8524
8525    pub fn canonical_type_for_reference(
8526        &self,
8527        file: &ProjectFile,
8528        raw_name: &str,
8529    ) -> Option<CodeUnit> {
8530        let resolved = self.resolve_type(file, raw_name)?;
8531        self.alias_target(&resolved).or(Some(resolved))
8532    }
8533
8534    pub fn parser_alias_resolves_to_type(
8535        &self,
8536        file: &ProjectFile,
8537        raw_name: &str,
8538        target: &CodeUnit,
8539    ) -> bool {
8540        let Some(alias_name) = normalize_reference_name(raw_name) else {
8541            return false;
8542        };
8543        let matches_file = |source_file: &ProjectFile| {
8544            self.file_alias_matches(self.cpp, source_file, &alias_name, target)
8545        };
8546        self.visible_source_files_by_root.get(file).map_or_else(
8547            || matches_file(file),
8548            |files| files.iter().any(matches_file),
8549        )
8550    }
8551
8552    fn file_alias_matches(
8553        &self,
8554        cpp: &dyn CppSource,
8555        file: &ProjectFile,
8556        alias_name: &str,
8557        target: &CodeUnit,
8558    ) -> bool {
8559        self.canonical_alias_cell(cpp, file).is_some_and(|cell| {
8560            cell.get()
8561                .expect("published canonical aliases")
8562                .iter()
8563                .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
8564        })
8565    }
8566
8567    fn canonical_alias_cell(
8568        &self,
8569        cpp: &dyn CppSource,
8570        file: &ProjectFile,
8571    ) -> Option<Arc<OnceLock<Box<[CppAlias]>>>> {
8572        let cell = {
8573            let mut cells = self.alias_cells.lock().expect("alias cell map lock");
8574            Arc::clone(
8575                cells
8576                    .entry(file.clone())
8577                    .or_insert_with(|| Arc::new(OnceLock::new())),
8578            )
8579        };
8580        if cell.get().is_none() {
8581            let aliases = aliases_from_canonical_source(cpp, self.token, file)?;
8582            cell.get_or_init(|| {
8583                self.parser_alias_source_parses
8584                    .fetch_add(1, Ordering::Relaxed);
8585                #[cfg(any(test, feature = "test-support"))]
8586                {
8587                    *self
8588                        .alias_source_parse_counts
8589                        .lock()
8590                        .expect("alias source parse count lock")
8591                        .entry(file.clone())
8592                        .or_default() += 1;
8593                }
8594                aliases.into_boxed_slice()
8595            });
8596        }
8597        Some(cell)
8598    }
8599
8600    #[cfg(any(test, feature = "test-support"))]
8601    pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
8602        self.visible_source_files_by_root
8603            .get(file)
8604            .cloned()
8605            .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
8606    }
8607
8608    #[cfg(any(test, feature = "test-support"))]
8609    pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
8610        self.alias_source_parse_counts
8611            .lock()
8612            .expect("alias source parse count lock")
8613            .get(file)
8614            .copied()
8615            .unwrap_or(0)
8616    }
8617
8618    #[cfg(any(test, feature = "test-support"))]
8619    pub fn parser_alias_fallback_file_count_for_test(&self) -> usize {
8620        self.parser_alias_fallback_files.load(Ordering::Relaxed)
8621    }
8622
8623    pub fn resolve_named(
8624        &self,
8625        file: &ProjectFile,
8626        raw_name: &str,
8627        kind: TargetKind,
8628    ) -> Option<CodeUnit> {
8629        let normalized = normalize_reference_name(raw_name)?;
8630        self.named_candidates_for_normalized(file, &normalized, kind)
8631            .into_iter()
8632            .next()
8633            .cloned()
8634    }
8635
8636    pub fn contains_named_symbol(
8637        &self,
8638        file: &ProjectFile,
8639        raw_name: &str,
8640        kind: TargetKind,
8641        target: &CodeUnit,
8642    ) -> bool {
8643        let Some(normalized) = normalize_reference_name(raw_name) else {
8644            return false;
8645        };
8646        self.named_candidates_for_normalized(file, &normalized, kind)
8647            .into_iter()
8648            .any(|unit| {
8649                matches_kind_for_lookup(unit, kind)
8650                    && reference_matches_unit(&normalized, unit)
8651                    && same_visible_symbol(unit, target)
8652            })
8653    }
8654
8655    pub fn named_candidates(
8656        &self,
8657        file: &ProjectFile,
8658        raw_name: &str,
8659        kind: TargetKind,
8660    ) -> Vec<CodeUnit> {
8661        let Some(normalized) = normalize_reference_name(raw_name) else {
8662            return Vec::new();
8663        };
8664        self.named_candidates_for_normalized(file, &normalized, kind)
8665            .into_iter()
8666            .cloned()
8667            .collect()
8668    }
8669
8670    pub fn resolve_known_non_target(
8671        &self,
8672        file: &ProjectFile,
8673        raw_name: &str,
8674        kind: TargetKind,
8675        target: &CodeUnit,
8676    ) -> bool {
8677        let Some(normalized) = normalize_reference_name(raw_name) else {
8678            return false;
8679        };
8680        normalized.contains("::")
8681            && self
8682                .named_candidates_for_normalized(file, &normalized, kind)
8683                .into_iter()
8684                .any(|unit| {
8685                    matches_kind_for_lookup(unit, kind)
8686                        && reference_matches_unit(&normalized, unit)
8687                        && !same_visible_symbol(unit, target)
8688                })
8689    }
8690
8691    pub fn resolve_call_return_binding(
8692        &self,
8693        analyzer: &CppGraphSource<'_>,
8694        file: &ProjectFile,
8695        raw_name: &str,
8696        arity: usize,
8697        lexical_namespace: Option<&str>,
8698        direct_type: Option<&CodeUnit>,
8699    ) -> Option<CppScanBinding> {
8700        let normalized = normalize_reference_name(raw_name)?;
8701        let mut candidates = Vec::new();
8702        for function in
8703            self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8704        {
8705            if cpp_callable_arity(analyzer, function).is_none_or(|known| known.accepts(arity))
8706                && !direct_type.is_some_and(|direct_type| {
8707                    self.callable_is_constructor_declaration(analyzer, function)
8708                        && type_owner_of(analyzer, function)
8709                            .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8710                })
8711            {
8712                candidates.push(function.clone());
8713            }
8714        }
8715        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8716        unanimous_return_binding(analyzer, self, file, &candidates)
8717    }
8718
8719    pub fn resolve_call_return_binding_without_arity(
8720        &self,
8721        analyzer: &CppGraphSource<'_>,
8722        file: &ProjectFile,
8723        raw_name: &str,
8724        lexical_namespace: Option<&str>,
8725        direct_type: Option<&CodeUnit>,
8726    ) -> (bool, Option<CppScanBinding>) {
8727        let Some(normalized) = normalize_reference_name(raw_name) else {
8728            return (false, None);
8729        };
8730        let mut candidates = self
8731            .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8732            .into_iter()
8733            .filter(|function| {
8734                function.is_function()
8735                    && !direct_type.is_some_and(|direct_type| {
8736                        self.callable_is_constructor_declaration(analyzer, function)
8737                            && type_owner_of(analyzer, function)
8738                                .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8739                    })
8740            })
8741            .cloned()
8742            .collect::<Vec<_>>();
8743        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8744        let has_candidates = !candidates.is_empty();
8745        (
8746            has_candidates,
8747            unanimous_return_binding(analyzer, self, file, &candidates),
8748        )
8749    }
8750
8751    pub fn visible_identifier_candidates<'b>(
8752        &'b self,
8753        file: &ProjectFile,
8754        identifier: &str,
8755    ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
8756        self.visible_by_identifier
8757            .get(file)
8758            .and_then(|by_name| by_name.get(identifier))
8759            .into_iter()
8760            .flatten()
8761    }
8762
8763    /// Whether a grammar component is the direct spelling of `target`.
8764    ///
8765    /// Concrete template specializations are indexed with their arguments in
8766    /// `identifier`, while a reference's name component contains only the
8767    /// primary name. Argument matching remains the later resolver's job.
8768    pub fn type_reference_component_directly_names_target(
8769        &self,
8770        component: &str,
8771        target: &CodeUnit,
8772    ) -> bool {
8773        component == target.identifier()
8774            || self
8775                .cpp_template_metadata
8776                .get(target)
8777                .is_some_and(|metadata| component == metadata.primary_name)
8778    }
8779
8780    /// Return terminal reference names that can denote `target` from `file`.
8781    ///
8782    /// The indexed candidate table covers ordinary declarations and aliases;
8783    /// Parser-only aliases are tested lazily when their spelling is actually
8784    /// encountered in a scanned type node. Enumerating them here would parse
8785    /// every source in the include closure even when the target's direct name
8786    /// is the only spelling present in the file.
8787    pub fn visible_type_reference_component_names_for_target(
8788        &self,
8789        analyzer: &CppGraphSource<'_>,
8790        file: &ProjectFile,
8791        target: &CodeUnit,
8792    ) -> HashSet<String> {
8793        let mut names = HashSet::from_iter([target.identifier().to_string()]);
8794        if let Some(metadata) = self.cpp_template_metadata.get(target) {
8795            names.insert(metadata.primary_name.clone());
8796        }
8797
8798        if let Some(by_identifier) = self.visible_by_identifier.get(file) {
8799            for (identifier, candidates) in by_identifier {
8800                if candidates.iter().any(|candidate| {
8801                    (candidate.is_class()
8802                        && (same_visible_symbol(candidate, target)
8803                            || self.compatible_primary_template_redeclarations(candidate, target)))
8804                        || (declared_type_alias(analyzer, candidate)
8805                            && self.alias_candidate_structurally_reaches_target(
8806                                analyzer, file, candidate, target,
8807                            ))
8808                }) {
8809                    names.insert(identifier.clone());
8810                }
8811            }
8812        }
8813
8814        names
8815    }
8816
8817    pub fn indexed_structural_class_scope(
8818        &self,
8819        file: &ProjectFile,
8820        class: Node<'_>,
8821        source: &str,
8822    ) -> Option<Vec<String>> {
8823        let key = (file.clone(), class.start_byte(), class.end_byte());
8824        if let Some(cached) = self
8825            .indexed_structural_class_scopes
8826            .lock()
8827            .expect("C++ indexed structural-class scope cache poisoned")
8828            .get(&key)
8829            .cloned()
8830        {
8831            return cached;
8832        }
8833        let resolved = (|| {
8834            let name = class.child_by_field_name("name")?;
8835            let identifier = if name.kind() == "template_type" {
8836                node_text(name.child_by_field_name("name")?, source).to_string()
8837            } else {
8838                let mut components = Vec::new();
8839                append_cpp_name_components(name, source, &mut components)?;
8840                components.last()?.clone()
8841            };
8842            let visible = self
8843                .visible_identifier_candidates(file, &identifier)
8844                .cloned()
8845                .collect::<Vec<_>>();
8846            let mut visible = visible;
8847            for candidate in
8848                self.visible_by_file
8849                    .get(file)
8850                    .into_iter()
8851                    .flatten()
8852                    .filter(|candidate| {
8853                        self.cpp_template_metadata
8854                            .get(candidate)
8855                            .is_some_and(|metadata| metadata.primary_name == identifier)
8856                    })
8857            {
8858                if !visible
8859                    .iter()
8860                    .any(|existing| same_logical_symbol(existing, candidate))
8861                {
8862                    visible.push(candidate.clone());
8863                }
8864            }
8865            // Built once per call rather than per candidate; `cpp_source` rebuilds
8866            // the five-field source from the same `self.cpp` on every call.
8867            let cpp_source = self.cpp_source();
8868            let candidates = visible
8869                .iter()
8870                .filter(|candidate| {
8871                    candidate.source() == file
8872                        && candidate.is_class()
8873                        && !declared_type_alias(&cpp_source, candidate)
8874                        && self.cpp.ranges(candidate).iter().any(|range| {
8875                            range.start_byte <= class.start_byte()
8876                                && class.end_byte() <= range.end_byte
8877                        })
8878                })
8879                .collect::<Vec<_>>();
8880            let owner = if name.kind() == "template_type" {
8881                let expected = normalize_cpp_whitespace(node_text(name, source));
8882                let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
8883                let exact = candidates
8884                    .iter()
8885                    .copied()
8886                    .filter(|candidate| {
8887                        candidate
8888                            .fq()
8889                            .segments()
8890                            .iter()
8891                            .rev()
8892                            .find_map(|&segment| {
8893                                let (text, kind) = interner.resolve(segment);
8894                                matches!(
8895                                    kind,
8896                                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
8897                                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
8898                                )
8899                                .then_some(text)
8900                            })
8901                            .is_some_and(|text| text == expected)
8902                    })
8903                    .collect::<Vec<_>>();
8904                unique_logical_type_candidate(exact)
8905                    .or_else(|| unique_logical_type_candidate(candidates.clone()))?
8906            } else {
8907                unique_logical_type_candidate(candidates)?
8908            };
8909            Some(canonical_cpp_scope_components(&owner))
8910        })();
8911        self.indexed_structural_class_scopes
8912            .lock()
8913            .expect("C++ indexed structural-class scope cache poisoned")
8914            .insert(key, resolved.clone());
8915        resolved
8916    }
8917
8918    pub fn indexed_enclosing_owner_scope(
8919        &self,
8920        analyzer: &CppGraphSource<'_>,
8921        file: &ProjectFile,
8922        node: Node<'_>,
8923    ) -> Option<Vec<String>> {
8924        let anchor = std::iter::successors(Some(node), |current| current.parent())
8925            .find(|current| {
8926                matches!(
8927                    current.kind(),
8928                    "function_definition"
8929                        | "class_specifier"
8930                        | "struct_specifier"
8931                        | "union_specifier"
8932                )
8933            })
8934            .unwrap_or(node);
8935        let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
8936        if let Some(cached) = self
8937            .indexed_enclosing_owner_scopes
8938            .lock()
8939            .expect("C++ indexed enclosing-owner scope cache poisoned")
8940            .get(&key)
8941            .cloned()
8942        {
8943            return cached;
8944        }
8945        let resolved = (|| {
8946            let range = Range {
8947                start_byte: node.start_byte(),
8948                end_byte: node.end_byte(),
8949                start_line: node.start_position().row,
8950                end_line: node.end_position().row,
8951            };
8952            let start = analyzer.enclosing_code_unit(file, &range)?;
8953            let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
8954                start,
8955                |unit| self.cached_precise_parent_of(analyzer, unit),
8956            )
8957            .find(|unit| {
8958                unit.is_class()
8959                    && !analyzer
8960                        .type_alias_provider()
8961                        .is_some_and(|provider| provider.is_type_alias(unit))
8962            })?;
8963            Some(canonical_cpp_scope_components(&owner))
8964        })();
8965        self.indexed_enclosing_owner_scopes
8966            .lock()
8967            .expect("C++ indexed enclosing-owner scope cache poisoned")
8968            .insert(key, resolved.clone());
8969        resolved
8970    }
8971
8972    fn cached_precise_parent_of(
8973        &self,
8974        analyzer: &CppGraphSource<'_>,
8975        code_unit: &CodeUnit,
8976    ) -> Option<CodeUnit> {
8977        if let Some(cached) = self
8978            .precise_parent_cache
8979            .lock()
8980            .expect("C++ precise-parent cache poisoned")
8981            .get(code_unit)
8982            .cloned()
8983        {
8984            return cached;
8985        }
8986        let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
8987        self.precise_parent_cache
8988            .lock()
8989            .expect("C++ precise-parent cache poisoned")
8990            .insert(code_unit.clone(), resolved.clone());
8991        resolved
8992    }
8993
8994    pub fn callable_is_constructor_declaration(
8995        &self,
8996        analyzer: &CppGraphSource<'_>,
8997        candidate: &CodeUnit,
8998    ) -> bool {
8999        if !candidate.is_function() {
9000            return false;
9001        }
9002        if analyzer
9003            .declaration_source_properties(candidate)
9004            .is_some_and(|facts| facts.iter().any(|fact| fact.callable_is_constructor))
9005        {
9006            return true;
9007        }
9008        let metadata = analyzer.signature_metadata(candidate);
9009        !metadata.is_empty()
9010            && metadata
9011                .iter()
9012                .all(|signature| signature.return_type_text().is_none())
9013    }
9014
9015    /// Whether a callable declaration is a class-template deduction guide.
9016    ///
9017    /// Tree-sitter represents `Box(T) -> Box<T>;` as a declaration with no
9018    /// type field whose function declarator owns a trailing return type. This
9019    /// structured shape distinguishes a guide from both a constructor (no
9020    /// trailing return) and an ordinary trailing-return function (an `auto`
9021    /// type field).
9022    pub fn callable_is_deduction_guide_declaration(
9023        &self,
9024        analyzer: &CppGraphSource<'_>,
9025        candidate: &CodeUnit,
9026    ) -> bool {
9027        candidate.is_function()
9028            && analyzer
9029                .declaration_source_properties(candidate)
9030                .is_some_and(|facts| facts.iter().any(|fact| fact.callable_is_deduction_guide))
9031    }
9032
9033    /// Whether a callable occurrence is directly wrapped by a C++ template
9034    /// declaration. This deliberately inspects declaration syntax instead of
9035    /// inferring template status from the rendered signature.
9036    pub fn callable_is_template_declaration(
9037        &self,
9038        analyzer: &CppGraphSource<'_>,
9039        candidate: &CodeUnit,
9040    ) -> bool {
9041        candidate.is_function()
9042            && analyzer
9043                .declaration_source_properties(candidate)
9044                .is_some_and(|facts| facts.iter().any(|fact| fact.callable_is_template))
9045    }
9046
9047    pub fn type_name_candidates<'b>(
9048        &'b self,
9049        file: &ProjectFile,
9050        normalized: &str,
9051    ) -> Vec<&'b CodeUnit> {
9052        self.candidate_units(file, normalized, TargetKind::Type)
9053    }
9054
9055    pub fn visible_members_for_owner_name<'b>(
9056        &'b self,
9057        file: &ProjectFile,
9058        owner: &CodeUnit,
9059        name: &str,
9060    ) -> Vec<&'b CodeUnit> {
9061        self.visible_identifier_candidates(file, name)
9062            .filter(|unit| {
9063                // Structured owner pop on the unit's own `fq()` (shared with
9064                // `CodeUnitIndex::parent_of`), not a re-split of its rendered fqn
9065                // string.
9066                brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
9067                    .is_some_and(|parent| parent == owner.fq_name())
9068            })
9069            .collect()
9070    }
9071
9072    pub fn visible_member_for_owner_name(
9073        &self,
9074        file: &ProjectFile,
9075        owner: &CodeUnit,
9076        name: &str,
9077    ) -> VisibleMemberResolution {
9078        let candidates = self.visible_members_for_owner_name(file, owner, name);
9079        let mut callables = Vec::new();
9080        let mut non_callable = None;
9081        for candidate in candidates {
9082            if candidate.is_function() {
9083                callables.push(candidate.clone());
9084            } else if non_callable.is_none() {
9085                non_callable = Some(candidate.clone());
9086            }
9087        }
9088        match (callables.is_empty(), non_callable) {
9089            (false, None) => VisibleMemberResolution::Callable(callables),
9090            (true, Some(_)) => VisibleMemberResolution::NonCallable,
9091            (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
9092            (true, None) => VisibleMemberResolution::Missing,
9093        }
9094    }
9095
9096    fn field_declared_type_fact(
9097        &self,
9098        analyzer: &CppGraphSource<'_>,
9099        field: &CodeUnit,
9100    ) -> Option<DeclaredFieldTypeFact> {
9101        if let Some(cached) = self
9102            .field_type_facts
9103            .lock()
9104            .expect("C++ field type fact cache poisoned")
9105            .get(field)
9106            .cloned()
9107        {
9108            return cached;
9109        }
9110        let decoded = decode_field_declared_type_fact(analyzer, field);
9111        self.field_type_facts
9112            .lock()
9113            .expect("C++ field type fact cache poisoned")
9114            .insert(field.clone(), decoded.clone());
9115        decoded
9116    }
9117
9118    fn structured_alias_target(
9119        &self,
9120        analyzer: &CppGraphSource<'_>,
9121        unit: &CodeUnit,
9122    ) -> Option<StructuredAliasTarget> {
9123        if let Some(cached) = self
9124            .structured_alias_targets
9125            .lock()
9126            .expect("C++ structured alias target cache poisoned")
9127            .get(unit)
9128            .cloned()
9129        {
9130            return cached;
9131        }
9132        let decoded = decode_structured_alias_target(analyzer, unit);
9133        self.structured_alias_targets
9134            .lock()
9135            .expect("C++ structured alias target cache poisoned")
9136            .insert(unit.clone(), decoded.clone());
9137        decoded
9138    }
9139
9140    pub fn type_candidates<'b>(
9141        &'b self,
9142        file: &ProjectFile,
9143        normalized: &str,
9144    ) -> Vec<&'b CodeUnit> {
9145        let mut candidates = self
9146            .candidate_units(file, normalized, TargetKind::Type)
9147            .into_iter()
9148            .filter(|unit| {
9149                unit.kind() == CodeUnitType::Class || declared_type_alias(&self.cpp_source(), unit)
9150            })
9151            .collect::<Vec<_>>();
9152        dedup_unit_refs(&mut candidates);
9153        candidates
9154    }
9155
9156    pub fn named_candidates_for_normalized<'b>(
9157        &'b self,
9158        file: &ProjectFile,
9159        normalized: &str,
9160        kind: TargetKind,
9161    ) -> Vec<&'b CodeUnit> {
9162        let mut candidates = self
9163            .candidate_units(file, normalized, kind)
9164            .into_iter()
9165            .filter(|unit| {
9166                matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
9167            })
9168            .collect::<Vec<_>>();
9169        dedup_unit_refs(&mut candidates);
9170        candidates
9171    }
9172
9173    pub fn candidate_units<'b>(
9174        &'b self,
9175        file: &ProjectFile,
9176        normalized: &str,
9177        kind: TargetKind,
9178    ) -> Vec<&'b CodeUnit> {
9179        if normalized.contains("::") {
9180            // `normalized` comes from `normalize_cpp_reference_text`, which
9181            // truncates at the first `(`/`{`/`<`, leaving a plain `::`-joined
9182            // qualified-id with no embedded `.`/`/`/`\` and operator tokens
9183            // kept intact by the shared splitter's operator merge — the same
9184            // domain `cpp_reference_fqn_candidates` below already parses with
9185            // the shared splitter. Re-tokenizing and taking the last segment
9186            // reproduces `rsplit("::").find(non-empty)`'s terminal-component
9187            // scan exactly.
9188            let Some(identifier) = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9189                brokk_bifrost_core::analyzer::Language::Cpp,
9190                normalized,
9191            )
9192            .pop() else {
9193                return Vec::new();
9194            };
9195            let fqns = cpp_reference_fqn_candidates(normalized, kind);
9196            return self
9197                .visible_identifier_candidates(file, &identifier)
9198                .filter(|unit| {
9199                    #[cfg(any(test, feature = "test-support"))]
9200                    self.qualified_candidate_inspections
9201                        .fetch_add(1, Ordering::Relaxed);
9202                    fqns.iter().any(|fqn| unit.fq_name() == *fqn)
9203                        || canonical_cpp_name_matches(unit, normalized)
9204                })
9205                .collect();
9206        }
9207        self.visible_identifier_candidates(file, normalized)
9208            .collect()
9209    }
9210
9211    #[cfg(any(test, feature = "test-support"))]
9212    pub fn reset_qualified_candidate_inspections(&self) {
9213        self.qualified_candidate_inspections
9214            .store(0, Ordering::Relaxed);
9215    }
9216
9217    #[cfg(any(test, feature = "test-support"))]
9218    pub fn qualified_candidate_inspections(&self) -> usize {
9219        self.qualified_candidate_inspections.load(Ordering::Relaxed)
9220    }
9221
9222    #[cfg(any(test, feature = "test-support"))]
9223    pub fn visibility_identifier_lookup_count(&self) -> usize {
9224        self.visibility_identifier_lookup_count
9225    }
9226
9227    #[cfg(any(test, feature = "test-support"))]
9228    pub fn visibility_identifier_batch_count(&self) -> usize {
9229        self.visibility_identifier_batch_count
9230    }
9231
9232    #[cfg(any(test, feature = "test-support"))]
9233    pub fn reset_target_preserving_type_resolution_count(&self) {
9234        self.target_preserving_type_resolution_count
9235            .store(0, Ordering::Relaxed);
9236    }
9237
9238    #[cfg(any(test, feature = "test-support"))]
9239    pub fn target_preserving_type_resolution_count(&self) -> usize {
9240        self.target_preserving_type_resolution_count
9241            .load(Ordering::Relaxed)
9242    }
9243
9244    #[cfg(any(test, feature = "test-support"))]
9245    pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
9246        self.visible_parser_alias_name_set_build_count
9247            .load(Ordering::Relaxed)
9248    }
9249}
9250
9251#[derive(Default)]
9252struct IncludeGraph {
9253    targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
9254}
9255
9256impl IncludeGraph {
9257    fn extend_with<F>(
9258        &mut self,
9259        root: &ProjectFile,
9260        cancellation: Option<&CancellationToken>,
9261        targets_for: &mut F,
9262    ) where
9263        F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9264    {
9265        let mut stack = vec![root.clone()];
9266        while let Some(file) = stack.pop() {
9267            if cancellation.is_some_and(CancellationToken::is_cancelled) {
9268                break;
9269            }
9270            if self.targets_by_file.contains_key(&file) {
9271                continue;
9272            }
9273            let targets = targets_for(&file);
9274            stack.extend(targets.iter().cloned());
9275            self.targets_by_file.insert(file, targets);
9276        }
9277    }
9278
9279    fn files(&self) -> impl Iterator<Item = &ProjectFile> {
9280        self.targets_by_file.keys()
9281    }
9282
9283    fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
9284        self.targets_by_file
9285            .get(file)
9286            .map(Vec::as_slice)
9287            .unwrap_or_default()
9288    }
9289
9290    fn reachable_files(
9291        &self,
9292        root: &ProjectFile,
9293        cancellation: Option<&CancellationToken>,
9294    ) -> HashSet<ProjectFile> {
9295        let mut pending = vec![root.clone()];
9296        let mut visited = HashSet::default();
9297        while let Some(file) = pending.pop() {
9298            if cancellation.is_some_and(CancellationToken::is_cancelled) {
9299                break;
9300            }
9301            if visited.insert(file.clone()) {
9302                pending.extend(self.targets(&file).iter().cloned());
9303            }
9304        }
9305        visited
9306    }
9307}
9308
9309fn build_bounded_visible_declarations(
9310    cpp: &dyn CppSource,
9311    token: QueryToken<'_>,
9312    analyzer: &CppGraphSource<'_>,
9313    roots: &HashSet<ProjectFile>,
9314    visible_sources: &HashMap<ProjectFile, HashSet<ProjectFile>>,
9315    cancellation: Option<&CancellationToken>,
9316    stats: &mut BoundedVisibilityStats,
9317) -> HashMap<ProjectFile, HashSet<CodeUnit>> {
9318    let mut candidates_by_identifier = HashMap::default();
9319    let mut declarations_by_source_and_reading: HashMap<
9320        (ProjectFile, bool),
9321        BoundedVisibilityDeclarations,
9322    > = HashMap::default();
9323    let mut dependency_names_by_unit: HashMap<CodeUnit, HashSet<String>> = HashMap::default();
9324    roots
9325        .iter()
9326        .map(|root| {
9327            let reading_is_c = analyzer.reference_uses_c_semantics(root);
9328            let root_declarations = declarations_by_source_and_reading
9329                .entry((root.clone(), reading_is_c))
9330                .or_insert_with(|| {
9331                    bounded_visibility_declarations(cpp, analyzer, root, reading_is_c, stats)
9332                });
9333            let mut visible = root_declarations
9334                .all
9335                .iter()
9336                .cloned()
9337                .collect::<HashSet<_>>();
9338            let mut pending_names = HashSet::default();
9339            if let Some(prepared) = cpp.prepared_syntax(token, root) {
9340                // One cursor for the whole file walk: `named_child(index)`
9341                // re-steps the sibling list from the first child on every
9342                // access, which made this whole-file scan quadratic in the
9343                // fan-out of each node (#3097).
9344                let mut cursor = prepared.tree().walk();
9345                let mut pending_nodes = vec![prepared.tree().root_node()];
9346                while let Some(node) = pending_nodes.pop() {
9347                    if matches!(
9348                        node.kind(),
9349                        "identifier"
9350                            | "type_identifier"
9351                            | "field_identifier"
9352                            | "namespace_identifier"
9353                    ) {
9354                        pending_names.insert(node_text(node, prepared.source()).to_string());
9355                    }
9356                    if node.kind() == "preproc_arg" {
9357                        for reference in
9358                            object_macro_replacement_type_references(node, prepared.source())
9359                        {
9360                            pending_names.extend(reference.components);
9361                        }
9362                    }
9363                    pending_nodes.extend(node.named_children(&mut cursor));
9364                }
9365            }
9366            stats.root_names += pending_names.len();
9367            let mut completed_names = HashSet::default();
9368            while !pending_names.is_empty() {
9369                stats.rounds += 1;
9370                let round_names = std::mem::take(&mut pending_names);
9371                let mut requested_names_by_source: HashMap<ProjectFile, HashSet<String>> =
9372                    HashMap::default();
9373                let mut identifiers = Vec::new();
9374                for identifier in round_names {
9375                    if !completed_names.insert(identifier.clone())
9376                        || cancellation.is_some_and(CancellationToken::is_cancelled)
9377                    {
9378                        continue;
9379                    }
9380                    identifiers.push(identifier);
9381                }
9382                let missing_identifiers = identifiers
9383                    .iter()
9384                    .filter(|identifier| !candidates_by_identifier.contains_key(*identifier))
9385                    .cloned()
9386                    .collect::<HashSet<_>>();
9387                if !missing_identifiers.is_empty() {
9388                    let lookup_started = Instant::now();
9389                    let mut candidates = cpp
9390                        .visibility_identifier_candidates_batch(&missing_identifiers, cancellation);
9391                    stats.lookup_elapsed += lookup_started.elapsed();
9392                    stats.identifier_lookups += missing_identifiers.len();
9393                    stats.identifier_batches += 1;
9394                    if cancellation.is_some_and(CancellationToken::is_cancelled) {
9395                        break;
9396                    }
9397                    for identifier in missing_identifiers {
9398                        let units = candidates.remove(&identifier).unwrap_or_default();
9399                        let candidate_count = units.len();
9400                        let candidate_sources = units
9401                            .into_iter()
9402                            .map(|unit| unit.source().clone())
9403                            .collect::<HashSet<_>>();
9404                        candidates_by_identifier
9405                            .insert(identifier, (candidate_sources, candidate_count));
9406                    }
9407                }
9408                for identifier in identifiers {
9409                    let (candidate_sources, candidate_count) = candidates_by_identifier
9410                        .get(&identifier)
9411                        .expect("every missing identifier was inserted after the batch lookup");
9412                    stats.candidate_units += *candidate_count;
9413                    for source in candidate_sources.iter().cloned() {
9414                        if source != *root
9415                            && visible_sources
9416                                .get(root)
9417                                .is_some_and(|files| files.contains(&source))
9418                        {
9419                            requested_names_by_source
9420                                .entry(source)
9421                                .or_default()
9422                                .insert(identifier.clone());
9423                        }
9424                    }
9425                }
9426                stats.candidate_sources += requested_names_by_source.len();
9427                for (source, requested_names) in requested_names_by_source {
9428                    let declarations = declarations_by_source_and_reading
9429                        .entry((source.clone(), reading_is_c))
9430                        .or_insert_with(|| {
9431                            bounded_visibility_declarations(
9432                                cpp,
9433                                analyzer,
9434                                &source,
9435                                reading_is_c,
9436                                stats,
9437                            )
9438                        });
9439                    let mut selected = HashSet::default();
9440                    for name in requested_names {
9441                        if let Some(units) = declarations.by_identifier.get(&name) {
9442                            selected.extend(units.iter().cloned());
9443                        }
9444                    }
9445                    for unit in selected {
9446                        stats.selected_units += 1;
9447                        let dependency_names = dependency_names_by_unit
9448                            .entry(unit.clone())
9449                            .or_insert_with(|| {
9450                                let facts_started = Instant::now();
9451                                let mut dependency_names = HashSet::default();
9452                                if let Some(facts) = analyzer.declaration_source_properties(&unit) {
9453                                    for fact in facts {
9454                                        for name in fact.dependency_type_names {
9455                                            stats.dependency_source_names += 1;
9456                                            #[cfg(any(test, feature = "test-support"))]
9457                                            BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT
9458                                                .with(|count| count.set(count.get() + 1));
9459                                            dependency_names.insert(name);
9460                                        }
9461                                    }
9462                                    stats.dependency_source_elapsed += facts_started.elapsed();
9463                                }
9464                                dependency_names
9465                            });
9466                        for name in dependency_names.iter() {
9467                            if !completed_names.contains(name) && pending_names.insert(name.clone())
9468                            {
9469                                stats.dependency_names += 1;
9470                            }
9471                        }
9472                        visible.insert(unit);
9473                    }
9474                }
9475            }
9476            (root.clone(), visible)
9477        })
9478        .collect()
9479}
9480
9481struct BoundedVisibilityDeclarations {
9482    all: Vec<CodeUnit>,
9483    by_identifier: HashMap<String, Vec<CodeUnit>>,
9484}
9485
9486fn bounded_visibility_declarations(
9487    cpp: &dyn CppSource,
9488    analyzer: &CppGraphSource<'_>,
9489    file: &ProjectFile,
9490    c_semantics: bool,
9491    stats: &mut BoundedVisibilityStats,
9492) -> BoundedVisibilityDeclarations {
9493    let declarations_started = Instant::now();
9494    let all = bounded_visibility_declarations_in_reading(analyzer, file, c_semantics)
9495        .into_iter()
9496        .collect::<Vec<_>>();
9497    stats.declaration_elapsed += declarations_started.elapsed();
9498    stats.declaration_reads += 1;
9499    stats.declaration_units += all.len();
9500
9501    let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
9502    for unit in &all {
9503        by_identifier
9504            .entry(unit.identifier().to_string())
9505            .or_default()
9506            .push(unit.clone());
9507        if unit.is_class()
9508            && let Some(metadata) = cpp.template_metadata(unit)
9509            && metadata.primary_name != unit.identifier()
9510        {
9511            by_identifier
9512                .entry(metadata.primary_name)
9513                .or_default()
9514                .push(unit.clone());
9515        }
9516    }
9517    BoundedVisibilityDeclarations { all, by_identifier }
9518}
9519
9520#[derive(Default)]
9521struct BoundedVisibilityStats {
9522    rounds: usize,
9523    root_names: usize,
9524    identifier_lookups: usize,
9525    identifier_batches: usize,
9526    candidate_units: usize,
9527    candidate_sources: usize,
9528    declaration_reads: usize,
9529    declaration_units: usize,
9530    selected_units: usize,
9531    dependency_source_names: usize,
9532    dependency_names: usize,
9533    lookup_elapsed: Duration,
9534    declaration_elapsed: Duration,
9535    dependency_source_elapsed: Duration,
9536}
9537
9538fn bounded_visibility_declarations_in_reading(
9539    analyzer: &CppGraphSource<'_>,
9540    file: &ProjectFile,
9541    c_semantics: bool,
9542) -> BTreeSet<CodeUnit> {
9543    #[cfg(any(test, feature = "test-support"))]
9544    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(count.get() + 1));
9545    analyzer.declarations_in_reading(file, c_semantics)
9546}
9547
9548#[cfg(any(test, feature = "test-support"))]
9549pub fn reset_bounded_visibility_declaration_read_count_for_test() {
9550    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(0));
9551}
9552
9553#[cfg(any(test, feature = "test-support"))]
9554pub fn bounded_visibility_declaration_read_count_for_test() -> usize {
9555    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(Cell::get)
9556}
9557
9558#[cfg(any(test, feature = "test-support"))]
9559pub fn reset_bounded_visibility_dependency_ast_node_count_for_test() {
9560    BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(|count| count.set(0));
9561}
9562
9563#[cfg(any(test, feature = "test-support"))]
9564pub fn bounded_visibility_dependency_ast_node_count_for_test() -> usize {
9565    BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(Cell::get)
9566}
9567
9568pub struct VisibilityData {
9569    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
9570    pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
9571}
9572
9573/// Build the per-root include closure and the declarations each root can see
9574/// through it.
9575///
9576/// `declarations_for` takes the reading to answer in (issue #1970): a root
9577/// compiled as C sees the C reading of every file in its closure, a root
9578/// compiled as C++ sees the C++ reading, and `reading_is_c_for` decides which
9579/// per root. The two readings agree for all but a handful of headers, so the
9580/// C map is built only when some root actually asks for it, and only over the
9581/// files that root reaches.
9582pub fn build_visibility_data<F, R, D>(
9583    roots: &HashSet<ProjectFile>,
9584    cancellation: Option<&CancellationToken>,
9585    mut targets_for: F,
9586    mut reading_is_c_for: R,
9587    mut declarations_for: D,
9588) -> VisibilityData
9589where
9590    F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9591    R: FnMut(&ProjectFile) -> bool,
9592    D: FnMut(&ProjectFile, bool) -> BTreeSet<CodeUnit>,
9593{
9594    let mut include_graph = IncludeGraph::default();
9595    for file in roots {
9596        if cancellation.is_some_and(CancellationToken::is_cancelled) {
9597            break;
9598        }
9599        include_graph.extend_with(file, cancellation, &mut targets_for);
9600    }
9601    let cpp_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
9602        .files()
9603        .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
9604        .map(|file| (file.clone(), declarations_for(file, false)))
9605        .collect();
9606    let mut c_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = HashMap::default();
9607    let mut visible_by_file = HashMap::default();
9608    let mut visible_source_files_by_root = HashMap::default();
9609    for file in roots {
9610        if cancellation.is_some_and(CancellationToken::is_cancelled) {
9611            break;
9612        }
9613        let mut visited = HashSet::default();
9614        let mut visible = HashSet::default();
9615        let declarations_by_file = if reading_is_c_for(file) {
9616            for reached in cpp_declarations_by_file.keys() {
9617                if !c_declarations_by_file.contains_key(reached) {
9618                    let declarations = declarations_for(reached, true);
9619                    c_declarations_by_file.insert(reached.clone(), declarations);
9620                }
9621            }
9622            &c_declarations_by_file
9623        } else {
9624            &cpp_declarations_by_file
9625        };
9626        collect_visible_declarations(
9627            &include_graph,
9628            declarations_by_file,
9629            file,
9630            &mut visited,
9631            &mut visible,
9632            cancellation,
9633        );
9634        visible_by_file.insert(file.clone(), visible);
9635        visible_source_files_by_root.insert(file.clone(), visited);
9636    }
9637    VisibilityData {
9638        visible_by_file,
9639        visible_source_files_by_root,
9640    }
9641}
9642
9643/// Admit the class that an out-of-line definition proves is in scope.
9644///
9645/// `Owner::member(...) { ... }` in a file is structured proof that `Owner`
9646/// names a class-like entity in that file's scope: a member declaration can
9647/// live in a file other than its class's only when it is written out of line.
9648/// A file a build concatenates rather than compiles carries no `#include` edge
9649/// to the header declaring `Owner` -- google/wuffs
9650/// `internal/cgen/auxiliary/image.cc` defines
9651/// `DecodeImageResult::DecodeImageResult` and never includes `image.hh` -- so
9652/// every unqualified member and constructor reference in it had no candidate at
9653/// all (#1832).
9654///
9655/// The evidence is the indexed declaration's own owner name, taken from its
9656/// `FqName`, so this stays a structured answer rather than a text fallback.
9657/// Only an owner the file cannot already see is admitted: that is what keeps a
9658/// header declaring its own class from additionally seeing every same-named
9659/// class in the workspace, and it makes the pass free for the ordinary file
9660/// whose owners are all visible.
9661#[derive(Default)]
9662struct OutOfLineOwnerBindingStats {
9663    unseen_owners: usize,
9664    definition_lookups: usize,
9665    admitted: usize,
9666}
9667
9668fn extend_with_out_of_line_owner_bindings(
9669    cpp: &dyn CppSource,
9670    visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
9671) -> OutOfLineOwnerBindingStats {
9672    let mut stats = OutOfLineOwnerBindingStats::default();
9673    for (file, visible) in visible_by_file.iter_mut() {
9674        // The include-closure walk seeds every root with its own declarations,
9675        // so the file's members are already here; re-reading them from the
9676        // analyzer would pay for the same declaration set twice.
9677        let mut unseen_owners: HashSet<String> = visible
9678            .iter()
9679            .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
9680            .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
9681            .collect();
9682        if unseen_owners.is_empty() {
9683            continue;
9684        }
9685        for unit in visible.iter().filter(|unit| unit.is_class()) {
9686            unseen_owners.remove(&unit.fq_name());
9687        }
9688        stats.unseen_owners += unseen_owners.len();
9689        stats.definition_lookups += unseen_owners.len();
9690        let admitted = unseen_owners
9691            .iter()
9692            .flat_map(|owner| cpp.definitions(owner))
9693            .filter(CodeUnit::is_class)
9694            .collect::<Vec<_>>();
9695        stats.admitted += admitted.len();
9696        visible.extend(admitted);
9697    }
9698    stats
9699}
9700
9701pub enum VisibleMemberResolution {
9702    Callable(Vec<CodeUnit>),
9703    NonCallable,
9704    AmbiguousKind,
9705    Missing,
9706}
9707
9708#[derive(Clone)]
9709pub enum EnclosingMemberOwnerResolution {
9710    Owner(CodeUnit),
9711    Ambiguous,
9712    Missing,
9713}
9714
9715pub fn resolve_declaring_member_owner(
9716    analyzer: &CppGraphSource<'_>,
9717    visibility: &VisibilityIndex<'_>,
9718    file: &ProjectFile,
9719    receiver_owner: &CodeUnit,
9720    member_name: &str,
9721) -> EnclosingMemberOwnerResolution {
9722    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
9723        return EnclosingMemberOwnerResolution::Missing;
9724    };
9725    let Some(receiver_owner) =
9726        visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
9727    else {
9728        return EnclosingMemberOwnerResolution::Ambiguous;
9729    };
9730    let resolve_level = |frontier: &[CodeUnit]| {
9731        let mut member_owners = Vec::new();
9732        for raw_owner in frontier {
9733            let Some(owner) =
9734                visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
9735            else {
9736                return EnclosingMemberOwnerResolution::Ambiguous;
9737            };
9738            for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
9739                // A type nested in an owner shares the owner's member-name
9740                // index, but it cannot be the value receiver of `owner.name`.
9741                // Keep value members here so an anonymous aggregate field
9742                // does not become ambiguous with its promoted receiver type.
9743                if !member.is_field() && !member.is_function() {
9744                    continue;
9745                }
9746                let Some(member_owner) = type_owner_of(analyzer, member) else {
9747                    return EnclosingMemberOwnerResolution::Ambiguous;
9748                };
9749                if !member_owners
9750                    .iter()
9751                    .any(|existing| same_visible_symbol(existing, &member_owner))
9752                {
9753                    member_owners.push(member_owner);
9754                }
9755            }
9756        }
9757        match member_owners.len() {
9758            0 => EnclosingMemberOwnerResolution::Missing,
9759            1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
9760            _ => EnclosingMemberOwnerResolution::Ambiguous,
9761        }
9762    };
9763    // The first declaration on each structured base path hides deeper names,
9764    // regardless of whether its callable overload is applicable at a particular
9765    // call site. Applicability is checked only after this owner is established.
9766    let direct = resolve_level(std::slice::from_ref(&receiver_owner));
9767    if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
9768        return direct;
9769    }
9770    let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
9771    let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
9772    let mut path_matches = Vec::new();
9773    while let Some(raw_owner) = stack.pop() {
9774        let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
9775        else {
9776            return EnclosingMemberOwnerResolution::Ambiguous;
9777        };
9778        // Persisted hierarchy edges do not encode virtual-base or base-subobject paths.
9779        // Propagate at most two occurrences of each owner: that preserves the distinction
9780        // between one and multiple resolving base paths without exponential diamond walks.
9781        let propagated = propagated_counts.entry(owner.clone()).or_default();
9782        if *propagated == 2 {
9783            continue;
9784        }
9785        *propagated += 1;
9786        match resolve_level(std::slice::from_ref(&owner)) {
9787            EnclosingMemberOwnerResolution::Owner(owner) => {
9788                path_matches.push(owner);
9789                if path_matches.len() == 2 {
9790                    return EnclosingMemberOwnerResolution::Ambiguous;
9791                }
9792            }
9793            EnclosingMemberOwnerResolution::Ambiguous => {
9794                return EnclosingMemberOwnerResolution::Ambiguous;
9795            }
9796            EnclosingMemberOwnerResolution::Missing => {
9797                stack.extend(hierarchy.get_direct_ancestors(&owner));
9798            }
9799        }
9800    }
9801    match path_matches.len() {
9802        0 => EnclosingMemberOwnerResolution::Missing,
9803        1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
9804        _ => unreachable!("base-path matches are capped at one before returning"),
9805    }
9806}
9807
9808/// Resolve the declaring owner of a callable after applying a member
9809/// `using <Base>::<member>;` declaration to one exact call arity.
9810///
9811/// Ordinary member lookup is intentionally name-based: the first class that
9812/// declares a name hides the same name on deeper bases. A member
9813/// using-declaration is the one exception. When none of the declarations on
9814/// that first owner accepts the call arity, it can reintroduce an applicable
9815/// overload from the named base. If a declaration on the first owner does
9816/// accept the arity, argument types would be needed to choose between it and
9817/// a same-arity introduced overload, so this resolver conservatively keeps the
9818/// ordinary owner (#1835/#1843).
9819///
9820/// The caller supplies ordinary name-based owner resolution so a file scan can
9821/// reuse its existing owner cache before applying this callable-only exception.
9822pub fn resolve_declaring_callable_owner(
9823    analyzer: &CppGraphSource<'_>,
9824    visibility: &VisibilityIndex<'_>,
9825    file: &ProjectFile,
9826    ordinary: EnclosingMemberOwnerResolution,
9827    member_name: &str,
9828    call_arity: usize,
9829) -> EnclosingMemberOwnerResolution {
9830    let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
9831        return ordinary;
9832    };
9833    if visibility
9834        .visible_members_for_owner_name(file, ordinary_owner, member_name)
9835        .into_iter()
9836        .any(|unit| {
9837            unit.is_function()
9838                && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(call_arity))
9839        })
9840    {
9841        return ordinary;
9842    }
9843
9844    let mut pending = match member_using_declaration_bases(
9845        analyzer,
9846        visibility,
9847        file,
9848        ordinary_owner,
9849        member_name,
9850    ) {
9851        Ok(bases) => bases,
9852        Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
9853    };
9854    let mut visited = HashSet::default();
9855    let mut introduced_owners = Vec::new();
9856    while let Some(owner) = pending.pop() {
9857        if !visited.insert(owner.clone()) {
9858            continue;
9859        }
9860        let accepts_arity = visibility
9861            .visible_members_for_owner_name(file, &owner, member_name)
9862            .into_iter()
9863            .any(|unit| {
9864                unit.is_function()
9865                    && cpp_callable_arity(analyzer, unit)
9866                        .is_none_or(|known| known.accepts(call_arity))
9867            });
9868        if accepts_arity {
9869            if !introduced_owners
9870                .iter()
9871                .any(|existing| same_visible_symbol(existing, &owner))
9872            {
9873                introduced_owners.push(owner);
9874            }
9875            continue;
9876        }
9877        match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
9878            Ok(bases) => pending.extend(bases),
9879            Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
9880        }
9881    }
9882    match introduced_owners.as_slice() {
9883        [] => ordinary,
9884        [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
9885        _ => EnclosingMemberOwnerResolution::Ambiguous,
9886    }
9887}
9888
9889fn member_using_declaration_bases(
9890    analyzer: &CppGraphSource<'_>,
9891    visibility: &VisibilityIndex<'_>,
9892    file: &ProjectFile,
9893    owner: &CodeUnit,
9894    member_name: &str,
9895) -> Result<Vec<CodeUnit>, ()> {
9896    let Some(facts) = analyzer.declaration_source_properties(owner) else {
9897        return Err(());
9898    };
9899    let scopes: Vec<_> = facts
9900        .iter()
9901        .flat_map(|fact| &fact.member_usings)
9902        .filter(|using| using.member == member_name)
9903        .map(|using| using.scope.join("::"))
9904        .collect();
9905    if scopes.is_empty() {
9906        return Ok(Vec::new());
9907    }
9908    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
9909        return Ok(Vec::new());
9910    };
9911    let mut bases = Vec::new();
9912    for raw_ancestor in hierarchy.get_ancestors(owner) {
9913        let Some(ancestor) =
9914            visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
9915        else {
9916            return Err(());
9917        };
9918        let qualified = cpp_name_for(&ancestor);
9919        if scopes
9920            .iter()
9921            .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
9922            && !bases
9923                .iter()
9924                .any(|existing| same_visible_symbol(existing, &ancestor))
9925        {
9926            bases.push(ancestor);
9927        }
9928    }
9929    Ok(bases)
9930}
9931
9932pub fn lexical_component_tiers<'a>(
9933    components: &'a [String],
9934    global: bool,
9935    lexical_scope: &'a [String],
9936) -> impl Iterator<Item = Vec<String>> + 'a {
9937    let first_prefix_len = if global { 0 } else { lexical_scope.len() };
9938    (0..=first_prefix_len).rev().map(move |prefix_len| {
9939        let mut qualified = Vec::with_capacity(prefix_len + components.len());
9940        qualified.extend_from_slice(&lexical_scope[..prefix_len]);
9941        qualified.extend_from_slice(components);
9942        qualified
9943    })
9944}
9945
9946pub fn build_visible_identifier_index(
9947    analyzer: &CppGraphSource<'_>,
9948    visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
9949    visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
9950    global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
9951) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
9952    let mut out = HashMap::default();
9953    for (file, visible) in visible_by_file {
9954        let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
9955        for unit in visible {
9956            if unit.is_field()
9957                && !visible_source_files_by_root
9958                    .get(file)
9959                    .is_some_and(|sources| sources.contains(unit.source()))
9960                && cpp_global_field_has_internal_linkage_cached(
9961                    analyzer,
9962                    global_field_internal_linkage,
9963                    unit,
9964                )
9965            {
9966                continue;
9967            }
9968            by_identifier
9969                .entry(unit.identifier().to_string())
9970                .or_default()
9971                .push(unit.clone());
9972        }
9973        for units in by_identifier.values_mut() {
9974            sort_lookup_units(units);
9975            units.dedup();
9976        }
9977        out.insert(file.clone(), by_identifier);
9978    }
9979    out
9980}
9981
9982fn sort_lookup_units(units: &mut [CodeUnit]) {
9983    units.sort_by(|left, right| {
9984        left.fq_name()
9985            .cmp(&right.fq_name())
9986            .then_with(|| left.signature().cmp(&right.signature()))
9987            .then_with(|| left.source().cmp(right.source()))
9988            .then_with(|| left.kind().cmp(&right.kind()))
9989            .then_with(|| {
9990                left.package_segment_count()
9991                    .cmp(&right.package_segment_count())
9992            })
9993            .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
9994            .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
9995    });
9996}
9997
9998fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
9999    let interner = segment_interner();
10000    for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
10001        let (left_text, left_kind) = interner.resolve(left_id);
10002        let (right_text, right_kind) = interner.resolve(right_id);
10003        let order = left_text
10004            .cmp(right_text)
10005            .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
10006        if order != CmpOrdering::Equal {
10007            return order;
10008        }
10009    }
10010    left.len().cmp(&right.len())
10011}
10012
10013const fn segment_kind_order(kind: SegmentKind) -> u8 {
10014    match kind {
10015        SegmentKind::Path => 0,
10016        SegmentKind::Package => 1,
10017        SegmentKind::Type => 2,
10018        SegmentKind::Companion => 3,
10019        SegmentKind::Nested => 4,
10020        SegmentKind::Member => 5,
10021        SegmentKind::Unknown => 6,
10022    }
10023}
10024
10025fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
10026    let mut deduped = Vec::with_capacity(units.len());
10027    for unit in units.drain(..) {
10028        if !deduped.contains(&unit) {
10029            deduped.push(unit);
10030        }
10031    }
10032    *units = deduped;
10033}
10034
10035pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
10036    // Same domain as `candidate_units` above: `reference` is a plain
10037    // `::`-joined qualified-id with operator tokens kept intact by the shared
10038    // splitter's operator merge.
10039    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10040        brokk_bifrost_core::analyzer::Language::Cpp,
10041        reference,
10042    );
10043    if parts.is_empty() {
10044        return Vec::new();
10045    }
10046
10047    let mut candidates = Vec::new();
10048    for package_len in 0..parts.len() {
10049        let package = parts[..package_len].join("::");
10050        let rest = &parts[package_len..];
10051        if rest.is_empty() {
10052            continue;
10053        }
10054        match kind {
10055            TargetKind::Type | TargetKind::Constructor => {
10056                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
10057                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10058            }
10059            TargetKind::FreeFunction
10060            | TargetKind::Method
10061            | TargetKind::GlobalField
10062            | TargetKind::MemberField
10063            | TargetKind::Macro => {
10064                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10065                if rest.len() > 1 {
10066                    let owner = rest[..rest.len() - 1].join("$");
10067                    let short = format!("{}.{}", owner, rest[rest.len() - 1]);
10068                    push_cpp_fqn_candidate(&mut candidates, &package, &short);
10069                }
10070            }
10071        }
10072    }
10073    candidates
10074}
10075
10076fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
10077    let fqn = if package.is_empty() {
10078        short.to_string()
10079    } else {
10080        format!("{package}.{short}")
10081    };
10082    if !out.contains(&fqn) {
10083        out.push(fqn);
10084    }
10085}
10086
10087pub fn infer_cpp_initializer_type(
10088    analyzer: &CppGraphSource<'_>,
10089    visibility: &VisibilityIndex<'_>,
10090    file: &ProjectFile,
10091    source: &str,
10092    node: Node<'_>,
10093) -> Option<CodeUnit> {
10094    infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
10095        .and_then(|binding| binding.unit)
10096}
10097
10098pub fn infer_cpp_initializer_binding(
10099    analyzer: &CppGraphSource<'_>,
10100    visibility: &VisibilityIndex<'_>,
10101    file: &ProjectFile,
10102    source: &str,
10103    node: Node<'_>,
10104    receiver_resolver: Option<&ReceiverResolver<'_>>,
10105) -> Option<CppScanBinding> {
10106    match node.kind() {
10107        "new_expression" => {
10108            let text = normalize_cpp_whitespace(node_text(node, source));
10109            let rest = text.strip_prefix("new ").unwrap_or(text.as_str());
10110            let type_text = rest.split(['(', '{']).next().unwrap_or(rest);
10111            let name = normalize_cpp_type_name(type_text);
10112            Some(CppScanBinding::from_type_name(
10113                name.clone(),
10114                visibility.resolve_type(file, &name),
10115                1,
10116            ))
10117        }
10118        "call_expression" => node.child_by_field_name("function").and_then(|function| {
10119            // `a().b()` and `p->b()` invoke a member on a receiver *value*. The
10120            // callee's source text is an expression, not a name, and every name
10121            // lookup below normalizes a reference by truncating at the first
10122            // `(`: `first().second` would read as `first`, so the chained call
10123            // would take the type of `first()` instead of the type of
10124            // `first().second()` (#2178). Only the member path can answer for
10125            // this shape, so route to it from the callee's node kind.
10126            if function.kind() == "field_expression" {
10127                let arity = visibility.call_arity_evidence(file, node, source).exact()?;
10128                return resolve_field_method_call_return_binding(
10129                    analyzer,
10130                    visibility,
10131                    file,
10132                    source,
10133                    function,
10134                    arity,
10135                    receiver_resolver,
10136                );
10137            }
10138            let function_text = node_text(function, source);
10139            let direct_type_binding = visibility
10140                .resolve_type(file, function_text)
10141                .map(|unit| CppScanBinding::from_unit(unit, 0));
10142            if function.kind() == "template_function" && direct_type_binding.is_some() {
10143                let lexical_namespace = enclosing_namespace_context(node, source);
10144                let arity = visibility.call_arity_evidence(file, node, source).exact();
10145                if let Some(arity) = arity
10146                    && let Some(binding) = visibility.resolve_call_return_binding(
10147                        analyzer,
10148                        file,
10149                        function_text,
10150                        arity,
10151                        lexical_namespace.as_deref(),
10152                        direct_type_binding
10153                            .as_ref()
10154                            .and_then(|binding| binding.unit.as_ref()),
10155                    )
10156                {
10157                    return Some(binding);
10158                }
10159                let (has_callable, callable_binding) = visibility
10160                    .resolve_call_return_binding_without_arity(
10161                        analyzer,
10162                        file,
10163                        function_text,
10164                        lexical_namespace.as_deref(),
10165                        direct_type_binding
10166                            .as_ref()
10167                            .and_then(|binding| binding.unit.as_ref()),
10168                    );
10169                if let Some(binding) = callable_binding {
10170                    return Some(binding);
10171                }
10172                if has_callable {
10173                    return None;
10174                }
10175                return direct_type_binding;
10176            }
10177            // Only the return-typed branches need the argument count. An
10178            // unknown arity leaves them out, exactly as in the template arm
10179            // above, and still constructs the direct type: `File(getPath())`
10180            // names `File` whether or not `getPath()`'s expansion is provable.
10181            let arity = visibility.call_arity_evidence(file, node, source).exact();
10182            if let Some(arity) = arity {
10183                let direct_type_binding_for_call = direct_type_binding.clone();
10184                if let Some(binding) = resolve_static_method_call_return_binding(
10185                    analyzer, visibility, file, source, function, arity,
10186                )
10187                .or_else(|| {
10188                    // An applicable free function supplies the receiver value
10189                    // before an unrelated visible type with the same terminal
10190                    // name. The direct type still excludes its own constructor
10191                    // declaration below and remains the construction fallback.
10192                    visibility.resolve_call_return_binding(
10193                        analyzer,
10194                        file,
10195                        function_text,
10196                        arity,
10197                        enclosing_namespace_context(node, source).as_deref(),
10198                        direct_type_binding_for_call
10199                            .as_ref()
10200                            .and_then(|binding| binding.unit.as_ref()),
10201                    )
10202                }) {
10203                    return Some(binding);
10204                }
10205            }
10206            direct_type_binding
10207        }),
10208        _ => None,
10209    }
10210}
10211
10212fn resolve_static_method_call_return_binding(
10213    analyzer: &CppGraphSource<'_>,
10214    visibility: &VisibilityIndex<'_>,
10215    file: &ProjectFile,
10216    source: &str,
10217    function: Node<'_>,
10218    arity: usize,
10219) -> Option<CppScanBinding> {
10220    if function.kind() != "qualified_identifier" {
10221        return None;
10222    }
10223    let qualified = normalize_cpp_reference_text(node_text(function, source));
10224    // A C++ qualified-id is `::`-joined with no embedded delimiters in any
10225    // single component (the shared splitter's operator-token merge keeps
10226    // `operator+`-style names intact), so re-tokenizing with the shared
10227    // structured splitter and peeling the terminal segment reproduces
10228    // `rsplit_once("::")`'s (owner, member) split exactly — same shape as
10229    // `cpp_out_of_line_function_owner`'s `qualified` split above.
10230    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10231        brokk_bifrost_core::analyzer::Language::Cpp,
10232        &qualified,
10233    );
10234    let (owner_text, member_name) = match parts.split_last() {
10235        Some((member, owner_parts)) if !owner_parts.is_empty() => {
10236            (owner_parts.join("::"), member.clone())
10237        }
10238        _ => {
10239            let scope = function.child_by_field_name("scope")?;
10240            let name = function.child_by_field_name("name")?;
10241            (
10242                node_text(scope, source).to_string(),
10243                node_text(name, source).to_string(),
10244            )
10245        }
10246    };
10247    let owner = visibility.resolve_type(file, &owner_text)?;
10248    let candidates = visibility
10249        .visible_members_for_owner_name(file, &owner, &member_name)
10250        .into_iter()
10251        .filter(|unit| {
10252            unit.is_function()
10253                && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(arity))
10254        })
10255        .cloned()
10256        .collect::<Vec<_>>();
10257    unanimous_return_binding(analyzer, visibility, file, &candidates)
10258}
10259
10260fn resolve_field_method_call_return_binding(
10261    analyzer: &CppGraphSource<'_>,
10262    visibility: &VisibilityIndex<'_>,
10263    file: &ProjectFile,
10264    source: &str,
10265    function: Node<'_>,
10266    arity: usize,
10267    receiver_resolver: Option<&ReceiverResolver<'_>>,
10268) -> Option<CppScanBinding> {
10269    debug_assert_eq!(
10270        function.kind(),
10271        "field_expression",
10272        "the member-call return binding answers only for a field-expression callee"
10273    );
10274    let receiver_resolver = receiver_resolver?;
10275    let field = function.child_by_field_name("field")?;
10276    let member_name = node_text(function_terminal_node(field), source);
10277    let receiver = function
10278        .child_by_field_name("argument")
10279        .or_else(|| function.named_child(0))?;
10280    let owners = receiver_resolver(receiver, source);
10281    let mut candidates = Vec::new();
10282    for owner in owners {
10283        let declaring_owner =
10284            match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
10285                EnclosingMemberOwnerResolution::Owner(owner) => owner,
10286                EnclosingMemberOwnerResolution::Missing => continue,
10287                EnclosingMemberOwnerResolution::Ambiguous => return None,
10288            };
10289        candidates.extend(
10290            visibility
10291                .visible_members_for_owner_name(file, &declaring_owner, member_name)
10292                .into_iter()
10293                .filter(|unit| {
10294                    unit.is_function()
10295                        && cpp_callable_arity(analyzer, unit)
10296                            .is_none_or(|known| known.accepts(arity))
10297                })
10298                .cloned(),
10299        );
10300    }
10301    unanimous_return_binding(analyzer, visibility, file, &candidates)
10302}
10303
10304fn unanimous_return_binding(
10305    analyzer: &CppGraphSource<'_>,
10306    visibility: &VisibilityIndex<'_>,
10307    file: &ProjectFile,
10308    candidates: &[CodeUnit],
10309) -> Option<CppScanBinding> {
10310    let mut resolved_return: Option<CppScanBinding> = None;
10311    for function in candidates {
10312        let metadata = analyzer.signature_metadata(function);
10313        let return_types = if metadata.is_empty() {
10314            vec![cpp_function_return_type_text(analyzer, function)?]
10315        } else {
10316            metadata
10317                .iter()
10318                .map(|metadata| metadata.return_type_text().map(str::to_string))
10319                .collect::<Option<Vec<_>>>()?
10320        };
10321        for return_text in return_types {
10322            let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
10323            let name = normalize_cpp_type_name(&return_text);
10324            let binding = CppScanBinding::from_type_name(
10325                name.clone(),
10326                visibility
10327                    .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
10328                indirection,
10329            );
10330            if let Some(existing) = resolved_return.as_ref()
10331                && (existing.indirection != binding.indirection
10332                    || match (&existing.unit, &binding.unit) {
10333                        (Some(left), Some(right)) => !same_visible_symbol(left, right),
10334                        (None, None) => existing.type_name != binding.type_name,
10335                        (Some(_), None) | (None, Some(_)) => true,
10336                    })
10337            {
10338                return None;
10339            }
10340            resolved_return = Some(binding);
10341        }
10342    }
10343    resolved_return
10344}
10345
10346fn aliases_from_canonical_source(
10347    cpp: &dyn CppSource,
10348    token: QueryToken<'_>,
10349    file: &ProjectFile,
10350) -> Option<Vec<CppAlias>> {
10351    let source = cpp.declaration_source_facts(token, file)?;
10352    Some(
10353        source
10354            .facts
10355            .declarations
10356            .iter()
10357            .filter_map(|fact| {
10358                let alias = fact.file_scope_alias.as_ref()?;
10359                Some(CppAlias {
10360                    name: alias.name.clone(),
10361                    target: alias.target.clone(),
10362                    namespace: alias.namespace.clone(),
10363                })
10364            })
10365            .collect(),
10366    )
10367}
10368
10369pub(crate) fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
10370    let mut current = node.parent();
10371    while let Some(parent) = current {
10372        match parent.kind() {
10373            "translation_unit"
10374            | "namespace_definition"
10375            | "declaration_list"
10376            | "linkage_specification" => current = parent.parent(),
10377            "template_declaration" => current = parent.parent(),
10378            _ => return false,
10379        }
10380    }
10381    true
10382}
10383
10384pub fn collect_include_closure(
10385    analyzer: &CppGraphSource<'_>,
10386    include_targets: &IncludeTargetIndex,
10387    file: &ProjectFile,
10388    out: &mut HashSet<ProjectFile>,
10389    cancellation: Option<&CancellationToken>,
10390) {
10391    let mut stack = vec![file.clone()];
10392    while let Some(file) = stack.pop() {
10393        if cancellation.is_some_and(CancellationToken::is_cancelled) {
10394            break;
10395        }
10396        if !out.insert(file.clone()) {
10397            continue;
10398        }
10399        for include in analyzer.canonical_include_paths(&file).unwrap_or_default() {
10400            for target in resolve_include_targets_with_index(&file, &include, include_targets) {
10401                stack.push(target);
10402            }
10403        }
10404    }
10405}
10406
10407fn collect_visible_declarations(
10408    include_graph: &IncludeGraph,
10409    declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
10410    file: &ProjectFile,
10411    visited: &mut HashSet<ProjectFile>,
10412    out: &mut HashSet<CodeUnit>,
10413    cancellation: Option<&CancellationToken>,
10414) {
10415    let mut stack = vec![file.clone()];
10416    while let Some(file) = stack.pop() {
10417        if cancellation.is_some_and(CancellationToken::is_cancelled) {
10418            break;
10419        }
10420        if !visited.insert(file.clone()) {
10421            continue;
10422        }
10423        if let Some(declarations) = declarations_by_file.get(&file) {
10424            out.extend(declarations.iter().cloned());
10425        }
10426        stack.extend(include_graph.targets(&file).iter().cloned());
10427    }
10428}
10429
10430fn parse_macro_parameter_list<T>(
10431    replacement: &str,
10432    read: impl FnOnce(Node<'_>, &str) -> T,
10433) -> Option<T> {
10434    let source = format!("void __bifrost_macro_parameters({replacement});");
10435    let mut parser = Parser::new();
10436    parser
10437        .set_language(&tree_sitter_cpp::LANGUAGE.into())
10438        .ok()?;
10439    let tree = parser.parse(&source, None)?;
10440    let root = tree.root_node();
10441    if root.has_error() {
10442        return None;
10443    }
10444    let declaration = root.named_child(0)?;
10445    let declarator = declaration.child_by_field_name("declarator")?;
10446    let parameters = declarator.child_by_field_name("parameters")?;
10447    Some(read(parameters, &source))
10448}
10449
10450fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
10451    parse_macro_parameter_list(replacement, crate::declarations::cpp_callable_arity)
10452}
10453
10454/// Interpret opaque macro replacement parameters with the primary producer's
10455/// canonical type spelling, preserving cv/ref qualifiers for overload hiding.
10456pub fn cpp_macro_parameter_types(replacement: &str) -> Option<Vec<String>> {
10457    parse_macro_parameter_list(
10458        replacement,
10459        crate::declarations::cpp_callable_parameter_types,
10460    )
10461}
10462
10463pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> Option<CallableArity> {
10464    analyzer
10465        .signature_metadata(unit)
10466        .into_iter()
10467        .find_map(|metadata| metadata.callable_arity())
10468}
10469
10470pub fn cpp_callable_parameter_types(
10471    analyzer: &CppGraphSource<'_>,
10472    unit: &CodeUnit,
10473) -> Option<Vec<String>> {
10474    analyzer
10475        .signature_metadata(unit)
10476        .into_iter()
10477        .find_map(|metadata| metadata.callable_parameter_types().map(<[String]>::to_vec))
10478}
10479
10480fn merge_compatible_callable_arities(
10481    left: CallableArity,
10482    right: CallableArity,
10483) -> Option<CallableArity> {
10484    let total = left.total();
10485    let left_repeated = left.accepts(total.saturating_add(1));
10486    let right_repeated = right.accepts(right.total().saturating_add(1));
10487    if total != right.total() || left_repeated != right_repeated {
10488        return None;
10489    }
10490    let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
10491    Some(CallableArity::new(required, total, left_repeated))
10492}
10493
10494fn find_include_activation(
10495    cpp: &dyn CppSource,
10496    token: QueryToken<'_>,
10497    file: &ProjectFile,
10498    prepared: &PreparedSyntaxTree,
10499    donor_source: &ProjectFile,
10500) -> Option<usize> {
10501    let include_targets = cpp.include_target_index();
10502    let mut direct_includes = Vec::new();
10503    let mut nodes = vec![prepared.tree().root_node()];
10504    // An include activates for the whole file, so only an unconditional
10505    // directive counts here.
10506    let reference = CallableReferenceContext {
10507        file,
10508        position: None,
10509    };
10510    while let Some(node) = nodes.pop() {
10511        if node.kind() == "preproc_include" {
10512            if callable_preprocessor_context_is_visible_for_reference(
10513                node,
10514                prepared.source(),
10515                &reference,
10516            ) {
10517                for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
10518                    if let Some(target) = unique_include_target(resolve_include_targets_with_index(
10519                        file,
10520                        include,
10521                        include_targets,
10522                    )) {
10523                        direct_includes.push((node.end_byte(), target));
10524                    }
10525                }
10526            }
10527            continue;
10528        }
10529        push_named_children_reversed(node, &mut nodes);
10530    }
10531    direct_includes.sort_by_key(|(activation, _)| *activation);
10532    let mut known_missing = HashSet::default();
10533    direct_includes
10534        .into_iter()
10535        .find(|(_, direct)| {
10536            unconditional_include_reaches(
10537                cpp,
10538                token,
10539                include_targets,
10540                direct,
10541                donor_source,
10542                file,
10543                &mut known_missing,
10544            )
10545        })
10546        .map(|(activation, _)| activation)
10547}
10548
10549fn find_conditional_include_projection_index(
10550    cpp: &dyn CppSource,
10551    token: QueryToken<'_>,
10552    file: &ProjectFile,
10553    prepared: &PreparedSyntaxTree,
10554    on_state: &dyn Fn(),
10555) -> ConditionalIncludeProjectionIndex {
10556    let reference_is_c = reference_uses_c_semantics(cpp, file);
10557    let include_targets = cpp.include_target_index();
10558    let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
10559        HashMap::default();
10560    let mut pending = Vec::new();
10561    let mut nodes = vec![prepared.tree().root_node()];
10562    while let Some(node) = nodes.pop() {
10563        if node.kind() == "preproc_include" {
10564            let Some(required_guards) =
10565                include_directive_guard_requirements(node, prepared.source(), reference_is_c)
10566            else {
10567                continue;
10568            };
10569            for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
10570                let Some(target) = unique_include_target(resolve_include_targets_with_index(
10571                    file,
10572                    include,
10573                    include_targets,
10574                )) else {
10575                    continue;
10576                };
10577                pending.push((target, node.end_byte(), required_guards.clone()));
10578            }
10579            continue;
10580        }
10581        push_named_children_reversed(node, &mut nodes);
10582    }
10583
10584    // One reached file can have several distinct compatible guard paths. Each
10585    // (file, activation byte) key keeps only the inclusion-minimal guard sets:
10586    // the consumers ask existence questions whose answers are monotone in the
10587    // guard set -- a path whose requirements hold, stay stable, and stay
10588    // compatible under one environment does so under every subset as well --
10589    // so a state subsumed by an existing subset cannot witness anything its
10590    // subset does not, and inserting a smaller set evicts the supersets it
10591    // subsumes. Exact-set dedup still terminated cycles, but dense `#ifdef`
10592    // lattices (QMK's per-keyboard feature guards) enumerated the powerset of
10593    // path-union guard sets through it: the state space, the per-key linear
10594    // scans, and resident memory all grew without bound (#2365).
10595    let mut expanded: HashMap<(ProjectFile, usize), Vec<HashSet<PreprocessorGuard>>> =
10596        HashMap::default();
10597    while let Some((current_file, activation_byte, path)) = pending.pop() {
10598        let guard_sets = expanded
10599            .entry((current_file.clone(), activation_byte))
10600            .or_default();
10601        // Minimality is keyed on the full guard set alone: a path's undecided
10602        // guards are the per-conditional subset of that set, so a subset path
10603        // carries a subset of them too.
10604        if guard_sets
10605            .iter()
10606            .any(|existing| existing.is_subset(&path.all))
10607        {
10608            continue;
10609        }
10610        let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
10611            .drain(..)
10612            .partition(|existing| path.all.is_subset(existing));
10613        *guard_sets = kept;
10614        guard_sets.push(path.all.clone());
10615        if !evicted.is_empty()
10616            && let Some(projections) = projections_by_source.get_mut(&current_file)
10617        {
10618            projections.retain(|projection| {
10619                projection.activation_byte != activation_byte
10620                    || !evicted.contains(&projection.required_guards)
10621            });
10622        }
10623        on_state();
10624
10625        // A fresh minimal set has no equal in the store: equality would have
10626        // been caught by the subset check above.
10627        projections_by_source
10628            .entry(current_file.clone())
10629            .or_default()
10630            .push(ConditionalIncludeProjection {
10631                activation_byte,
10632                required_guards: path.all.clone(),
10633                partial_guards: path.partial.clone(),
10634            });
10635
10636        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
10637            continue;
10638        };
10639        let mut nodes = vec![current_prepared.tree().root_node()];
10640        while let Some(node) = nodes.pop() {
10641            if node.kind() == "preproc_include" {
10642                let Some(include_guards) = include_directive_guard_requirements(
10643                    node,
10644                    current_prepared.source(),
10645                    reference_is_c,
10646                ) else {
10647                    continue;
10648                };
10649                let Some(reached) = path.merged(&include_guards) else {
10650                    continue;
10651                };
10652                for (include, _) in
10653                    include_path_from_node(node, current_prepared.source()).into_iter()
10654                {
10655                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
10656                        &current_file,
10657                        include,
10658                        include_targets,
10659                    )) else {
10660                        continue;
10661                    };
10662                    pending.push((target, activation_byte, reached.clone()));
10663                }
10664                continue;
10665            }
10666            push_named_children_reversed(node, &mut nodes);
10667        }
10668    }
10669
10670    projections_by_source
10671        .into_iter()
10672        .map(|(source, mut projections)| {
10673            projections.sort_by_key(|projection| projection.activation_byte);
10674            (source, Arc::from(projections))
10675        })
10676        .collect()
10677}
10678
10679/// Decide one conditional include target without materializing every source
10680/// reached by every guard combination. A path `admission` already rejects
10681/// cannot become admissible after adding nested include guards -- both rules
10682/// only ever add requirements -- so discard it before expanding the next
10683/// header.
10684#[allow(clippy::too_many_arguments)]
10685fn find_conditional_include_projection_for_source(
10686    cpp: &dyn CppSource,
10687    token: QueryToken<'_>,
10688    file: &ProjectFile,
10689    prepared: &PreparedSyntaxTree,
10690    donor_source: &ProjectFile,
10691    admission: IncludePathAdmission,
10692    reference_guards: Option<&HashSet<PreprocessorGuard>>,
10693    reference_byte: usize,
10694    on_state: &dyn Fn(),
10695) -> bool {
10696    let Some(reference_guards) = reference_guards else {
10697        return false;
10698    };
10699    let reference_is_c = reference_uses_c_semantics(cpp, file);
10700    let include_targets = cpp.include_target_index();
10701    let mut pending = Vec::new();
10702    let mut nodes = vec![prepared.tree().root_node()];
10703    while let Some(node) = nodes.pop() {
10704        if node.kind() == "preproc_include" {
10705            let Some(required_guards) =
10706                include_directive_guard_requirements(node, prepared.source(), reference_is_c)
10707            else {
10708                continue;
10709            };
10710            if node.end_byte() > reference_byte
10711                || !admission.admits(
10712                    &required_guards.all,
10713                    &required_guards.partial,
10714                    Some(reference_guards),
10715                )
10716            {
10717                continue;
10718            }
10719            for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
10720                let Some(target) = unique_include_target(resolve_include_targets_with_index(
10721                    file,
10722                    include,
10723                    include_targets,
10724                )) else {
10725                    continue;
10726                };
10727                if &target == donor_source {
10728                    return true;
10729                }
10730                pending.push((target, required_guards.clone()));
10731            }
10732            continue;
10733        }
10734        push_named_children_reversed(node, &mut nodes);
10735    }
10736
10737    let mut expanded: HashMap<ProjectFile, Vec<HashSet<PreprocessorGuard>>> = HashMap::default();
10738    while let Some((current_file, path)) = pending.pop() {
10739        let guard_sets = expanded.entry(current_file.clone()).or_default();
10740        if guard_sets.contains(&path.all) {
10741            continue;
10742        }
10743        guard_sets.push(path.all.clone());
10744        on_state();
10745
10746        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
10747            continue;
10748        };
10749        let mut nodes = vec![current_prepared.tree().root_node()];
10750        while let Some(node) = nodes.pop() {
10751            if node.kind() == "preproc_include" {
10752                let Some(include_guards) = include_directive_guard_requirements(
10753                    node,
10754                    current_prepared.source(),
10755                    reference_is_c,
10756                ) else {
10757                    continue;
10758                };
10759                let Some(reached) = path.merged(&include_guards) else {
10760                    continue;
10761                };
10762                if !admission.admits(&reached.all, &reached.partial, Some(reference_guards)) {
10763                    continue;
10764                }
10765                for (include, _) in
10766                    include_path_from_node(node, current_prepared.source()).into_iter()
10767                {
10768                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
10769                        &current_file,
10770                        include,
10771                        include_targets,
10772                    )) else {
10773                        continue;
10774                    };
10775                    if &target == donor_source {
10776                        return true;
10777                    }
10778                    pending.push((target, reached.clone()));
10779                }
10780                continue;
10781            }
10782            push_named_children_reversed(node, &mut nodes);
10783        }
10784    }
10785    false
10786}
10787
10788/// Whether `translation_unit`'s unconditional `#include` closure reaches
10789/// `header`, directly or through any chain of headers.
10790///
10791/// The include-closure question asked on its own, for
10792/// [`crate::identity::cpp_header_body_files_are_related`]. The walk resolves
10793/// each include the way visibility does -- to a unique target or to nothing --
10794/// so a duplicated basename relates nothing, and it is memoized per file pair
10795/// on the analyzer.
10796///
10797/// The reference position is `translation_unit` itself: the question is
10798/// whether that unit compiles the header, so that unit's own dialect and
10799/// preprocessor context govern the walk.
10800pub fn cpp_include_closure_reaches(
10801    cpp: &dyn CppSource,
10802    token: QueryToken<'_>,
10803    translation_unit: &ProjectFile,
10804    header: &ProjectFile,
10805) -> bool {
10806    unconditional_include_reaches(
10807        cpp,
10808        token,
10809        cpp.include_target_index(),
10810        translation_unit,
10811        header,
10812        translation_unit,
10813        &mut HashSet::default(),
10814    )
10815}
10816
10817fn unconditional_include_reaches(
10818    cpp: &dyn CppSource,
10819    token: QueryToken<'_>,
10820    include_targets: &IncludeTargetIndex,
10821    first: &ProjectFile,
10822    donor_source: &ProjectFile,
10823    reference_file: &ProjectFile,
10824    known_missing: &mut HashSet<ProjectFile>,
10825) -> bool {
10826    if first == donor_source {
10827        return true;
10828    }
10829    if known_missing.contains(first) {
10830        return false;
10831    }
10832    let reference_is_c = reference_file
10833        .rel_path()
10834        .extension()
10835        .and_then(|extension| extension.to_str())
10836        == Some("c");
10837    if let Some(reaches) =
10838        cpp.cached_unconditional_include_reachability(first, donor_source, reference_is_c)
10839    {
10840        return reaches;
10841    }
10842    let mut visited = HashSet::default();
10843    let mut files = vec![first.clone()];
10844    // Only an unconditional directive extends the include reach, so the walk
10845    // asks the question without a reference position.
10846    let reference = CallableReferenceContext {
10847        file: reference_file,
10848        position: None,
10849    };
10850    while let Some(file) = files.pop() {
10851        if file == *donor_source {
10852            cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, true);
10853            return true;
10854        }
10855        if known_missing.contains(&file) || !visited.insert(file.clone()) {
10856            continue;
10857        }
10858        let Some(prepared) = cpp.prepared_syntax(token, &file) else {
10859            continue;
10860        };
10861        // One cursor for the whole-file walk; see `named_children_iter` (#3097).
10862        let mut cursor = prepared.tree().walk();
10863        let mut nodes = vec![prepared.tree().root_node()];
10864        while let Some(node) = nodes.pop() {
10865            if node.kind() == "preproc_include" {
10866                if callable_preprocessor_context_is_visible_for_reference(
10867                    node,
10868                    prepared.source(),
10869                    &reference,
10870                ) {
10871                    for (include, _) in include_path_from_node(node, prepared.source()).into_iter()
10872                    {
10873                        if let Some(target) = unique_include_target(
10874                            resolve_include_targets_with_index(&file, include, include_targets),
10875                        ) {
10876                            files.push(target);
10877                        }
10878                    }
10879                }
10880                continue;
10881            }
10882            let first_pushed = nodes.len();
10883            nodes.extend(node.named_children(&mut cursor));
10884            nodes[first_pushed..].reverse();
10885        }
10886    }
10887    known_missing.extend(visited);
10888    cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, false);
10889    false
10890}
10891
10892fn declaration_guard_requirements(
10893    analyzer: &CppGraphSource<'_>,
10894    cpp: &dyn CppSource,
10895    candidate: &CodeUnit,
10896) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
10897    declaration_guard_sites(analyzer, cpp, candidate, None)
10898        .into_iter()
10899        .map(|site| (site.byte, site.guards))
10900        .collect()
10901}
10902
10903/// One declaration range's preprocessor facts, as a visible-at-a-reference
10904/// question reads them.
10905struct DeclarationGuardSite {
10906    /// The byte at which the range introduces the name.
10907    byte: usize,
10908    /// The preprocessor requirements the range's declaration stands under.
10909    guards: HashSet<PreprocessorGuard>,
10910    /// Whether the range is one branch of completed `#if`/`#else` families
10911    /// that all close before the reference. Always false without a reference
10912    /// position.
10913    completed_family_before_reference: bool,
10914}
10915
10916/// The per-range facts behind [`declaration_guard_requirements`], plus the
10917/// reference-relative fact the completed-family rule asks for.
10918fn declaration_guard_sites(
10919    analyzer: &CppGraphSource<'_>,
10920    cpp: &dyn CppSource,
10921    candidate: &CodeUnit,
10922    reference_byte: Option<usize>,
10923) -> Vec<DeclarationGuardSite> {
10924    let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
10925        return Vec::new();
10926    };
10927    let root = prepared.tree().root_node();
10928    analyzer
10929        .declaration_source_occurrences(candidate)
10930        .into_iter()
10931        .flatten()
10932        .filter_map(|occurrence| {
10933            let guards = cpp_guard_set_to_runtime(occurrence.fact.guard_requirements.as_ref()?)?;
10934            Some(DeclarationGuardSite {
10935                byte: occurrence.range.start_byte,
10936                guards,
10937                completed_family_before_reference: reference_byte.is_some_and(|reference_byte| {
10938                    root.descendant_for_byte_range(
10939                        occurrence.range.start_byte,
10940                        occurrence.range.end_byte,
10941                    )
10942                    .is_some_and(|node| {
10943                        declaration_follows_completed_family_before_reference(
10944                            node,
10945                            prepared.source(),
10946                            reference_byte,
10947                        )
10948                    })
10949                }),
10950            })
10951        })
10952        .collect()
10953}
10954
10955/// Whether every conditional enclosing `node` is a completed
10956/// `#if`/`#elif`/`#else` family that closes before `reference_byte`.
10957///
10958/// A completed family is an exhaustive case analysis: every configuration
10959/// takes exactly one of its branches. A reference sited below the whole family
10960/// is compiled in a configuration that either takes the declaration's branch,
10961/// in which case the declaration is what the name resolves to there, or takes
10962/// another branch, in which case the declaration is simply not part of that
10963/// configuration. Nothing in the reference's own position settles the family,
10964/// so the declaration is one resolution the source presents at the reference
10965/// rather than a contradiction of it (#3297).
10966///
10967/// A lone `#if` is not a case analysis: nothing in the source says any
10968/// configuration defines its macro, so a declaration under one stays
10969/// unprovable and the implication rule keeps it out. Neither does a family
10970/// whose end is not behind the reference: a reference inside the other branch
10971/// contradicts the declaration, and a reference inside the declaration's own
10972/// branch is already covered by implication.
10973fn declaration_follows_completed_family_before_reference(
10974    node: Node<'_>,
10975    source: &str,
10976    reference_byte: usize,
10977) -> bool {
10978    callable_guard_completion_byte(node, source).is_some_and(|end| end > 0 && end <= reference_byte)
10979}
10980
10981/// Producer-owned completion boundary for all enclosing conditional families.
10982/// Zero is the neutral boundary for a declaration outside those families.
10983pub(crate) fn callable_guard_completion_byte(node: Node<'_>, source: &str) -> Option<usize> {
10984    let mut completion_byte = 0;
10985    let mut ancestor = node.parent();
10986    while let Some(conditional) = ancestor {
10987        if matches!(
10988            conditional.kind(),
10989            "preproc_if" | "preproc_ifdef" | "preproc_elif"
10990        ) && !is_file_covering_include_guard(conditional, source)
10991            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
10992            && preprocessor_conditional_contains_descendant(conditional, node)
10993        {
10994            let family = preprocessor_conditional_family_root(conditional);
10995            if !preprocessor_conditional_family_has_terminal_else(family) {
10996                return None;
10997            }
10998            completion_byte = completion_byte.max(family.end_byte());
10999        }
11000        ancestor = conditional.parent();
11001    }
11002    Some(completion_byte)
11003}
11004
11005fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
11006    analyzer
11007        .ranges(candidate)
11008        .into_iter()
11009        .map(|range| range.start_byte)
11010        .min()
11011}
11012
11013/// The macro names every configuration in `contexts` defines -- the fact set
11014/// one file's compile-database coverage proves (#2011). `None` when the
11015/// database has no entry for the file, which is different from an empty
11016/// intersection: no entry means no coverage, while an empty intersection is
11017/// covered-and-proves-nothing.
11018fn context_fact_names(contexts: &[CppCompileContext]) -> Option<HashSet<String>> {
11019    let (first, rest) = contexts.split_first()?;
11020    Some(
11021        first
11022            .defined_macros
11023            .iter()
11024            .filter(|name| {
11025                rest.iter()
11026                    .all(|context| context.defined_macros.contains(*name))
11027            })
11028            .cloned()
11029            .collect(),
11030    )
11031}
11032
11033pub fn guard_requirements_hold_at_reference(
11034    required: &HashSet<PreprocessorGuard>,
11035    reference: Option<&HashSet<PreprocessorGuard>>,
11036) -> bool {
11037    reference.is_some_and(|active| {
11038        required
11039            .iter()
11040            .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
11041    })
11042}
11043
11044fn preprocessor_guard_holds_at_reference(
11045    required: &PreprocessorGuard,
11046    active: &HashSet<PreprocessorGuard>,
11047) -> bool {
11048    if active.contains(required) {
11049        return true;
11050    }
11051    let active_expression = BooleanGuardExpression::all(
11052        active
11053            .iter()
11054            .filter_map(PreprocessorGuard::as_boolean_expression),
11055    );
11056    required
11057        .as_boolean_expression()
11058        .is_some_and(|required| active_expression.implies(&required))
11059}
11060
11061/// Cross-file guard rule: two guard sets are compatible when neither one
11062/// contradicts the other. Use this instead of the subset test whenever the
11063/// guards come from a foreign file, which resolves its own conditionals
11064/// independently of the reference.
11065fn guards_compatible_at_reference(
11066    declaration: &HashSet<PreprocessorGuard>,
11067    reference: Option<&HashSet<PreprocessorGuard>>,
11068) -> bool {
11069    reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
11070}
11071
11072/// The byte range of the `#if`/`#elif`/`#else` chain that encloses the smallest
11073/// node covering `[start_byte, end_byte)`, or `None` when nothing there is
11074/// conditional.
11075///
11076/// Two declarations of one name that report the same chain stand in different
11077/// branches of it, so at most one of them is compiled in any configuration.
11078/// They are alternate spellings of a single declaration, not competing
11079/// declarations, and navigation must not present them as an ambiguity.
11080pub fn preprocessor_conditional_family_range(
11081    root: Node<'_>,
11082    start_byte: usize,
11083    end_byte: usize,
11084) -> Option<(usize, usize)> {
11085    let node = root.descendant_for_byte_range(start_byte, end_byte)?;
11086    preprocessor_conditional_family_node(node)
11087        .map(|family| (family.start_byte(), family.end_byte()))
11088}
11089
11090pub(crate) fn preprocessor_conditional_family_node(node: Node<'_>) -> Option<Node<'_>> {
11091    let mut ancestor = Some(node);
11092    while let Some(current) = ancestor {
11093        if is_preprocessor_conditional(current)
11094            && preprocessor_conditional_contains_descendant(current, node)
11095        {
11096            return Some(preprocessor_conditional_family_root(current));
11097        }
11098        ancestor = current.parent();
11099    }
11100    None
11101}
11102
11103pub(crate) fn preprocessor_conditional_family_for_declaration(node: Node<'_>) -> Option<Node<'_>> {
11104    let mut ancestor = node.parent();
11105    while let Some(current) = ancestor {
11106        if is_preprocessor_conditional(current)
11107            && preprocessor_conditional_contains_descendant(current, node)
11108        {
11109            let family = preprocessor_conditional_family_root(current);
11110            if preprocessor_conditional_family_has_terminal_else(family) {
11111                return Some(family);
11112            }
11113        }
11114        ancestor = current.parent();
11115    }
11116    None
11117}
11118
11119fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
11120    while let Some(parent) = conditional.parent() {
11121        let is_alternative = parent
11122            .child_by_field_name("alternative")
11123            .is_some_and(|alternative| {
11124                alternative.start_byte() == conditional.start_byte()
11125                    && alternative.end_byte() == conditional.end_byte()
11126            });
11127        if !is_alternative {
11128            break;
11129        }
11130        conditional = parent;
11131    }
11132    conditional
11133}
11134
11135fn preprocessor_conditional_family_has_terminal_else(mut conditional: Node<'_>) -> bool {
11136    loop {
11137        let Some(alternative) = conditional.child_by_field_name("alternative") else {
11138            return false;
11139        };
11140        match alternative.kind() {
11141            "preproc_else" => return true,
11142            "preproc_elif" => conditional = alternative,
11143            _ => return false,
11144        }
11145    }
11146}
11147
11148/// The undecidable preprocessor guards an `#include` directive stands under,
11149/// or `None` when its conditional context keeps the directive out of a
11150/// translation unit of the reference's language: a false constant, a
11151/// condition this analyzer cannot read, or a `__cplusplus` branch that
11152/// language never takes.
11153///
11154/// This is [`callable_declaration_guard_requirements`]'s rule for the include
11155/// edge, and the language test is what keeps
11156/// [`IncludePathAdmission::Compatible`] honest: an empty active guard set
11157/// never implies `defined(__cplusplus)`, but it never contradicts it either,
11158/// so without this a `.c` file would reach the C++ arm of every
11159/// `#ifdef __cplusplus` / `#else` include pair.
11160fn include_directive_guard_requirements(
11161    node: Node<'_>,
11162    source: &str,
11163    reference_is_c: bool,
11164) -> Option<PreprocessorGuardEnvironment> {
11165    let required = preprocessor_guard_environment_by_family(node, source)?;
11166    let excluded_by_language = required.all.iter().any(|guard| match guard {
11167        PreprocessorGuard::Defined(name) => reference_is_c && name == "__cplusplus",
11168        PreprocessorGuard::Undefined(name) => !reference_is_c && name == "__cplusplus",
11169        _ => false,
11170    });
11171    (!excluded_by_language).then_some(required)
11172}
11173
11174pub fn preprocessor_guard_environment(
11175    node: Node<'_>,
11176    source: &str,
11177) -> Option<HashSet<PreprocessorGuard>> {
11178    preprocessor_guard_environment_by_family(node, source).map(|environment| environment.all)
11179}
11180
11181/// The preprocessor guards `node` stands under, split by whether the
11182/// conditional that contributes each one belongs to a family that covers every
11183/// configuration.
11184///
11185/// A complete `#if`/`#else` or `#if`/`#elif`/`#else` family is a case analysis
11186/// every configuration takes exactly one branch of, which is why the resolver
11187/// already reads a declaration through one (`exhaustive_guard_family_activation`,
11188/// `complementary_same_fqn_type_declarations`). A lone `#if` is not: nothing in
11189/// the source says any configuration defines that macro, so only the build can
11190/// decide it, and it stays in `partial`.
11191#[derive(Clone)]
11192struct PreprocessorGuardEnvironment {
11193    all: HashSet<PreprocessorGuard>,
11194    partial: HashSet<PreprocessorGuard>,
11195}
11196
11197impl PreprocessorGuardEnvironment {
11198    /// The environment of a path standing under both `self` and `other`, or
11199    /// `None` when the two contradict. A guard a lone `#if` contributes on
11200    /// either side stays undecided for the whole path.
11201    fn merged(&self, other: &Self) -> Option<Self> {
11202        Some(Self {
11203            all: merge_preprocessor_guards(&self.all, &other.all)?,
11204            partial: self.partial.union(&other.partial).cloned().collect(),
11205        })
11206    }
11207}
11208
11209fn preprocessor_guard_environment_by_family(
11210    node: Node<'_>,
11211    source: &str,
11212) -> Option<PreprocessorGuardEnvironment> {
11213    let mut all = HashSet::default();
11214    let mut partial = HashSet::default();
11215    let mut ancestor = node.parent();
11216    while let Some(conditional) = ancestor {
11217        if matches!(
11218            conditional.kind(),
11219            "preproc_if" | "preproc_ifdef" | "preproc_elif"
11220        ) && !is_file_covering_include_guard(conditional, source)
11221            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11222            && preprocessor_conditional_contains_descendant(conditional, node)
11223        {
11224            let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
11225            match guard {
11226                PreprocessorGuard::Constant(true) => {
11227                    ancestor = conditional.parent();
11228                    continue;
11229                }
11230                PreprocessorGuard::Constant(false) => return None,
11231                _ => {}
11232            }
11233            if all.contains(&guard.negated()) {
11234                return None;
11235            }
11236            if !preprocessor_conditional_family_has_terminal_else(
11237                preprocessor_conditional_family_root(conditional),
11238            ) {
11239                partial.insert(guard.clone());
11240            }
11241            all.insert(guard);
11242        }
11243        ancestor = conditional.parent();
11244    }
11245    if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
11246        match guard {
11247            PreprocessorGuard::Constant(true) => {}
11248            PreprocessorGuard::Constant(false) => return None,
11249            _ => {
11250                if all.contains(&guard.negated()) {
11251                    return None;
11252                }
11253                // The family of a conditional the parser split across a
11254                // statement boundary is not readable from the tree, so it
11255                // counts as undecided.
11256                partial.insert(guard.clone());
11257                all.insert(guard);
11258            }
11259        }
11260    }
11261    Some(PreprocessorGuardEnvironment { all, partial })
11262}
11263
11264pub(crate) fn callable_preprocessor_guard_requirements(
11265    node: Node<'_>,
11266    source: &str,
11267) -> Option<HashSet<PreprocessorGuard>> {
11268    let mut guards = HashSet::default();
11269    let mut ancestor = node.parent();
11270    while let Some(conditional) = ancestor {
11271        if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
11272            && !is_file_covering_include_guard(conditional, source)
11273            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11274            && preprocessor_conditional_contains_descendant(conditional, node)
11275        {
11276            let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
11277            match guard {
11278                PreprocessorGuard::Constant(true) => {}
11279                PreprocessorGuard::Constant(false) => return None,
11280                _ => {
11281                    if guards.contains(&guard.negated()) {
11282                        return None;
11283                    }
11284                    guards.insert(guard);
11285                }
11286            }
11287        }
11288        ancestor = conditional.parent();
11289    }
11290    Some(guards)
11291}
11292
11293fn fragmented_statement_preprocessor_guard(
11294    descendant: Node<'_>,
11295    source: &str,
11296) -> Option<PreprocessorGuard> {
11297    // A conditional that starts before `} else if (...) {` crosses the
11298    // enclosing statement's grammar boundary. tree-sitter leaves its opener
11299    // as a `preproc_if` with a missing terminator in the consequence and
11300    // reparses the real `#endif` as a `preproc_call` in the alternative. Pair
11301    // those structured nodes before restoring the guard to intervening uses.
11302    let mut ancestor = descendant.parent();
11303    while let Some(statement) = ancestor {
11304        if statement.kind() == "if_statement"
11305            && let (Some(consequence), Some(alternative)) = (
11306                statement.child_by_field_name("consequence"),
11307                statement.child_by_field_name("alternative"),
11308            )
11309            && alternative.start_byte() <= descendant.start_byte()
11310            && descendant.end_byte() <= alternative.end_byte()
11311        {
11312            let mut cursor = consequence.walk();
11313            let openers = consequence
11314                .named_children(&mut cursor)
11315                .filter(|child| {
11316                    matches!(child.kind(), "preproc_if" | "preproc_ifdef")
11317                        && child
11318                            .child(child.child_count().saturating_sub(1))
11319                            .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
11320                })
11321                .collect::<Vec<_>>();
11322            if openers.len() != 1 {
11323                ancestor = statement.parent();
11324                continue;
11325            }
11326
11327            let mut terminators = Vec::new();
11328            let mut stack = vec![alternative];
11329            while let Some(node) = stack.pop() {
11330                if node.kind() == "preproc_call"
11331                    && node.start_byte() >= descendant.end_byte()
11332                    && node
11333                        .child_by_field_name("directive")
11334                        .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
11335                {
11336                    terminators.push(node);
11337                    continue;
11338                }
11339                push_named_children_reversed(node, &mut stack);
11340            }
11341            if terminators.len() == 1 {
11342                return simple_preprocessor_guard(openers[0], source);
11343            }
11344        }
11345        ancestor = statement.parent();
11346    }
11347    None
11348}
11349
11350pub(crate) fn preprocessor_guard_for_descendant(
11351    conditional: Node<'_>,
11352    descendant: Node<'_>,
11353    source: &str,
11354) -> Option<PreprocessorGuard> {
11355    let mut guard = simple_preprocessor_guard(conditional, source)?;
11356    if conditional
11357        .child_by_field_name("alternative")
11358        .is_some_and(|alternative| {
11359            alternative.start_byte() <= descendant.start_byte()
11360                && descendant.end_byte() <= alternative.end_byte()
11361        })
11362    {
11363        let alternative = conditional.child_by_field_name("alternative")?;
11364        // Tree-sitter nests an `#elif` chain in each `alternative` field. A
11365        // descendant in any later branch must first exclude the parent branch,
11366        // then collect the nested `preproc_elif` guard from its own ancestor.
11367        if !matches!(alternative.kind(), "preproc_else" | "preproc_elif") {
11368            return None;
11369        }
11370        guard = guard.negated();
11371    }
11372    Some(guard)
11373}
11374
11375pub(crate) fn preprocessor_conditional_contains_descendant(
11376    conditional: Node<'_>,
11377    descendant: Node<'_>,
11378) -> bool {
11379    cpp_displaced_preprocessor_boundary(conditional)
11380        .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
11381}
11382
11383pub fn merge_preprocessor_guards(
11384    left: &HashSet<PreprocessorGuard>,
11385    right: &HashSet<PreprocessorGuard>,
11386) -> Option<HashSet<PreprocessorGuard>> {
11387    let mut merged = left.clone();
11388    for guard in right {
11389        let boolean_negation = guard
11390            .as_boolean_expression()
11391            .map(|expression| expression.negated());
11392        if merged.contains(&guard.negated())
11393            || boolean_negation.is_some_and(|negated| {
11394                merged
11395                    .iter()
11396                    .filter_map(PreprocessorGuard::as_boolean_expression)
11397                    .any(|existing| existing == negated)
11398            })
11399        {
11400            return None;
11401        }
11402        merged.insert(guard.clone());
11403    }
11404    Some(merged)
11405}
11406
11407fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
11408    if conditional.kind() == "preproc_ifdef" {
11409        let name = conditional.child_by_field_name("name")?;
11410        let name = node_text(name, source).to_string();
11411        return match conditional.child(0)?.kind() {
11412            "#ifdef" => Some(PreprocessorGuard::Defined(name)),
11413            "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
11414            _ => None,
11415        };
11416    }
11417    let condition = conditional.child_by_field_name("condition")?;
11418    simple_preprocessor_expression_guard(condition, source).or_else(|| {
11419        Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
11420            node_text(condition, source),
11421        )))
11422    })
11423}
11424
11425fn simple_preprocessor_expression_guard(
11426    expression: Node<'_>,
11427    source: &str,
11428) -> Option<PreprocessorGuard> {
11429    match expression.kind() {
11430        "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
11431            node_text(expression, source).to_string(),
11432        ))),
11433        "number_literal" => match node_text(expression, source).trim() {
11434            "0" => Some(PreprocessorGuard::Constant(false)),
11435            "1" => Some(PreprocessorGuard::Constant(true)),
11436            _ => None,
11437        },
11438        "preproc_defined" => {
11439            let identifier = (0..expression.named_child_count())
11440                .filter_map(|index| expression.named_child(index))
11441                .find(|child| child.kind() == "identifier")?;
11442            Some(PreprocessorGuard::Defined(
11443                node_text(identifier, source).to_string(),
11444            ))
11445        }
11446        "unary_expression"
11447            if expression
11448                .child_by_field_name("operator")
11449                .is_some_and(|operator| operator.kind() == "!") =>
11450        {
11451            simple_preprocessor_expression_guard(
11452                expression.child_by_field_name("argument")?,
11453                source,
11454            )
11455            .map(|guard| guard.negated())
11456        }
11457        "parenthesized_expression" => (0..expression.named_child_count())
11458            .filter_map(|index| expression.named_child(index))
11459            .next()
11460            .and_then(|child| simple_preprocessor_expression_guard(child, source)),
11461        "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
11462            expression, source,
11463        ))),
11464        _ => None,
11465    }
11466}
11467
11468fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
11469    match expression.kind() {
11470        "number_literal" => match node_text(expression, source).trim() {
11471            "0" => BooleanGuardExpression::Constant(false),
11472            "1" => BooleanGuardExpression::Constant(true),
11473            _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11474                expression, source,
11475            ))),
11476        },
11477        "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
11478        "preproc_defined" => {
11479            let identifier = (0..expression.named_child_count())
11480                .filter_map(|index| expression.named_child(index))
11481                .find(|child| child.kind() == "identifier");
11482            identifier.map_or_else(
11483                || {
11484                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11485                        expression, source,
11486                    )))
11487                },
11488                |identifier| {
11489                    BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
11490                },
11491            )
11492        }
11493        "unary_expression"
11494            if expression
11495                .child_by_field_name("operator")
11496                .is_some_and(|operator| operator.kind() == "!") =>
11497        {
11498            expression.child_by_field_name("argument").map_or_else(
11499                || {
11500                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11501                        expression, source,
11502                    )))
11503                },
11504                |argument| boolean_preprocessor_expression(argument, source).negated(),
11505            )
11506        }
11507        "parenthesized_expression" => (0..expression.named_child_count())
11508            .filter_map(|index| expression.named_child(index))
11509            .next()
11510            .map_or_else(
11511                || {
11512                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11513                        expression, source,
11514                    )))
11515                },
11516                |child| boolean_preprocessor_expression(child, source),
11517            ),
11518        "binary_expression" => {
11519            let operands = || {
11520                Some((
11521                    boolean_preprocessor_expression(
11522                        expression.child_by_field_name("left")?,
11523                        source,
11524                    ),
11525                    boolean_preprocessor_expression(
11526                        expression.child_by_field_name("right")?,
11527                        source,
11528                    ),
11529                ))
11530            };
11531            match expression
11532                .child_by_field_name("operator")
11533                .map(|operator| operator.kind())
11534            {
11535                Some("&&") => operands().map_or_else(
11536                    || {
11537                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11538                            expression, source,
11539                        )))
11540                    },
11541                    |(left, right)| BooleanGuardExpression::all([left, right]),
11542                ),
11543                Some("||") => operands().map_or_else(
11544                    || {
11545                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11546                            expression, source,
11547                        )))
11548                    },
11549                    |(left, right)| BooleanGuardExpression::any([left, right]),
11550                ),
11551                _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11552                    expression, source,
11553                ))),
11554            }
11555        }
11556        _ => {
11557            BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
11558        }
11559    }
11560}
11561
11562fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
11563    if targets.len() == 1 {
11564        targets.pop()
11565    } else {
11566        None
11567    }
11568}
11569
11570fn callable_declaration_nodes<'tree>(
11571    analyzer: &CppGraphSource<'_>,
11572    prepared: &'tree PreparedSyntaxTree,
11573    candidate: &CodeUnit,
11574) -> Vec<Node<'tree>> {
11575    let root = prepared.tree().root_node();
11576    analyzer
11577        .ranges(candidate)
11578        .into_iter()
11579        .filter_map(|range| {
11580            let mut declaration =
11581                root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
11582            // A declaration an attribute-like macro invocation swallowed lives
11583            // inside the `ERROR` the parser left, not inside a `declaration`
11584            // node, so that envelope is where the climb stops (#2552).
11585            //
11586            // The climb can also run out of ancestors. A collapse that takes a
11587            // whole file with it -- whisper.cpp's `include/whisper.h` from its
11588            // first `WHISPER_DEPRECATED(` to the end (#3094) -- leaves no
11589            // envelope around most of what a region reparse recovers there, and
11590            // the recovered declaration's own `;` can fall outside the envelope
11591            // it does leave, so the range spans the container rather than one
11592            // node. Either way the declaration stands at the scope the climb
11593            // ended in, which is what the callers ask these nodes about.
11594            while !matches!(
11595                declaration.kind(),
11596                "declaration" | "field_declaration" | "function_definition"
11597            ) && !crate::declarations::is_macro_wrapped_declaration_envelope(
11598                declaration,
11599                prepared.source(),
11600            ) {
11601                let Some(parent) = declaration.parent() else {
11602                    break;
11603                };
11604                declaration = parent;
11605            }
11606            Some(declaration)
11607        })
11608        .collect()
11609}
11610
11611fn real_function_definition_ancestor<'tree>(
11612    node: Node<'tree>,
11613    source: &str,
11614) -> Option<Node<'tree>> {
11615    let mut ancestor = node.parent();
11616    while let Some(node) = ancestor {
11617        if node.kind() == "function_definition"
11618            && !is_recovered_declaration_scope_container(node, source)
11619        {
11620            return Some(node);
11621        }
11622        ancestor = node.parent();
11623    }
11624    None
11625}
11626
11627fn callable_declaration_activation_in_file(
11628    analyzer: &CppGraphSource<'_>,
11629    candidate: &CodeUnit,
11630    reference: &CallableReferenceContext<'_>,
11631) -> Option<usize> {
11632    analyzer
11633        .declaration_source_occurrences(candidate)?
11634        .into_iter()
11635        .filter_map(|occurrence| {
11636            let activation = occurrence.fact.callable_activation?;
11637            let guards = cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
11638            callable_guard_requirements_are_visible(
11639                &guards,
11640                occurrence.fact.callable_guard_completion_byte,
11641                reference,
11642            )
11643            .then_some(activation)
11644        })
11645        .min()
11646}
11647
11648fn callable_guard_requirements_are_visible(
11649    required: &HashSet<PreprocessorGuard>,
11650    completion_byte: Option<usize>,
11651    reference: &CallableReferenceContext<'_>,
11652) -> bool {
11653    if required.iter().all(|guard| match guard {
11654        PreprocessorGuard::Defined(name) if name == "__cplusplus" => !reference.is_c(),
11655        PreprocessorGuard::Undefined(name) if name == "__cplusplus" => reference.is_c(),
11656        guard => reference
11657            .guards()
11658            .is_some_and(|active| preprocessor_guard_holds_at_reference(guard, active)),
11659    }) {
11660        return true;
11661    }
11662    if required.iter().any(|guard| match guard {
11663        PreprocessorGuard::Defined(name) if name == "__cplusplus" => reference.is_c(),
11664        PreprocessorGuard::Undefined(name) if name == "__cplusplus" => !reference.is_c(),
11665        _ => false,
11666    }) {
11667        return false;
11668    }
11669    let undecided: HashSet<_> = required
11670        .iter()
11671        .filter(|guard| {
11672            !matches!(guard, PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
11673            if name == "__cplusplus")
11674        })
11675        .cloned()
11676        .collect();
11677    reference.position.as_ref().is_some_and(|position| {
11678        completion_byte.is_some_and(|end| end > 0 && end <= position.byte)
11679            && guards_compatible_at_reference(&undecided, reference.guards())
11680    })
11681}
11682
11683/// The reference side of a callable visibility question.
11684///
11685/// An include-graph walk and a whole-file arity activation ask the question
11686/// without one reference position, so they carry no `position` and therefore no
11687/// guard environment.
11688struct CallableReferenceContext<'a> {
11689    file: &'a ProjectFile,
11690    position: Option<CallableReferencePosition<'a>>,
11691}
11692
11693/// One reference position plus its preprocessor guard environment. The
11694/// environment is computed on demand because most declarations carry no
11695/// non-trivial guard.
11696struct CallableReferencePosition<'a> {
11697    prepared: &'a PreparedSyntaxTree,
11698    byte: usize,
11699    guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
11700}
11701
11702impl CallableReferenceContext<'_> {
11703    fn is_c(&self) -> bool {
11704        self.file
11705            .rel_path()
11706            .extension()
11707            .and_then(|extension| extension.to_str())
11708            == Some("c")
11709    }
11710
11711    fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
11712        let position = self.position.as_ref()?;
11713        position
11714            .guards
11715            .get_or_init(|| {
11716                position
11717                    .prepared
11718                    .tree()
11719                    .root_node()
11720                    .descendant_for_byte_range(position.byte, position.byte.saturating_add(1))
11721                    .and_then(|node| {
11722                        preprocessor_guard_environment(node, position.prepared.source())
11723                    })
11724            })
11725            .as_ref()
11726    }
11727}
11728
11729/// The undecidable preprocessor guards `node` stands under, or `None` when its
11730/// conditional context excludes it from `reference`'s translation unit outright:
11731/// a false constant, a condition this analyzer cannot read, or a `__cplusplus`
11732/// branch the reference's language never takes.
11733///
11734/// Collecting one guard per enclosing conditional makes the whole walk a
11735/// conjunction of the declaration's requirements. Which comparison those
11736/// requirements then face depends on where the declaration lives: a same-file
11737/// declaration shares the reference's configuration, so the reference's active
11738/// guards must imply them, while a foreign file resolves its own conditionals
11739/// and only has to stay compatible with the reference.
11740fn callable_declaration_guard_requirements(
11741    node: Node<'_>,
11742    source: &str,
11743    reference: &CallableReferenceContext<'_>,
11744) -> Option<HashSet<PreprocessorGuard>> {
11745    let reference_is_c = reference.is_c();
11746    let mut required = HashSet::default();
11747    let mut ancestor = node.parent();
11748    while let Some(conditional) = ancestor {
11749        if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
11750            && !is_file_covering_include_guard(conditional, source)
11751            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11752            && preprocessor_conditional_contains_descendant(conditional, node)
11753        {
11754            let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
11755            match guard {
11756                PreprocessorGuard::Constant(true) => {}
11757                PreprocessorGuard::Constant(false) => return None,
11758                PreprocessorGuard::Defined(name) if name == "__cplusplus" => {
11759                    if reference_is_c {
11760                        return None;
11761                    }
11762                }
11763                PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
11764                    if !reference_is_c {
11765                        return None;
11766                    }
11767                }
11768                guard => {
11769                    required.insert(guard);
11770                }
11771            }
11772        }
11773        ancestor = conditional.parent();
11774    }
11775    Some(required)
11776}
11777
11778/// Whether a declaration in the reference's own file is co-active with the
11779/// reference: one translation unit resolves every conditional the same way, so
11780/// the reference's active guards must imply the declaration's requirements.
11781///
11782/// The exception is a declaration in one branch of a completed `#if`/`#else`
11783/// family the reference follows: the reference's own position settles nothing
11784/// about that family, so a compatible branch is one resolution the source
11785/// presents at the reference (#3297), exactly as
11786/// [`VisibilityIndex::external_type_candidate_visible_in_context`] reads a type
11787/// declaration there.
11788fn callable_preprocessor_context_is_visible_for_reference(
11789    node: Node<'_>,
11790    source: &str,
11791    reference: &CallableReferenceContext<'_>,
11792) -> bool {
11793    let Some(required) = callable_declaration_guard_requirements(node, source, reference) else {
11794        return false;
11795    };
11796    if required.is_empty() || guard_requirements_hold_at_reference(&required, reference.guards()) {
11797        return true;
11798    }
11799    reference.position.as_ref().is_some_and(|position| {
11800        guards_compatible_at_reference(&required, reference.guards())
11801            && declaration_follows_completed_family_before_reference(node, source, position.byte)
11802    })
11803}
11804
11805fn flattened_macro_namespace_declaration_matches(
11806    analyzer: &CppGraphSource<'_>,
11807    _cpp: &dyn CppSource,
11808    reference_file: &ProjectFile,
11809    visible_declaration: &CodeUnit,
11810    qualified_candidate: &CodeUnit,
11811    reference_byte: usize,
11812) -> bool {
11813    // Namespace-opening macros can leave tree-sitter unable to retain the
11814    // namespace owner after a later recovery point. In that shape the forward
11815    // declaration is indexed at translation-unit scope, while the definition
11816    // still has its qualified owner. Require all surviving structural evidence
11817    // before treating the declaration as activation for that definition.
11818    if visible_declaration.kind() != qualified_candidate.kind()
11819        || visible_declaration.identifier() != qualified_candidate.identifier()
11820        || visible_declaration.signature() != qualified_candidate.signature()
11821        || !visible_declaration.package_name().is_empty()
11822        || qualified_candidate.package_name().is_empty()
11823    {
11824        return false;
11825    }
11826
11827    let Some(occurrences) = analyzer.declaration_source_occurrences(visible_declaration) else {
11828        return false;
11829    };
11830    occurrences.into_iter().any(|occurrence| {
11831        occurrence
11832            .displaced_namespace_closing_brace
11833            .is_some_and(|closing_brace| {
11834                visible_declaration.source() != reference_file || closing_brace < reference_byte
11835            })
11836    })
11837}
11838
11839pub(crate) fn flattened_macro_namespace_components(
11840    declaration: Node<'_>,
11841    source: &str,
11842) -> Option<Vec<String>> {
11843    flattened_macro_function_namespace_components(declaration, source)
11844        .or_else(|| flattened_macro_error_namespace_components(declaration, source))
11845}
11846
11847fn flattened_macro_function_namespace_components(
11848    declaration: Node<'_>,
11849    source: &str,
11850) -> Option<Vec<String>> {
11851    let body = declaration
11852        .parent()
11853        .filter(|parent| parent.kind() == "compound_statement")?;
11854    let function = body.parent()?;
11855    if function.child_by_field_name("body") != Some(body) {
11856        return None;
11857    }
11858    let namespace_name = recovered_macro_namespace_name(function, source)?;
11859    let mut components = enclosing_namespace_components(declaration, source)?;
11860    components.push(namespace_name);
11861    Some(components)
11862}
11863
11864/// The namespace name a namespace-opening macro token displaced into a
11865/// synthetic `function_definition`, or `None` when `function` is not that
11866/// recovery shape.
11867///
11868/// `ABSL_NAMESPACE_BEGIN` (or `FMT_BEGIN_NAMESPACE`, ...) immediately before
11869/// `namespace x {` leaves tree-sitter with a `function_definition` whose type is
11870/// the macro token, whose declarator is the namespace name behind an `ERROR`
11871/// holding the `namespace` keyword, and whose body spans the whole namespace
11872/// region. The matching `*_NAMESPACE_END` sibling is what separates the recovery
11873/// artifact from a real function definition.
11874fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
11875    if function.kind() != "function_definition" || !function.has_error() {
11876        return None;
11877    }
11878    let body = function
11879        .child_by_field_name("body")
11880        .filter(|body| body.kind() == "compound_statement")?;
11881    let mut cursor = function.walk();
11882    let prefix = function
11883        .named_children(&mut cursor)
11884        .take_while(|child| child.start_byte() < body.start_byte())
11885        .filter(|child| child.kind() != "comment")
11886        .collect::<Vec<_>>();
11887    let begin_index = prefix.iter().rposition(|child| {
11888        flattened_macro_sentinel_name(*child, source)
11889            .is_some_and(|name| is_namespace_begin_sentinel(&name))
11890    })?;
11891    let mut identifiers = Vec::new();
11892    let mut stack = prefix[begin_index + 1..]
11893        .iter()
11894        .rev()
11895        .copied()
11896        .collect::<Vec<_>>();
11897    while let Some(current) = stack.pop() {
11898        if let Some(identifier) = direct_cpp_identifier_name(current, source) {
11899            identifiers.push(identifier);
11900            continue;
11901        }
11902        let mut cursor = current.walk();
11903        let children = current.named_children(&mut cursor).collect::<Vec<_>>();
11904        stack.extend(children.into_iter().rev());
11905    }
11906    let [keyword, namespace_name] = identifiers.as_slice() else {
11907        return None;
11908    };
11909    if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
11910    {
11911        return None;
11912    }
11913    let mut next = function.next_named_sibling();
11914    let next = loop {
11915        let candidate = next?;
11916        next = candidate.next_named_sibling();
11917        if candidate.kind() != "comment" {
11918            break candidate;
11919        }
11920    };
11921    flattened_macro_sentinel_name(next, source)
11922        .is_some_and(|name| is_namespace_end_sentinel(&name))
11923        .then(|| namespace_name.clone())
11924}
11925
11926/// A `function_definition` that exists only because tree-sitter recovered a
11927/// macro-decorated class head or a namespace-opening macro token. A declaration
11928/// in such a body keeps class or namespace scope, so a scope walk must step over
11929/// the wrapper instead of treating the declaration as block local.
11930pub(crate) fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
11931    crate::declarations::is_recovered_exported_class_container(node, source)
11932        || crate::declarations::is_recovered_fragmented_partial_specialization_container(
11933            node, source,
11934        )
11935        || recovered_macro_namespace_name(node, source).is_some()
11936}
11937
11938fn flattened_macro_error_namespace_components(
11939    declaration: Node<'_>,
11940    source: &str,
11941) -> Option<Vec<String>> {
11942    let parent = declaration
11943        .parent()
11944        .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
11945    let mut cursor = parent.walk();
11946    let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
11947    let declaration_index = siblings
11948        .iter()
11949        .position(|candidate| same_node(*candidate, declaration))?;
11950    let begin_index = (0..declaration_index).rev().find(|index| {
11951        flattened_macro_sentinel_name(siblings[*index], source)
11952            .is_some_and(|name| is_namespace_begin_sentinel(&name))
11953    })?;
11954
11955    let significant = siblings[begin_index + 1..declaration_index]
11956        .iter()
11957        .copied()
11958        .filter(|node| node.kind() != "comment")
11959        .collect::<Vec<_>>();
11960    let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
11961        return None;
11962    };
11963    if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
11964        return None;
11965    }
11966    let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
11967    if significant[2..].iter().any(|node| {
11968        flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
11969            is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
11970        })
11971    }) {
11972        return None;
11973    }
11974
11975    let mut saw_namespace_close = false;
11976    for sibling in siblings.iter().skip(declaration_index + 1).copied() {
11977        if sibling.kind() == "comment" {
11978            continue;
11979        }
11980        if !saw_namespace_close {
11981            if direct_unmatched_closing_brace(sibling) {
11982                saw_namespace_close = true;
11983                continue;
11984            }
11985            if flattened_macro_sentinel_name(sibling, source).is_some() {
11986                return None;
11987            }
11988            continue;
11989        }
11990        if !flattened_macro_sentinel_name(sibling, source)
11991            .is_some_and(|name| is_namespace_end_sentinel(&name))
11992        {
11993            return None;
11994        }
11995        let mut components = enclosing_namespace_components(declaration, source)?;
11996        components.push(namespace_name);
11997        return Some(components);
11998    }
11999    None
12000}
12001
12002fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
12003    // At translation-unit scope the trailing `X_NAMESPACE_END` token parses as
12004    // an `expression_statement` with a missing semicolon; inside a namespace
12005    // body the same token stays a bare `type_identifier`.
12006    let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
12007        node.named_child(0)?
12008    } else {
12009        node
12010    };
12011    let candidate = direct_cpp_identifier_name(node, source).or_else(|| {
12012        node.child_by_field_name("type")
12013            .and_then(|type_node| direct_cpp_identifier_name(type_node, source))
12014    })?;
12015    (cpp_export_macro_token(&candidate)
12016        && (is_namespace_begin_sentinel(&candidate) || is_namespace_end_sentinel(&candidate)))
12017    .then_some(candidate)
12018}
12019
12020/// Namespace-opening macros are spelled both ways in the wild:
12021/// `ABSL_NAMESPACE_BEGIN` (abseil, nlohmann) and `FMT_BEGIN_NAMESPACE` (fmt).
12022fn is_namespace_begin_sentinel(name: &str) -> bool {
12023    name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE")
12024}
12025
12026fn is_namespace_end_sentinel(name: &str) -> bool {
12027    name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE")
12028}
12029
12030fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
12031    if node.kind() != "ERROR" || node.named_child_count() != 1 {
12032        return None;
12033    }
12034    let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
12035    (!cpp_export_macro_token(&name)).then_some(name)
12036}
12037
12038fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
12039    if !matches!(
12040        node.kind(),
12041        "identifier" | "namespace_identifier" | "type_identifier"
12042    ) {
12043        return None;
12044    }
12045    let name = normalize_cpp_whitespace(node_text(node, source));
12046    (!name.is_empty()).then_some(name)
12047}
12048
12049fn guard_requirement_sets_match(
12050    left: &[(usize, HashSet<PreprocessorGuard>)],
12051    right: &[(usize, HashSet<PreprocessorGuard>)],
12052) -> bool {
12053    left.len() == right.len()
12054        && left.iter().all(|(_, left_guards)| {
12055            right
12056                .iter()
12057                .any(|(_, right_guards)| left_guards == right_guards)
12058        })
12059        && right.iter().all(|(_, right_guards)| {
12060            left.iter()
12061                .any(|(_, left_guards)| right_guards == left_guards)
12062        })
12063}
12064
12065pub(crate) fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
12066    node.kind() == "ERROR"
12067        && (0..node.child_count())
12068            .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
12069}
12070
12071pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
12072    let mut ancestor = node.parent();
12073    while let Some(parent) = ancestor {
12074        if is_preprocessor_conditional(parent)
12075            && !is_file_covering_include_guard(parent, source)
12076            && !is_split_cpp_language_linkage_wrapper(parent, node, source)
12077        {
12078            return false;
12079        }
12080        ancestor = parent.parent();
12081    }
12082    true
12083}
12084
12085fn is_split_cpp_language_linkage_wrapper(
12086    conditional: Node<'_>,
12087    descendant: Node<'_>,
12088    source: &str,
12089) -> bool {
12090    if conditional.child_by_field_name("alternative").is_some()
12091        || !matches!(
12092            simple_preprocessor_guard(conditional, source),
12093            Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12094        )
12095    {
12096        return false;
12097    }
12098    let mut current = descendant.parent();
12099    let linkage = loop {
12100        let Some(node) = current else {
12101            return false;
12102        };
12103        if node == conditional {
12104            return false;
12105        }
12106        if node.kind() == "linkage_specification" {
12107            break node;
12108        }
12109        current = node.parent();
12110    };
12111    if linkage
12112        .child_by_field_name("value")
12113        .is_none_or(|value| node_text(value, source) != "\"C\"")
12114    {
12115        return false;
12116    }
12117    let Some(body) = linkage.child_by_field_name("body") else {
12118        return false;
12119    };
12120    let closes_opening_branch = (0..body.named_child_count())
12121        .filter_map(|index| body.named_child(index))
12122        .take_while(|child| child.end_byte() <= descendant.start_byte())
12123        .any(|child| {
12124            child.kind() == "preproc_call"
12125                && child
12126                    .child_by_field_name("directive")
12127                    .is_some_and(|directive| node_text(directive, source) == "#endif")
12128        });
12129    let reopens_for_closing_brace = (0..body.named_child_count())
12130        .filter_map(|index| body.named_child(index))
12131        .skip_while(|child| child.start_byte() < descendant.end_byte())
12132        .any(|child| {
12133            matches!(
12134                simple_preprocessor_guard(child, source),
12135                Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12136            ) && (0..child.child_count()).any(|index| {
12137                child
12138                    .child(index)
12139                    .is_some_and(|token| token.kind() == "#endif" && token.is_missing())
12140            })
12141        });
12142    closes_opening_branch && reopens_for_closing_brace
12143}
12144
12145/// The argument list a call-shaped node supplies: `f(args)`, `new T(args)`,
12146/// `T{args}` and the member initializer `: field(args)`, whose grammar gives its
12147/// argument list no field name.
12148pub fn call_arguments_node(node: Node<'_>) -> Option<Node<'_>> {
12149    node.child_by_field_name("arguments")
12150        .or_else(|| node.child_by_field_name("parameters"))
12151        .or_else(|| node.child_by_field_name("value"))
12152        .or_else(|| first_named_child_of_kind(node, "argument_list"))
12153        .or_else(|| first_named_child_of_kind(node, "initializer_list"))
12154}
12155
12156pub fn call_arity(node: Node<'_>) -> usize {
12157    call_arguments_node(node)
12158        .map(|args| argument_children(args).count())
12159        .unwrap_or(0)
12160}
12161
12162pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
12163    let recovered_block_arguments = recovered_block_literal_arguments(node);
12164    (0..node.child_count())
12165        .filter_map(move |index| node.child(index))
12166        .filter(|child| child.is_named() && !child.is_extra())
12167        .flat_map(move |child| {
12168            if let Some((raw, left, right)) = recovered_block_arguments
12169                && child == raw
12170            {
12171                [Some(left), Some(right)]
12172            } else {
12173                [Some(child), None]
12174            }
12175        })
12176        .flatten()
12177}
12178
12179/// Recover the two ordinary C values that the C++ grammar folds into one
12180/// `new_expression` for `callee(new, trailing)`.
12181///
12182/// The malformed node has an exact grammar-owned shape: the anonymous `new`
12183/// token, an extra `ERROR` containing only the comma token, and the trailing
12184/// value in the `type` field.  The caller supplies the compilation-dialect
12185/// proof from [`reference_uses_c_semantics`]; a C++ source therefore never
12186/// reinterprets a real new-expression through this path.
12187pub fn recovered_c_new_expression_arguments(
12188    node: Node<'_>,
12189    uses_c_semantics: bool,
12190) -> Option<[Node<'_>; 2]> {
12191    if !uses_c_semantics || node.kind() != "new_expression" {
12192        return None;
12193    }
12194    let parent = node.parent()?;
12195    if parent.kind() != "argument_list" {
12196        return None;
12197    }
12198    let keyword = node.child(0)?;
12199    let error = node.child(1)?;
12200    let trailing = node.child(2)?;
12201    if node.child(3).is_some()
12202        || keyword.kind() != "new"
12203        || keyword.is_named()
12204        || keyword.child_count() != 0
12205        || error.kind() != "ERROR"
12206        || !error.is_extra()
12207        || error.child_count() != 1
12208        || error.child(0).is_none_or(|comma| comma.kind() != ",")
12209        || node.child_by_field_name("type") != Some(trailing)
12210        || trailing.kind() != "type_identifier"
12211    {
12212        return None;
12213    }
12214    Some([keyword, trailing])
12215}
12216
12217/// The recovered C value covering one focused source range, starting from any
12218/// node within the malformed new-expression.
12219pub fn recovered_c_new_expression_argument_at(
12220    mut node: Node<'_>,
12221    start_byte: usize,
12222    end_byte: usize,
12223    uses_c_semantics: bool,
12224) -> Option<Node<'_>> {
12225    // The recovery only exists for C sources, and the climb below reaches the
12226    // root before it can answer no. `Node::parent` re-descends from the root on
12227    // every step, so leaving the check inside the loop made every scanned node
12228    // of a C++ file pay a full quadratic ancestor walk for an answer settled by
12229    // the file's language (#3097).
12230    if !uses_c_semantics {
12231        return None;
12232    }
12233    loop {
12234        if let Some(arguments) = recovered_c_new_expression_arguments(node, uses_c_semantics) {
12235            return arguments.into_iter().find(|argument| {
12236                argument.start_byte() <= start_byte && end_byte <= argument.end_byte()
12237            });
12238        }
12239        node = node.parent()?;
12240    }
12241}
12242
12243fn recovered_c_keyword_argument_count(
12244    file: &ProjectFile,
12245    call: Node<'_>,
12246    arguments: Node<'_>,
12247    source: &str,
12248) -> usize {
12249    // A C identifier that is a C++ keyword can be displaced twice by the C++
12250    // grammar: first into a direct parameter-list `ERROR(keyword)`, then into
12251    // a direct argument-list `ERROR(',', keyword)`. Match those CST tokens in
12252    // the enclosing C function before restoring the otherwise dropped slot.
12253    if !is_c_source_file(file) || arguments.kind() != "argument_list" {
12254        return 0;
12255    }
12256    let mut ancestor = Some(call);
12257    let function = loop {
12258        let Some(current) = ancestor else {
12259            return 0;
12260        };
12261        if current.kind() == "function_definition" {
12262            break current;
12263        }
12264        ancestor = current.parent();
12265    };
12266    let Some(parameters) = function
12267        .child_by_field_name("declarator")
12268        .and_then(|declarator| declarator.child_by_field_name("parameters"))
12269    else {
12270        return 0;
12271    };
12272    let displaced_parameter_keywords = (0..parameters.child_count())
12273        .filter_map(|index| parameters.child(index))
12274        .filter(|error| error.kind() == "ERROR")
12275        .filter_map(|error| {
12276            let parameter = error.prev_named_sibling()?;
12277            if parameter.kind() != "parameter_declaration"
12278                || parameter.end_byte() != error.start_byte()
12279                || extract_variable_name(parameter, source).is_some()
12280            {
12281                return None;
12282            }
12283            let mut children = (0..error.child_count())
12284                .filter_map(|index| error.child(index))
12285                .filter(|child| !child.is_extra() && !child.is_missing());
12286            let keyword = children.next()?;
12287            (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
12288                .then_some(keyword)
12289        })
12290        .collect::<Vec<_>>();
12291    if displaced_parameter_keywords.is_empty() {
12292        return 0;
12293    }
12294
12295    (0..arguments.child_count())
12296        .filter_map(|index| arguments.child(index))
12297        .filter(|error| error.kind() == "ERROR" && error.is_extra())
12298        .filter(|error| {
12299            let mut children = (0..error.child_count())
12300                .filter_map(|index| error.child(index))
12301                .filter(|child| !child.is_extra() && !child.is_missing());
12302            let Some(comma) = children.next() else {
12303                return false;
12304            };
12305            let Some(keyword) = children.next() else {
12306                return false;
12307            };
12308            children.next().is_none()
12309                && comma.kind() == ","
12310                && !keyword.is_named()
12311                && keyword.child_count() == 0
12312                && displaced_parameter_keywords
12313                    .iter()
12314                    .any(|parameter| parameter.kind_id() == keyword.kind_id())
12315        })
12316        .count()
12317}
12318
12319fn recovered_block_literal_arguments<'tree>(
12320    arguments: Node<'tree>,
12321) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
12322    if arguments.kind() != "argument_list" {
12323        return None;
12324    }
12325    let mut raw_arguments = (0..arguments.child_count())
12326        .filter_map(|index| arguments.child(index))
12327        .filter(|child| child.is_named() && !child.is_extra());
12328    let raw = raw_arguments.next()?;
12329    if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
12330        return None;
12331    }
12332
12333    let left = raw.child_by_field_name("left")?;
12334    if left.is_missing() || left.start_byte() == left.end_byte() {
12335        return None;
12336    }
12337    let right = raw.child_by_field_name("right")?;
12338    if right.kind() != "compound_literal_expression"
12339        || right.is_missing()
12340        || right
12341            .child_by_field_name("type")
12342            .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
12343        || right
12344            .child_by_field_name("value")
12345            .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
12346    {
12347        return None;
12348    }
12349    let has_intervening_error = (0..raw.child_count())
12350        .filter_map(|index| raw.child(index))
12351        .any(|child| {
12352            child.kind() == "ERROR"
12353                && !child.is_missing()
12354                && child.start_byte() >= left.end_byte()
12355                && child.end_byte() <= right.start_byte()
12356        });
12357    has_intervening_error.then_some((raw, left, right))
12358}
12359
12360pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
12361    match node.kind() {
12362        "new_expression" => node
12363            .child_by_field_name("type")
12364            .or_else(|| node.named_child(0)),
12365        "compound_literal_expression" => node.child_by_field_name("type"),
12366        "call_expression" => node.child_by_field_name("function"),
12367        _ => None,
12368    }
12369}
12370
12371/// The structured type named by a C-style cast expression.
12372///
12373/// Tree-sitter wraps the actual type syntax in a `type_descriptor`.  Return its
12374/// structured `type` field so resolution retains qualified and nested syntax
12375/// without reparsing source text.
12376pub fn cast_expression_type_node(node: Node<'_>) -> Option<Node<'_>> {
12377    if node.kind() != "cast_expression" {
12378        return None;
12379    }
12380    let descriptor = node.child_by_field_name("type")?;
12381    if descriptor.kind() == "type_descriptor" {
12382        descriptor.child_by_field_name("type")
12383    } else {
12384        Some(descriptor)
12385    }
12386}
12387
12388/// The expression a field access reads its member from.
12389///
12390/// That is normally the node in the `argument` field. A preprocessor
12391/// conditional inside a call's argument list defeats the grammar: tree-sitter
12392/// ends the argument list at the `#if` line, then reads the text that follows
12393/// `#endif` as a field access on the whole call expression and leaves the real
12394/// receiver in an `ERROR` node between the `argument` child and the operator.
12395/// mbedtls writes exactly that at `library/ssl_tls.c:4999`, where
12396/// `ssl->session->encrypt_then_mac` is one conditionally compiled call argument
12397/// (#2982).
12398///
12399/// `.` and `->` read whatever ends immediately before them in either tree, so
12400/// answer from the operator's position instead of trusting the `argument`
12401/// field, and unwrap the single expression a displaced `ERROR` holds.
12402pub fn cpp_field_expression_receiver(field: Node<'_>) -> Option<Node<'_>> {
12403    debug_assert_eq!(field.kind(), "field_expression");
12404    let operator = field.child_by_field_name("operator")?;
12405    let mut cursor = field.walk();
12406    let receiver = field
12407        .named_children(&mut cursor)
12408        .filter(|child| child.end_byte() <= operator.start_byte())
12409        .last()?;
12410    if receiver.kind() != "ERROR" {
12411        return Some(receiver);
12412    }
12413    (receiver.named_child_count() == 1)
12414        .then(|| receiver.named_child(0))
12415        .flatten()
12416}
12417
12418pub fn field_initializer_constructs_target(
12419    node: Node<'_>,
12420    ctx: &ScanCtx<'_>,
12421    owner: &CodeUnit,
12422) -> bool {
12423    // A qualified name in a constructor initializer denotes a base
12424    // subobject constructor (`namespace::Base(args)`), not a member field.  The
12425    // field-initializer grammar exposes the qualified name as one structured
12426    // `qualified_identifier`; resolve its owner through the same lexical type
12427    // machinery used for ordinary C++ type references before considering the
12428    // initializer a hit.  This keeps an unrelated `namespace::Other(...)`, a
12429    // qualified non-constructor member, and an unresolved owner out of the
12430    // target constructor's inverse usage set.
12431    if first_named_child_of_kind(node, "qualified_identifier").is_some() {
12432        return qualified_base_initializer_constructs_target(node, ctx, owner);
12433    }
12434    let Some(name) = node
12435        .child_by_field_name("name")
12436        .or_else(|| first_named_child_of_kind(node, "field_identifier"))
12437        .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
12438    else {
12439        return false;
12440    };
12441    let field_name = node_text(name, ctx.source);
12442    ctx.visibility
12443        .visible_identifier_candidates(ctx.file, field_name)
12444        .filter(|unit| unit.is_field() && unit.identifier() == field_name)
12445        .any(|unit| field_declares_type(unit, ctx, owner) && field_holds_subobject(unit, ctx))
12446}
12447
12448/// Whether a member initializer for `unit` runs a constructor of the type the
12449/// member declares.
12450///
12451/// Only a member that holds the type by value does. `Owner* p` initialized as
12452/// `p(nullptr)` binds a pointer and `Owner& r` as `r(other)` binds a
12453/// reference; neither runs an `Owner` constructor, even though both declare
12454/// `Owner` as the receiver type a member access through them resolves (#3286).
12455/// A member whose declaration the index cannot decode keeps the by-value
12456/// reading, which is what the declared-type match already assumed.
12457fn field_holds_subobject(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
12458    ctx.visibility
12459        .field_declared_type_fact(&ctx.analyzer, unit)
12460        .is_none_or(|fact| !fact.binds_indirectly)
12461}
12462
12463fn qualified_base_initializer_constructs_target(
12464    node: Node<'_>,
12465    ctx: &ScanCtx<'_>,
12466    owner: &CodeUnit,
12467) -> bool {
12468    let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
12469        return false;
12470    };
12471    let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
12472        return false;
12473    };
12474    let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
12475        return false;
12476    };
12477    let resolves_target = |components: &[String]| {
12478        matches!(
12479            ctx.visibility.resolve_type_components_lexically_for_target(
12480                &ctx.analyzer,
12481                ctx.file,
12482                components,
12483                is_globally_qualified_cpp_name(qualified),
12484                &lexical_scope,
12485                owner,
12486            ),
12487            LexicalTypeResolution::Resolved { unit, .. }
12488                if same_visible_symbol(&unit, owner)
12489        )
12490    };
12491    if resolves_target(&components) {
12492        return true;
12493    }
12494
12495    // Some real-world code spells a base mem-initializer as
12496    // `Base::Base(args)`. In that structured path the final component repeats
12497    // the constructor name; resolve the preceding type path. The terminal
12498    // identity check prevents an arbitrary qualified member from taking this
12499    // route.
12500    components
12501        .last()
12502        .is_some_and(|terminal| terminal == owner.identifier())
12503        && resolves_target(&components[..components.len() - 1])
12504}
12505
12506fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
12507    ctx.visibility
12508        .field_declared_type_fact(&ctx.analyzer, unit)
12509        .is_some_and(|fact| {
12510            ctx.visibility
12511                .resolves_to_type(&ctx.analyzer, ctx.file, &fact.type_text, owner)
12512                || ctx.visibility.resolves_to_type(
12513                    &ctx.analyzer,
12514                    ctx.file,
12515                    &normalize_field_type_text(&fact.type_text),
12516                    owner,
12517                )
12518        })
12519}
12520
12521pub fn field_declared_binding(
12522    analyzer: &CppGraphSource<'_>,
12523    visibility: &VisibilityIndex<'_>,
12524    visible_from: &ProjectFile,
12525    field: &CodeUnit,
12526) -> Option<CppScanBinding> {
12527    let fact = visibility.field_declared_type_fact(analyzer, field)?;
12528    let normalized = normalize_field_type_text(&fact.type_text);
12529    let resolved = visibility.resolve_unique_canonical_type_for_declaration(
12530        analyzer,
12531        visible_from,
12532        field,
12533        &normalized,
12534    );
12535    let resolved = match (resolved, fact.template_arguments.as_deref()) {
12536        (Some(primary), Some(arguments)) => visibility
12537            .resolve_template_arguments(visible_from, primary, arguments)
12538            .ok(),
12539        (resolved, None) => resolved,
12540        (None, Some(_)) => None,
12541    }
12542    .or_else(|| anonymous_aggregate_field_owner(analyzer, visibility, visible_from, field));
12543    Some(CppScanBinding::from_type_name(
12544        normalized,
12545        resolved,
12546        fact.indirection,
12547    ))
12548}
12549
12550/// Whether a member-initializer `field(args)` runs the declared type's
12551/// constructor: only a member that holds the type by value does. `Owner* p`
12552/// initialized as `p(nullptr)` binds a pointer and `Owner& r` as `r(other)`
12553/// binds a reference; neither runs an `Owner` constructor (#3286). A member
12554/// whose declaration the index cannot decode keeps the by-value reading, the
12555/// same assumption the usage scan's declared-type match makes.
12556pub fn field_initializer_holds_subobject(
12557    analyzer: &CppGraphSource<'_>,
12558    visibility: &VisibilityIndex<'_>,
12559    field: &CodeUnit,
12560) -> bool {
12561    visibility
12562        .field_declared_type_fact(analyzer, field)
12563        .is_none_or(|fact| !fact.binds_indirectly)
12564}
12565
12566/// Resolve the receiver type minted for a named declarator on an anonymous C
12567/// aggregate, such as `struct { int r; } c`. The declaration index preserves
12568/// the aggregate as the nested class `Owner$c`, but the field's type fact has
12569/// no spelling that can name that class. Confirm the anonymous aggregate from
12570/// its parsed declaration, then use the owner's structured child relationship
12571/// to recover the one corresponding receiver type.
12572fn anonymous_aggregate_field_owner(
12573    analyzer: &CppGraphSource<'_>,
12574    visibility: &VisibilityIndex<'_>,
12575    visible_from: &ProjectFile,
12576    field: &CodeUnit,
12577) -> Option<CodeUnit> {
12578    let owner = type_owner_of(analyzer, field)?;
12579    if !owner.is_class() {
12580        return None;
12581    }
12582    let declaration = analyzer.get_source(field, false)?;
12583    let mut parser = Parser::new();
12584    parser
12585        .set_language(&tree_sitter_cpp::LANGUAGE.into())
12586        .ok()?;
12587    let tree = parser.parse(&declaration, None)?;
12588    let mut stack = vec![tree.root_node()];
12589    while let Some(node) = stack.pop() {
12590        if matches!(node.kind(), "declaration" | "field_declaration")
12591            && let Some(type_node) = node
12592                .child_by_field_name("type")
12593                .or_else(|| first_type_child(node))
12594            && matches!(type_node.kind(), "struct_specifier" | "union_specifier")
12595            && type_node.child_by_field_name("name").is_none()
12596            && declared_name_binding(node, type_node, field.identifier(), &declaration).is_some()
12597        {
12598            let matches = visibility
12599                .visible_members_for_owner_name(visible_from, &owner, field.identifier())
12600                .into_iter()
12601                .filter(|child| child.is_class() && child.identifier() == field.identifier())
12602                .collect::<Vec<_>>();
12603            return match matches.as_slice() {
12604                [child] => Some((*child).clone()),
12605                _ => None,
12606            };
12607        }
12608        let mut cursor = node.walk();
12609        stack.extend(node.named_children(&mut cursor));
12610    }
12611    None
12612}
12613
12614/// Resolve an anonymous C aggregate's generated owner from its declaration
12615/// range. Anonymous local structs and unions have no type name to enter into
12616/// the visibility index; declaration extraction gives them a structured class
12617/// identity keyed by the aggregate node's exact CST range instead. Matching
12618/// that range keeps nested aggregates and unrelated generated owners out of
12619/// the result without inspecting source text.
12620pub fn anonymous_aggregate_owner(
12621    analyzer: &CppGraphSource<'_>,
12622    file: &ProjectFile,
12623    node: Node<'_>,
12624) -> Option<CodeUnit> {
12625    if !matches!(node.kind(), "struct_specifier" | "union_specifier")
12626        || node.child_by_field_name("name").is_some()
12627    {
12628        return None;
12629    }
12630    let mut candidates = analyzer
12631        .declarations(file)
12632        .into_iter()
12633        .filter(|candidate| {
12634            candidate.is_class()
12635                && analyzer.ranges(candidate).into_iter().any(|range| {
12636                    range.start_byte == node.start_byte() && range.end_byte == node.end_byte()
12637                })
12638        })
12639        .collect::<Vec<_>>();
12640    candidates.sort_by_key(|candidate| candidate.fq_name());
12641    candidates.dedup();
12642    match candidates.as_slice() {
12643        [candidate] => Some(candidate.clone()),
12644        _ => None,
12645    }
12646}
12647
12648/// The one logical type the candidates name, or why they do not name one.
12649fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
12650    let Some(first) = candidates.first() else {
12651        return Err(TypeCandidateFailure::Unresolvable);
12652    };
12653    if candidates
12654        .iter()
12655        .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
12656    {
12657        Ok((*first).clone())
12658    } else {
12659        Err(TypeCandidateFailure::Ambiguous)
12660    }
12661}
12662
12663fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
12664    logical_type_candidate(candidates).ok()
12665}
12666
12667fn unique_type_candidate_preserving_alias(
12668    analyzer: &CppGraphSource<'_>,
12669    file: &ProjectFile,
12670    candidates: &[&CodeUnit],
12671) -> Option<CodeUnit> {
12672    let first = *candidates.first()?;
12673    if declared_type_alias(analyzer, first) {
12674        return candidates
12675            .iter()
12676            .all(|candidate| {
12677                declared_type_alias(analyzer, candidate)
12678                    && candidate.kind() == first.kind()
12679                    && candidate.fq_name() == first.fq_name()
12680                    && candidate.source() == first.source()
12681            })
12682            .then(|| first.clone());
12683    }
12684    if analyzer.reference_uses_c_semantics(file)
12685        && first.is_class()
12686        && indexed_c_tag_kind(analyzer, first).is_some()
12687    {
12688        let mut full_source = None;
12689        let mut tag_kind = None;
12690        for candidate in candidates.iter().copied() {
12691            let candidate_tag_kind = indexed_c_tag_kind(analyzer, candidate)?;
12692            if tag_kind
12693                .replace(candidate_tag_kind)
12694                .is_some_and(|existing| existing != candidate_tag_kind)
12695            {
12696                return None;
12697            }
12698            if cpp_class_declaration_strength(analyzer, candidate)
12699                == CppClassDeclarationStrength::Full
12700                && full_source
12701                    .replace(candidate.source())
12702                    .is_some_and(|existing| existing != candidate.source())
12703            {
12704                return None;
12705            }
12706        }
12707    }
12708    candidates
12709        .iter()
12710        .all(|candidate| {
12711            !declared_type_alias(analyzer, candidate)
12712                && candidate.kind() == first.kind()
12713                && candidate.fq_name() == first.fq_name()
12714        })
12715        .then(|| first.clone())
12716}
12717
12718pub(crate) fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
12719    analyzer
12720        .type_alias_provider()
12721        .is_some_and(|provider| provider.is_type_alias(unit))
12722}
12723
12724pub fn field_declared_type_binding(
12725    analyzer: &CppGraphSource<'_>,
12726    visibility: &VisibilityIndex<'_>,
12727    visible_from: &ProjectFile,
12728    field: &CodeUnit,
12729) -> Option<(String, Option<CodeUnit>, i32)> {
12730    let fact = visibility.field_declared_type_fact(analyzer, field)?;
12731    let normalized = normalize_field_type_text(&fact.type_text);
12732    let primary = visibility.resolve_unique_canonical_type_for_declaration(
12733        analyzer,
12734        visible_from,
12735        field,
12736        &normalized,
12737    );
12738    let resolved = match (primary, fact.template_arguments.as_deref()) {
12739        (Some(primary), Some(arguments)) => visibility
12740            .resolve_template_arguments(visible_from, primary, arguments)
12741            .ok(),
12742        (resolved, None) => resolved,
12743        (None, Some(_)) => None,
12744    };
12745    Some((normalized, resolved, fact.indirection))
12746}
12747
12748fn decode_field_declared_type_fact(
12749    analyzer: &CppGraphSource<'_>,
12750    field: &CodeUnit,
12751) -> Option<DeclaredFieldTypeFact> {
12752    if let Some(facts) = analyzer.declaration_source_properties(field)
12753        && let Some(first) = facts.first().and_then(|fact| fact.field_type.as_ref())
12754        && facts
12755            .iter()
12756            .all(|fact| fact.field_type.as_ref() == Some(first))
12757    {
12758        return Some(first.clone());
12759    }
12760    let Some(declaration) = analyzer.get_source(field, false) else {
12761        return decode_indexed_field_declared_type_fact(analyzer, field);
12762    };
12763    let mut parser = Parser::new();
12764    parser
12765        .set_language(&tree_sitter_cpp::LANGUAGE.into())
12766        .ok()?;
12767    // A field's indexed source is stored without its surrounding class body.
12768    // Give tree-sitter that grammatical context before checking recovery-only
12769    // field shapes such as `PyObject_HEAD Imaging image;`.
12770    let contextual_declaration = format!("struct __bifrost_field_context {{ {declaration} }};");
12771    let contextual_tree = parser.parse(&contextual_declaration, None)?;
12772    let mut stack = vec![contextual_tree.root_node()];
12773    while let Some(node) = stack.pop() {
12774        if let Some(recovered) = recovered_pyobject_head_field(node, &contextual_declaration)
12775            && node_text(recovered.name, &contextual_declaration) == field.identifier()
12776        {
12777            return Some(DeclaredFieldTypeFact {
12778                type_text: node_text(recovered.type_node, &contextual_declaration).to_string(),
12779                indirection: recovered.pointer_depth(),
12780                binds_indirectly: recovered.pointer_depth() > 0,
12781                template_arguments: None,
12782            });
12783        }
12784        if let Some(recovered) =
12785            recovered_function_like_field_declarator(node, &contextual_declaration)
12786            && node_text(recovered.name, &contextual_declaration) == field.identifier()
12787        {
12788            let type_node = node
12789                .child_by_field_name("type")
12790                .or_else(|| first_type_child(node))?;
12791            return Some(DeclaredFieldTypeFact {
12792                type_text: node_text(type_node, &contextual_declaration).to_string(),
12793                indirection: recovered.pointer_depth(),
12794                // A function-like declarator names a callable member, never a
12795                // subobject of the declared return type.
12796                binds_indirectly: true,
12797                template_arguments: cpp_template_reference_arguments(
12798                    type_node,
12799                    &contextual_declaration,
12800                ),
12801            });
12802        }
12803        if let Some(fact) =
12804            decode_declared_field_type_node(node, field.identifier(), &contextual_declaration)
12805        {
12806            return Some(fact);
12807        }
12808        let mut cursor = node.walk();
12809        stack.extend(node.named_children(&mut cursor));
12810    }
12811    let tree = parser.parse(&declaration, None)?;
12812    let mut stack = vec![tree.root_node()];
12813    while let Some(node) = stack.pop() {
12814        if let Some(fact) = decode_declared_field_type_node(node, field.identifier(), &declaration)
12815        {
12816            return Some(fact);
12817        }
12818        let mut cursor = node.walk();
12819        stack.extend(node.named_children(&mut cursor));
12820    }
12821    None
12822}
12823
12824/// Decode a field whose generated owner prevents the ordinary source lookup
12825/// from selecting a standalone declaration. The indexed ranges still point
12826/// into the physical syntax tree, so recover the enclosing declaration from
12827/// that structure and apply the same declarator decoder to it.
12828fn decode_indexed_field_declared_type_fact(
12829    analyzer: &CppGraphSource<'_>,
12830    field: &CodeUnit,
12831) -> Option<DeclaredFieldTypeFact> {
12832    let cpp = analyzer.cpp?;
12833    let prepared = cpp.prepared_syntax(analyzer.token, field.source())?;
12834    let source = prepared.source();
12835    let root = prepared.tree().root_node();
12836    for range in analyzer.ranges(field) {
12837        let end = range.start_byte.saturating_add(1).min(source.len());
12838        let mut current = root.descendant_for_byte_range(range.start_byte, end);
12839        while let Some(node) = current {
12840            if matches!(node.kind(), "declaration" | "field_declaration")
12841                && let Some(fact) =
12842                    decode_declared_field_type_node(node, field.identifier(), source)
12843            {
12844                return Some(fact);
12845            }
12846            current = node.parent();
12847        }
12848    }
12849    None
12850}
12851
12852fn decode_declared_field_type_node(
12853    node: Node<'_>,
12854    field_name: &str,
12855    source: &str,
12856) -> Option<DeclaredFieldTypeFact> {
12857    if !matches!(node.kind(), "declaration" | "field_declaration") {
12858        return None;
12859    }
12860    let type_node = node
12861        .child_by_field_name("type")
12862        .or_else(|| first_type_child(node))?;
12863    let binding = declared_name_binding(node, type_node, field_name, source)?;
12864    let declared_type = if matches!(
12865        type_node.kind(),
12866        "class_specifier" | "struct_specifier" | "union_specifier"
12867    ) {
12868        type_node.child_by_field_name("name")
12869    } else {
12870        Some(type_node)
12871    };
12872    Some(DeclaredFieldTypeFact {
12873        type_text: declared_type.map_or_else(
12874            || field_name.to_string(),
12875            |declared_type| node_text(declared_type, source).to_string(),
12876        ),
12877        indirection: binding.pointer_depth,
12878        binds_indirectly: binding.indirect,
12879        template_arguments: declared_type
12880            .and_then(|declared_type| cpp_template_reference_arguments(declared_type, source)),
12881    })
12882}
12883
12884/// Text of the type that a C or C++ alias declaration names, read from the
12885/// `type_definition` or `alias_declaration` node's `type` field.
12886///
12887/// The declaration text is never scanned. A function-pointer typedef
12888/// interleaves its aliased type with its declarator (`typedef R (*F)(int)`),
12889/// so no prefix or suffix of the spelling isolates the target.
12890///
12891/// An alias whose declarator is a function declarator names a function type:
12892/// `typedef R F(int)`, `typedef R (*F)(int)`, `typedef R *F(int)`, and
12893/// `using F = R (*)(int)`. The analyzer's type model names declared types only,
12894/// so such an alias has no canonical target. Its `type` field holds the return
12895/// type `R`, which is a different type from the alias, so this returns `None`
12896/// rather than that return type.
12897pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
12898    let mut parser = Parser::new();
12899    parser
12900        .set_language(&tree_sitter_cpp::LANGUAGE.into())
12901        .ok()?;
12902    let tree = parser.parse(declaration, None)?;
12903    let mut stack = vec![tree.root_node()];
12904    while let Some(node) = stack.pop() {
12905        let type_node = match node.kind() {
12906            "type_definition" => {
12907                let mut cursor = node.walk();
12908                if node
12909                    .children_by_field_name("declarator", &mut cursor)
12910                    .any(declarator_names_function_type)
12911                {
12912                    return None;
12913                }
12914                node.child_by_field_name("type")?
12915            }
12916            "alias_declaration" => {
12917                let type_node = node.child_by_field_name("type")?;
12918                if type_node
12919                    .child_by_field_name("declarator")
12920                    .is_some_and(declarator_names_function_type)
12921                {
12922                    return None;
12923                }
12924                type_node
12925            }
12926            _ => {
12927                let mut cursor = node.walk();
12928                let children = node.named_children(&mut cursor).collect::<Vec<_>>();
12929                stack.extend(children.into_iter().rev());
12930                continue;
12931            }
12932        };
12933        return Some(node_text(type_node, declaration).to_string());
12934    }
12935    None
12936}
12937
12938/// True when an alias declarator names a function type.
12939///
12940/// The declarator chain is walked through the `declarator` field, so the
12941/// parameter list -- a sibling field -- is never entered and a parameter's own
12942/// function declarator cannot be mistaken for the alias's.
12943pub(crate) fn declarator_names_function_type(declarator: Node<'_>) -> bool {
12944    let mut current = Some(declarator);
12945    while let Some(node) = current {
12946        match node.kind() {
12947            "function_declarator" | "abstract_function_declarator" => return true,
12948            "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
12949                current = node.named_child(0);
12950            }
12951            _ => current = node.child_by_field_name("declarator"),
12952        }
12953    }
12954    false
12955}
12956
12957fn decode_structured_alias_target(
12958    analyzer: &CppGraphSource<'_>,
12959    unit: &CodeUnit,
12960) -> Option<StructuredAliasTarget> {
12961    let facts = analyzer.declaration_source_properties(unit)?;
12962    let first = facts.first()?.alias_target.as_ref()?;
12963    facts
12964        .iter()
12965        .all(|fact| fact.alias_target.as_ref() == Some(first))
12966        .then(|| first.clone())
12967}
12968
12969pub(crate) fn structured_alias_type_target(
12970    mut type_node: Node<'_>,
12971    source: &str,
12972) -> Option<StructuredAliasTarget> {
12973    while type_node.kind() == "type_descriptor" {
12974        type_node = type_node.child_by_field_name("type")?;
12975    }
12976    if type_node.kind() == "primitive_type" {
12977        return Some(StructuredAliasTarget::Builtin);
12978    }
12979    if matches!(
12980        type_node.kind(),
12981        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
12982    ) {
12983        type_node = type_node.child_by_field_name("name")?;
12984    }
12985    let global = type_node.child_by_field_name("scope").is_none()
12986        && type_node.child(0).is_some_and(|child| child.kind() == "::");
12987    let mut components = Vec::new();
12988    append_structured_type_components(type_node, source, &mut components)?;
12989    let arguments = cpp_template_reference_arguments(type_node, source);
12990    (!components.is_empty()).then_some(StructuredAliasTarget::Named {
12991        components,
12992        global,
12993        arguments,
12994    })
12995}
12996
12997fn append_structured_type_components(
12998    node: Node<'_>,
12999    source: &str,
13000    out: &mut Vec<String>,
13001) -> Option<()> {
13002    match node.kind() {
13003        "identifier" | "namespace_identifier" | "type_identifier" => {
13004            out.push(node_text(node, source).to_string());
13005            Some(())
13006        }
13007        "template_type" | "template_function" => {
13008            append_structured_type_components(node.child_by_field_name("name")?, source, out)
13009        }
13010        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
13011            if let Some(scope) = node.child_by_field_name("scope") {
13012                append_structured_type_components(scope, source, out)?;
13013            }
13014            append_structured_type_components(node.child_by_field_name("name")?, source, out)
13015        }
13016        _ => None,
13017    }
13018}
13019
13020/// How the declarator that names a declared entity binds the declaration's
13021/// type.
13022pub(crate) struct DeclaredNameBinding {
13023    /// Pointer declarators between the type and the name.
13024    pub pointer_depth: i32,
13025    /// The name binds the declared type through a pointer or a reference, so
13026    /// the declared entity holds no subobject of that type. An array of the
13027    /// type does hold subobjects, so it is not indirect.
13028    pub indirect: bool,
13029}
13030
13031pub(crate) fn declared_name_binding(
13032    declaration: Node<'_>,
13033    type_node: Node<'_>,
13034    field_name: &str,
13035    source: &str,
13036) -> Option<DeclaredNameBinding> {
13037    let mut stack = Vec::new();
13038    let mut cursor = declaration.walk();
13039    stack.extend(
13040        declaration
13041            .named_children(&mut cursor)
13042            .filter(|child| !same_node(*child, type_node)),
13043    );
13044    while let Some(node) = stack.pop() {
13045        if matches!(node.kind(), "identifier" | "field_identifier")
13046            && node_text(node, source) == field_name
13047        {
13048            let mut binding = DeclaredNameBinding {
13049                pointer_depth: 0,
13050                indirect: false,
13051            };
13052            let mut current = node.parent();
13053            while let Some(parent) = current {
13054                if same_node(parent, declaration) {
13055                    return Some(binding);
13056                }
13057                match parent.kind() {
13058                    "pointer_declarator" => {
13059                        binding.pointer_depth += 1;
13060                        binding.indirect = true;
13061                    }
13062                    "reference_declarator" => binding.indirect = true,
13063                    _ => {}
13064                }
13065                current = parent.parent();
13066            }
13067            return None;
13068        }
13069        let mut cursor = node.walk();
13070        stack.extend(node.named_children(&mut cursor));
13071    }
13072    None
13073}
13074
13075fn normalize_field_type_text(type_text: &str) -> String {
13076    const FIELD_SPECIFIERS: [&str; 8] = [
13077        "extern ",
13078        "static ",
13079        "mutable ",
13080        "constexpr ",
13081        "constinit ",
13082        "inline ",
13083        "volatile ",
13084        "const ",
13085    ];
13086
13087    let mut normalized = normalize_type_text(type_text);
13088    loop {
13089        let Some(stripped) = FIELD_SPECIFIERS
13090            .iter()
13091            .find_map(|specifier| normalized.strip_prefix(specifier))
13092        else {
13093            return normalized;
13094        };
13095        normalized = normalize_type_text(stripped);
13096    }
13097}
13098
13099pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
13100    let Some(type_node) = node.child_by_field_name("type") else {
13101        return false;
13102    };
13103    ctx.visibility.resolves_to_type(
13104        &ctx.analyzer,
13105        ctx.file,
13106        node_text(type_node, ctx.source),
13107        owner,
13108    )
13109}
13110
13111pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13112    !ctx.analyzer
13113        .declarations(ctx.file)
13114        .into_iter()
13115        .filter(|unit| unit.is_function())
13116        .any(|unit| {
13117            ctx.analyzer.ranges(&unit).iter().any(|range| {
13118                node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
13119            })
13120        })
13121}
13122
13123/// How a `T var ...;` declaration initializes its object.
13124pub enum DeclarationConstructorInitializer<'tree> {
13125    /// Direct initialization, `T var(args)` or `T var{args}`: the argument list
13126    /// the declaration hands the constructor.
13127    Arguments(Node<'tree>),
13128    /// Copy initialization from one expression, `T var = expr`, which supplies a
13129    /// single constructor argument without spelling an argument list.
13130    Expression(Node<'tree>),
13131    /// `T var;`, which names no constructor argument at all.
13132    Empty,
13133}
13134
13135pub fn declaration_constructor_initializer(
13136    node: Node<'_>,
13137) -> DeclarationConstructorInitializer<'_> {
13138    let mut cursor = node.walk();
13139    for child in node.named_children(&mut cursor) {
13140        if child.kind() == "init_declarator" {
13141            let Some(value) = child
13142                .child_by_field_name("value")
13143                .or_else(|| first_named_child_of_kind(child, "initializer_list"))
13144                .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
13145            else {
13146                return DeclarationConstructorInitializer::Empty;
13147            };
13148            return match value.kind() {
13149                "argument_list" | "initializer_list" => {
13150                    DeclarationConstructorInitializer::Arguments(value)
13151                }
13152                "compound_literal_expression" => call_arguments_node(value)
13153                    .map_or(DeclarationConstructorInitializer::Empty, |arguments| {
13154                        DeclarationConstructorInitializer::Arguments(arguments)
13155                    }),
13156                _ => DeclarationConstructorInitializer::Expression(value),
13157            };
13158        }
13159        if let Some(declarator) = declaration_declarator(node, child) {
13160            return declarator_parameters(declarator)
13161                .map_or(DeclarationConstructorInitializer::Empty, |parameters| {
13162                    DeclarationConstructorInitializer::Arguments(parameters)
13163                });
13164        }
13165    }
13166    DeclarationConstructorInitializer::Empty
13167}
13168
13169pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
13170    match declaration_constructor_initializer(node) {
13171        DeclarationConstructorInitializer::Arguments(arguments) => {
13172            argument_children(arguments).count()
13173        }
13174        DeclarationConstructorInitializer::Expression(_) => 1,
13175        DeclarationConstructorInitializer::Empty => 0,
13176    }
13177}
13178
13179/// The parameter list of the innermost declarator, which is where a
13180/// `T var(args)` declaration parsed as a function declarator keeps the
13181/// constructor arguments.
13182fn declarator_parameters(node: Node<'_>) -> Option<Node<'_>> {
13183    let mut current = node;
13184    loop {
13185        if let Some(parameters) = current.child_by_field_name("parameters") {
13186            return Some(parameters);
13187        }
13188        current = current.child_by_field_name("declarator")?;
13189    }
13190}
13191
13192pub(super) fn first_named_child_of_kind<'tree>(
13193    node: Node<'tree>,
13194    kind: &str,
13195) -> Option<Node<'tree>> {
13196    let mut cursor = node.walk();
13197    node.named_children(&mut cursor)
13198        .find(|child| child.kind() == kind)
13199}
13200
13201fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
13202    let mut stack = vec![root];
13203    while let Some(node) = stack.pop() {
13204        if node.kind() == kind {
13205            return Some(node);
13206        }
13207        push_named_children_reversed(node, &mut stack);
13208    }
13209    None
13210}
13211
13212fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
13213    if node.kind() == "identifier" {
13214        return true;
13215    }
13216    if node.kind() == "parenthesized_expression" {
13217        return false;
13218    }
13219    if node.kind() == "call_expression" {
13220        return node
13221            .child_by_field_name("function")
13222            .is_some_and(|function| function.kind() == "identifier");
13223    }
13224    let mut stack = vec![node];
13225    while let Some(descendant) = stack.pop() {
13226        if descendant != node && descendant.kind() == "parenthesized_expression" {
13227            continue;
13228        }
13229        if descendant.kind() == "identifier" {
13230            return true;
13231        }
13232        if descendant.kind() == "call_expression" {
13233            if descendant
13234                .child_by_field_name("function")
13235                .is_some_and(|function| function.kind() == "identifier")
13236            {
13237                return true;
13238            }
13239            continue;
13240        }
13241        push_named_children_reversed(descendant, &mut stack);
13242    }
13243    false
13244}
13245
13246fn macro_expansion_shape_is_safe(
13247    node: Node<'_>,
13248    source: &str,
13249    parameters: &[String],
13250    environment: &MacroEnvironment,
13251) -> bool {
13252    if matches!(node.kind(), "identifier" | "parenthesized_expression") {
13253        return true;
13254    }
13255    if node.kind() == "call_expression" {
13256        let Some(function) = node.child_by_field_name("function") else {
13257            return true;
13258        };
13259        if function.kind() != "identifier" {
13260            return true;
13261        }
13262        let function_name = node_text(function, source);
13263        if parameters
13264            .iter()
13265            .any(|parameter| parameter == function_name)
13266        {
13267            return false;
13268        }
13269        if !environment.may_bind(function_name) {
13270            return true;
13271        }
13272        let Some(arguments) = node.child_by_field_name("arguments") else {
13273            return false;
13274        };
13275        return argument_children(arguments).all(|argument| {
13276            if argument.kind() == "identifier"
13277                && parameters
13278                    .iter()
13279                    .any(|parameter| parameter == node_text(argument, source))
13280            {
13281                return false;
13282            }
13283            macro_expansion_shape_is_safe(argument, source, parameters, environment)
13284        });
13285    }
13286    let mut stack = vec![node];
13287    while let Some(descendant) = stack.pop() {
13288        if descendant != node {
13289            if descendant.kind() == "parenthesized_expression" {
13290                continue;
13291            }
13292            if descendant.kind() == "call_expression" {
13293                let expands = descendant
13294                    .child_by_field_name("function")
13295                    .filter(|function| function.kind() == "identifier")
13296                    .is_some_and(|function| environment.may_bind(node_text(function, source)));
13297                if expands {
13298                    return false;
13299                }
13300                continue;
13301            }
13302        }
13303        if descendant.kind() == "identifier" {
13304            let identifier = node_text(descendant, source);
13305            if parameters.iter().any(|parameter| parameter == identifier)
13306                || environment.may_bind(identifier)
13307            {
13308                return false;
13309            }
13310        }
13311        push_named_children_reversed(descendant, &mut stack);
13312    }
13313    true
13314}
13315
13316fn structured_include_path<'a>(path: Node<'_>, source: &'a str) -> Option<&'a str> {
13317    let text = node_text(path, source);
13318    match path.kind() {
13319        "string_literal" => text.strip_prefix('"')?.strip_suffix('"'),
13320        "system_lib_string" => text.strip_prefix('<')?.strip_suffix('>'),
13321        _ => None,
13322    }
13323}
13324
13325fn collect_structured_include_facts(prepared: &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]> {
13326    let source = prepared.source();
13327    let mut facts = Vec::new();
13328    let mut nodes = vec![prepared.tree().root_node()];
13329    while let Some(node) = nodes.pop() {
13330        if node.kind() == "preproc_include" {
13331            let Some(path) = node
13332                .child_by_field_name("path")
13333                .and_then(|path| structured_include_path(path, source))
13334                .map(str::to_owned)
13335            else {
13336                continue;
13337            };
13338            facts.push(StructuredIncludeFact {
13339                start_byte: node.start_byte(),
13340                end_byte: node.end_byte(),
13341                path,
13342            });
13343            continue;
13344        }
13345        push_named_children_reversed(node, &mut nodes);
13346    }
13347    Arc::from(facts.into_boxed_slice())
13348}
13349
13350fn has_unresolved_include_visible_before_in_prepared(
13351    file: &ProjectFile,
13352    prepared: &PreparedSyntaxTree,
13353    include_targets: &IncludeTargetIndex,
13354    facts: &[StructuredIncludeFact],
13355    before_byte: usize,
13356) -> bool {
13357    let guards = OnceCell::new();
13358    let reference = CallableReferenceContext {
13359        file,
13360        position: Some(CallableReferencePosition {
13361            prepared,
13362            byte: before_byte,
13363            guards: &guards,
13364        }),
13365    };
13366    let root = prepared.tree().root_node();
13367    facts
13368        .iter()
13369        .filter(|fact| fact.end_byte <= before_byte)
13370        .any(|fact| {
13371            let node = root
13372                .descendant_for_byte_range(fact.start_byte, fact.end_byte)
13373                .expect("structured include fact range must be in prepared tree");
13374            assert_eq!(
13375                node.kind(),
13376                "preproc_include",
13377                "structured include fact range must identify its include node"
13378            );
13379            callable_preprocessor_context_is_visible_for_reference(
13380                node,
13381                prepared.source(),
13382                &reference,
13383            ) && resolve_include_targets_with_index(file, &fact.path, include_targets).is_empty()
13384        })
13385}
13386
13387fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
13388    let descendant = node;
13389    while let Some(parent) = node.parent() {
13390        if is_preprocessor_conditional(parent)
13391            && !is_file_covering_include_guard(parent, source)
13392            && preprocessor_conditional_contains_descendant(parent, descendant)
13393        {
13394            return true;
13395        }
13396        node = parent;
13397    }
13398    false
13399}
13400
13401/// The [`OwningPreprocessorConditionals`] of a macro event at `event`.
13402///
13403/// The descendant this walks up from is the one
13404/// [`VisibilityIndex::macro_event_condition_value`] starts from -- the
13405/// innermost node at the event's first byte, not the event node itself --
13406/// because containment compares that descendant's end against the recovered
13407/// conditional boundary, and the two nodes end in different places. An event
13408/// that [`has_preprocessor_conditional_ancestor`] rejects owns nothing: that
13409/// predicate is what has always decided whether an event is conditional at
13410/// all, and answering it first also skips the walk for the ordinary
13411/// unconditional event.
13412fn owning_preprocessor_conditionals(
13413    root: Node<'_>,
13414    event: Node<'_>,
13415    source: &str,
13416) -> OwningPreprocessorConditionals {
13417    if !has_preprocessor_conditional_ancestor(event, source) {
13418        return OwningPreprocessorConditionals::default();
13419    }
13420    let start = event.start_byte();
13421    let descendant = root
13422        .descendant_for_byte_range(start, start.saturating_add(1).min(source.len()))
13423        .expect("a byte inside the parsed tree names a descendant");
13424    let mut owners = Vec::new();
13425    let mut current = descendant.parent();
13426    while let Some(conditional) = current {
13427        if is_preprocessor_conditional(conditional)
13428            && !is_file_covering_include_guard(conditional, source)
13429            && preprocessor_conditional_contains_descendant(conditional, descendant)
13430        {
13431            owners.push(conditional.start_byte());
13432        }
13433        current = conditional.parent();
13434    }
13435    owners.into_boxed_slice()
13436}
13437
13438fn is_preprocessor_conditional(node: Node<'_>) -> bool {
13439    matches!(
13440        node.kind(),
13441        "preproc_if"
13442            | "preproc_ifdef"
13443            | "preproc_ifndef"
13444            | "preproc_elif"
13445            | "preproc_elifdef"
13446            | "preproc_else"
13447    )
13448}
13449
13450fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
13451    node.parent()
13452        .filter(|parent| parent.kind() == "translation_unit")
13453        .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
13454        && is_canonical_include_guard(node, source)
13455}
13456
13457fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
13458    if node.kind() != "preproc_ifdef"
13459        || node
13460            .child(0)
13461            .is_none_or(|directive| directive.kind() != "#ifndef")
13462        || node.child_by_field_name("alternative").is_some()
13463    {
13464        return false;
13465    }
13466    let Some(guard_name) = node.child_by_field_name("name") else {
13467        return false;
13468    };
13469    let mut cursor = node.walk();
13470    node.named_children(&mut cursor)
13471        .find(|child| *child != guard_name && child.kind() != "comment")
13472        .filter(|child| child.kind() == "preproc_def")
13473        .and_then(|definition| definition.child_by_field_name("name"))
13474        .is_some_and(|defined_name| {
13475            node_text(defined_name, source) == node_text(guard_name, source)
13476        })
13477}
13478
13479fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
13480    let mut guard = None;
13481    for child in named_children_iter(root) {
13482        if child.kind() == "comment" || is_pragma_once(child, source) {
13483            continue;
13484        }
13485        if guard.is_none() && is_canonical_include_guard(child, source) {
13486            guard = Some(child);
13487        } else {
13488            return None;
13489        }
13490    }
13491    guard
13492        .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
13493        .map(|name| node_text(name, source).to_string())
13494}
13495
13496fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
13497    if (0..root.named_child_count())
13498        .filter_map(|index| root.named_child(index))
13499        .any(|child| is_pragma_once(child, source))
13500    {
13501        return MacroIncludeProtection::PragmaOnce;
13502    }
13503    top_level_canonical_include_guard_name(root, source)
13504        .map(MacroIncludeProtection::MacroGuard)
13505        .unwrap_or(MacroIncludeProtection::None)
13506}
13507
13508fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
13509    node.kind() == "preproc_call"
13510        && node
13511            .child_by_field_name("directive")
13512            .is_some_and(|directive| node_text(directive, source) == "#pragma")
13513        && node
13514            .child_by_field_name("argument")
13515            .is_some_and(|argument| node_text(argument, source).trim() == "once")
13516}
13517
13518fn parse_preproc_identifier(argument: &str) -> Option<String> {
13519    let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
13520    let mut parser = Parser::new();
13521    parser
13522        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13523        .ok()?;
13524    let tree = parser.parse(&sentinel, None)?;
13525    if tree.root_node().has_error() {
13526        return None;
13527    }
13528    let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
13529    let identifier = statement.named_child(0)?;
13530    (identifier.kind() == "identifier" && statement.named_child_count() == 1)
13531        .then(|| node_text(identifier, &sentinel).to_string())
13532}
13533
13534pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
13535    match node.kind() {
13536        "identifier" | "field_identifier" => {
13537            let name = node_text(node, source).trim();
13538            (!name.is_empty()).then(|| name.to_string())
13539        }
13540        "abstract_array_declarator"
13541        | "abstract_function_declarator"
13542        | "abstract_parenthesized_declarator"
13543        | "abstract_pointer_declarator"
13544        | "abstract_reference_declarator" => None,
13545        "function_declarator" => node
13546            .child_by_field_name("declarator")
13547            .or_else(|| node.child_by_field_name("name"))
13548            .and_then(|child| extract_variable_name(child, source)),
13549        _ => node
13550            .child_by_field_name("declarator")
13551            .or_else(|| node.child_by_field_name("name"))
13552            .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
13553            .and_then(|child| extract_variable_name(child, source)),
13554    }
13555}
13556
13557/// Whether `file` is proven to use plain-C source semantics.
13558///
13559/// `Language::Cpp` intentionally serves both C and C++. Headers do not carry a
13560/// compilation dialect on their own, so only an exact `.c` source extension is
13561/// sufficient to reinterpret C++-grammar keyword nodes such as `this` as C
13562/// identifiers.
13563///
13564/// The exact-lowercase-`.c` rule itself lives in [`LanguageDialect::for_path`],
13565/// which extraction reads too (a `.c` file is extracted with C tag scope), so
13566/// the doctrine has exactly one definition.
13567pub fn is_c_source_file(file: &ProjectFile) -> bool {
13568    LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
13569}
13570
13571/// Whether tree-sitter parsed the operand of C `sizeof(T)` as an expression
13572/// identifier even though `T` may denote a typedef.
13573///
13574/// The grammar cannot distinguish `sizeof(value)` from `sizeof(Type)` without
13575/// semantic information. Keep this helper structural and narrow; callers must
13576/// still prove a visible type and reject an active ordinary-namespace shadow.
13577pub fn is_c_sizeof_expression_type_candidate(file: &ProjectFile, node: Node<'_>) -> bool {
13578    if !is_c_source_file(file) || node.kind() != "identifier" {
13579        return false;
13580    }
13581    let mut operand = node;
13582    while let Some(parent) = operand.parent().filter(|parent| {
13583        parent.kind() == "parenthesized_expression"
13584            && parent.named_child_count() == 1
13585            && parent.named_child(0) == Some(operand)
13586    }) {
13587        operand = parent;
13588    }
13589    operand.parent().is_some_and(|parent| {
13590        parent.kind() == "sizeof_expression" && parent.child_by_field_name("value") == Some(operand)
13591    })
13592}
13593
13594/// Return the type and member leaves of a C `offsetof` member designator.
13595///
13596/// `offsetof_expression` is a dedicated tree-sitter node, so its two operands
13597/// must be interpreted through their named fields.  In particular, do not
13598/// infer the aggregate from the enclosing lexical scope: an `offsetof` can
13599/// name a member of an unrelated aggregate, including a field promoted from
13600/// an anonymous union.  A missing or unsupported operand is deliberately
13601/// rejected so callers can keep the reference unresolved.
13602pub fn c_offsetof_member_parts(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
13603    if node.kind() != "field_identifier" {
13604        return None;
13605    }
13606    let expression = node.parent().filter(|parent| {
13607        parent.kind() == "offsetof_expression" && parent.child_by_field_name("member") == Some(node)
13608    })?;
13609    if expression.has_error() {
13610        return None;
13611    }
13612    let closing = expression.child(expression.child_count().saturating_sub(1))?;
13613    if closing.kind() != ")" || closing.is_missing() {
13614        return None;
13615    }
13616    let type_descriptor = expression.child_by_field_name("type")?;
13617    if type_descriptor.kind() != "type_descriptor"
13618        || type_descriptor.is_missing()
13619        || type_descriptor.has_error()
13620    {
13621        return None;
13622    }
13623    let type_specifier = type_descriptor.child_by_field_name("type")?;
13624    if type_specifier.is_missing() || type_specifier.has_error() {
13625        return None;
13626    }
13627    let type_reference = match type_specifier.kind() {
13628        "class_specifier" | "struct_specifier" | "union_specifier" => {
13629            type_specifier.child_by_field_name("name")?
13630        }
13631        _ => type_specifier,
13632    };
13633    (!type_reference.is_missing() && !type_reference.has_error()).then_some((type_reference, node))
13634}
13635
13636/// Whether `node` is the member leaf of an `offsetof_expression`, including a
13637/// malformed type operand.  Callers use this guard to prevent the ordinary
13638/// field-name heuristics from guessing an owner after structured resolution
13639/// has failed.
13640pub fn is_c_offsetof_member_node(node: Node<'_>) -> bool {
13641    node.kind() == "field_identifier"
13642        && node.parent().is_some_and(|parent| {
13643            parent.kind() == "offsetof_expression"
13644                && parent.child_by_field_name("member") == Some(node)
13645        })
13646}
13647
13648/// Whether `node` is a template argument name that tree-sitter spelled with
13649/// type syntax.
13650///
13651/// The grammar cannot tell a type argument from a non-type (value) argument, so
13652/// it gives both the same shape:
13653/// `template_argument_list -> type_descriptor -> type_identifier`. In
13654/// `std::array<W, N>` the type `W` and the constant `N` parse identically, and
13655/// so do `std::span<const uint8_t, ED448_LEN>`'s length and a nested type
13656/// member used as a real type argument.
13657///
13658/// This helper reports only the syntactic position. A caller must still prove
13659/// which namespace explains the spelling: forward navigation asks the type
13660/// namespace first and reads the leaf as a value only when no type explains it,
13661/// and the inverse field scan admits the leaf only when no visible type does
13662/// (#2556).
13663pub fn is_type_shaped_template_argument_name(node: Node<'_>) -> bool {
13664    if node.kind() != "type_identifier" {
13665        return false;
13666    }
13667    let Some(descriptor) = node
13668        .parent()
13669        .filter(|parent| parent.kind() == "type_descriptor")
13670    else {
13671        return false;
13672    };
13673    if descriptor.child_by_field_name("type") != Some(node) {
13674        return false;
13675    }
13676    let Some(arguments) = descriptor
13677        .parent()
13678        .filter(|parent| parent.kind() == "template_argument_list")
13679    else {
13680        return false;
13681    };
13682    arguments.parent().is_some_and(|owner| {
13683        matches!(
13684            owner.kind(),
13685            "template_type" | "template_function" | "template_method"
13686        ) && owner.child_by_field_name("arguments") == Some(arguments)
13687    })
13688}
13689
13690/// Whether a reference written in `file` reads C++ source with C semantics.
13691///
13692/// [`is_c_source_file`] answers the half a path settles on its own. The other
13693/// half is a header, which has no dialect of its own: it is read as C exactly
13694/// when every workspace translation unit that provably compiles it compiles it
13695/// as C ([`CppSource::header_uses_c_semantics`], issue #1970).
13696///
13697/// This is the gate for anything that is really about the compilation
13698/// language of the code being read -- which reading of an included header's
13699/// declarations is in scope, whether `this` is an ordinary identifier. It is
13700/// NOT the gate for a question that is genuinely about a `.c` file on disk;
13701/// those keep calling [`is_c_source_file`].
13702pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
13703    is_c_source_file(file) || cpp.header_uses_c_semantics(file)
13704}
13705
13706pub fn is_declarator_node(node: Node<'_>) -> bool {
13707    matches!(
13708        node.kind(),
13709        "identifier"
13710            | "field_identifier"
13711            | "qualified_identifier"
13712            | "scoped_identifier"
13713            | "pointer_declarator"
13714            | "reference_declarator"
13715            | "array_declarator"
13716            | "parenthesized_declarator"
13717            | "function_declarator"
13718    )
13719}
13720
13721/// Returns the declarator represented by one direct child of a declaration.
13722///
13723/// Tree-sitter exposes the first declarator through the `declarator` field,
13724/// but subsequent comma-separated declarators are unfielded direct children:
13725/// `int first, second;` therefore has an `identifier` child for `first` and a
13726/// second, otherwise identical, `identifier` child for `second`. Consumers
13727/// must inspect both shapes or a later declarator can be mistaken for a use of
13728/// an unrelated indexed symbol.
13729pub fn declaration_declarator<'tree>(
13730    declaration: Node<'tree>,
13731    child: Node<'tree>,
13732) -> Option<Node<'tree>> {
13733    if !matches!(
13734        declaration.kind(),
13735        "declaration"
13736            | "field_declaration"
13737            | "parameter_declaration"
13738            | "optional_parameter_declaration"
13739            | "function_definition"
13740            | "type_definition"
13741            | "alias_declaration"
13742            | "template_instantiation"
13743    ) {
13744        return None;
13745    }
13746    if declaration
13747        .child_by_field_name("type")
13748        .is_some_and(|type_node| same_node(type_node, child))
13749    {
13750        return None;
13751    }
13752    if child.kind() == "init_declarator" {
13753        return child.child_by_field_name("declarator");
13754    }
13755    let field = field_name_in_parent(declaration, child);
13756    if (is_declarator_node(child) && matches!(field, Some("declarator") | None))
13757        || (declaration.kind() == "type_definition"
13758            && child.kind() == "type_identifier"
13759            && matches!(field, Some("declarator") | None))
13760    {
13761        Some(child)
13762    } else {
13763        None
13764    }
13765}
13766
13767/// One run of a container's children whose parsed namespaces differ from
13768/// their lexical namespaces, with the complete lexical namespace path.
13769#[derive(Clone, Debug, PartialEq, Eq)]
13770pub struct RecoveredNamespaceRegion {
13771    /// Start byte of the first child in the run.
13772    pub start: usize,
13773    /// End byte of the last child in the run.
13774    pub end: usize,
13775    /// The complete enclosing namespace path of the run, outermost first.
13776    /// This can be empty when a parsed namespace extends past its real close.
13777    pub components: Vec<String>,
13778    /// The `namespace Name { ... }` construct that opens each of `components`,
13779    /// parallel to it, from the `namespace` keyword through the matching close
13780    /// the brace stack paired with its `{`. A level whose close the stack could
13781    /// not prove -- the file never closes it, or the file's brace token stream
13782    /// lost an opening token (#3328) -- carries its head alone. This is the
13783    /// range the declaration walk records for the Module, so a namespace whose
13784    /// head collapsed into an `ERROR` reports where it is written rather than
13785    /// where its first member is (#3309).
13786    pub component_ranges: Vec<Range>,
13787    /// The close the brace stack paired with each level's `{`, parallel to
13788    /// `components`. A node after that close is outside the level (#3633).
13789    /// `None` when the file never closes the level or the walk proved no
13790    /// pairing in the file (#3328); the level then applies to the whole region
13791    /// as a best-effort scope.
13792    pub component_closes: Vec<Option<Range>>,
13793}
13794
13795/// The namespaces C++ parse recovery drops from a file's tree.
13796///
13797/// When tree-sitter cannot parse a construct inside a namespace body it closes
13798/// an inner scope with a MISSING brace, or skips an opening brace into an
13799/// ERROR node. Every real `}` after that then closes one scope too early: a
13800/// class body's `}` closes the namespace, the namespace's own `}` closes its
13801/// parent, and the outermost real closes land in a trailing ERROR node. The
13802/// declarations between a stolen close and the real one keep their byte
13803/// positions but lose their `namespace_definition` ancestors (Catch2's
13804/// `catch_matchers_templated.hpp`, issue #1537).
13805///
13806/// A file-global stack over real AST brace tokens restores both lost and
13807/// overextended namespaces. Missing braces do nothing; error-free subtrees are
13808/// balanced and can be skipped. For each child of a damaged container, compare
13809/// the namespaces still open on that stack with the parsed ancestor path. A
13810/// difference yields a region carrying the complete lexical path, including
13811/// an empty path when recovery swallowed file-scope declarations. Consecutive
13812/// children sharing that path form one [`RecoveredNamespaceRegion`]. The same
13813/// pass retains matching brace positions for declaration partitioning (#3087).
13814/// A file without parse errors needs neither correction nor a brace index.
13815///
13816/// The stack restores the source's nesting only while the tree's brace tokens
13817/// are the source's braces. Severe recovery breaks that: in simdjson's
13818/// `compile_time_json-inl.h` the C++26 reflection syntax leaves the whole
13819/// translation unit an `ERROR`, and inside it `case '{':` re-lexes as a
13820/// `compound_literal_expression` whose `initializer_list` opens on the char
13821/// literal's own `{`, while the statement's real `{` is consumed as `character`
13822/// tokens of a char literal that never terminates. That file's tree holds 219
13823/// real `}` against 211 real `{`; eight closes therefore find the stack empty,
13824/// and the two namespace levels are paired with interior closes 245 lines
13825/// before their own (#3328). An unpaired close proves the stream lost an open at
13826/// a position the stack cannot recover, so the pairing of that file is not
13827/// evidence of any extent: [`Self::matching_close_brace`] then answers nothing
13828/// and each restored level keeps the head that writes it. The namespace paths
13829/// remain, as the best-effort structure recovery they are.
13830///
13831/// Every consumer reads a node's namespace path through one authority rule,
13832/// shared by [`Self::enclosing_namespace_levels`] and
13833/// [`Self::restore_enclosing_namespaces`], so declaration collection and both
13834/// lookup directions agree:
13835///
13836/// - Outside every region, the node's parsed `namespace_definition` chain is
13837///   the whole answer.
13838/// - Inside a region, the region's restored levels come first and only the
13839///   parsed ancestors that start inside the region follow them. The region
13840///   overrides a parsed ancestor that starts before it, which is how a
13841///   namespace the tree kept open past its real close gives up the
13842///   declarations after that close (#3087).
13843/// - A restored level stops applying at the close the stack paired with its
13844///   `{`, and so does every level inside it. The walk compares a child with
13845///   the stack before it descends into that child, so a run can reach past a
13846///   close inside its last child. In simdjson's `tests/dom/basictests.cpp` the
13847///   collapsed `namespace type_tests {` on line 1467 closes on line 1811, and
13848///   recovery put that `}` inside a `preproc_else` that runs to line 2505: the
13849///   region that restores `type_tests` covers the whole `preproc_else`, and
13850///   without this check every namespace written after the close inherited
13851///   `type_tests` (#3633). A level whose close the stack did not prove applies
13852///   to the whole region, the best-effort answer #3328 leaves.
13853/// - A region whose restored path is empty keeps exactly the parsed ancestors
13854///   whose own `{` and `}` are both real tokens, wherever they start, and drops
13855///   the others. This holds whether or not the file's pairing is proven.
13856#[derive(Clone, Debug, Default)]
13857pub struct OrphanedNamespaceScopeIndex {
13858    regions: Vec<RecoveredNamespaceRegion>,
13859    /// Open-brace start byte to the close the file-global stack paired with it.
13860    /// Empty when the walk proved the brace token stream incomplete.
13861    brace_closes: HashMap<usize, Range>,
13862}
13863
13864impl OrphanedNamespaceScopeIndex {
13865    pub fn build(root: Node<'_>, source: &str) -> Self {
13866        if !root.has_error() {
13867            return Self::default();
13868        }
13869        /// One namespace level open on the brace stack: the `namespace Name {`
13870        /// head it is written as, and the `{`'s start byte, which is the
13871        /// `brace_closes` key its close lands under. The close is not known
13872        /// while the level is open, so the final ranges are assembled once the
13873        /// walk has paired every brace.
13874        #[derive(Clone, Copy)]
13875        struct OpenHead {
13876            head: Range,
13877            open: usize,
13878        }
13879        /// A [`RecoveredNamespaceRegion`] whose levels still name their open
13880        /// braces rather than their closes.
13881        struct PendingRegion {
13882            start: usize,
13883            end: usize,
13884            components: Vec<String>,
13885            heads: Vec<OpenHead>,
13886        }
13887        struct Frame<'tree> {
13888            node: Node<'tree>,
13889            children: Vec<Node<'tree>>,
13890            next: usize,
13891            parsed_scope: Vec<String>,
13892            run: Option<PendingRegion>,
13893        }
13894        fn frame<'tree>(
13895            node: Node<'tree>,
13896            mut parsed_scope: Vec<String>,
13897            source: &str,
13898        ) -> Frame<'tree> {
13899            if node.kind() == "namespace_definition"
13900                && let Some(name) = node.child_by_field_name("name")
13901            {
13902                let mut components = Vec::new();
13903                if append_cpp_name_components(name, source, &mut components).is_some() {
13904                    parsed_scope.extend(components);
13905                }
13906            }
13907            let mut cursor = node.walk();
13908            Frame {
13909                node,
13910                children: node.children(&mut cursor).collect(),
13911                next: 0,
13912                parsed_scope,
13913                run: None,
13914            }
13915        }
13916        let mut regions = Vec::new();
13917        let mut brace_closes = HashMap::default();
13918        // Real closes that arrived with nothing open. Each one proves the tree
13919        // is missing a scope-opening token the source writes, which is what
13920        // makes every pairing in the file unusable as an extent (#3328). The
13921        // complete set is kept, not a count, so a walk that has to be diagnosed
13922        // shows every position the stream went out of sync.
13923        let mut unpaired_closes: Vec<Range> = Vec::new();
13924        // The brace stack belongs to the file, not to a parser frame. A real
13925        // close inside a damaged child can close its parent's namespace. The
13926        // following children must see that removal immediately (#3087).
13927        let mut open = Vec::new();
13928        let mut lexical_scope = Vec::new();
13929        // One head per entry of `lexical_scope`: `namespace a::b {` opens two
13930        // levels and both are written by that one head, exactly as the
13931        // ordinary namespace visit records one range for every level of the
13932        // C++17 shorthand (#1878).
13933        let mut lexical_heads: Vec<OpenHead> = Vec::new();
13934        let mut frames = vec![frame(root, Vec::new(), source)];
13935        // A frame's node is the previous frame's direct child, so the frame
13936        // below answers what Node::parent would without re-descending from the
13937        // root; per-node parent or sibling climbs are quadratic over a wide
13938        // translation unit (#3141).
13939        while !frames.is_empty() {
13940            let parent_node = frames.len().checked_sub(2).map(|index| frames[index].node);
13941            let current = frames.last_mut().expect("frames is non-empty");
13942            if current.next == current.children.len() {
13943                regions.extend(frames.pop().expect("the frame just borrowed").run);
13944                continue;
13945            }
13946            let child = current.children[current.next];
13947            current.next += 1;
13948            match child.kind() {
13949                "{" if !child.is_missing() => {
13950                    regions.extend(current.run.take());
13951                    // The head is the construct that writes the namespace: the
13952                    // `namespace_definition` node when one survived, and the
13953                    // bare `namespace` keyword when recovery collapsed the head
13954                    // into flat `ERROR` children (#3084).
13955                    let mut named = parent_node.and_then(|parent| {
13956                        let components =
13957                            namespace_body_name_components(parent, current.node, source);
13958                        (!components.is_empty()).then_some((parent, components))
13959                    });
13960                    if named.is_none() {
13961                        named = recovered_namespace_open_components(
13962                            &current.children[..current.next - 1],
13963                            source,
13964                        );
13965                    }
13966                    open.push((child.start_byte(), lexical_scope.len()));
13967                    if let Some((written_at, components)) = named {
13968                        let head = OpenHead {
13969                            head: Range {
13970                                start_byte: written_at.start_byte(),
13971                                end_byte: child.end_byte(),
13972                                start_line: written_at.start_position().row + 1,
13973                                end_line: child.end_position().row + 1,
13974                            },
13975                            open: child.start_byte(),
13976                        };
13977                        lexical_heads.extend(std::iter::repeat_n(head, components.len()));
13978                        lexical_scope.extend(components);
13979                    }
13980                    continue;
13981                }
13982                "}" if !child.is_missing() => {
13983                    regions.extend(current.run.take());
13984                    let close = Range {
13985                        start_byte: child.start_byte(),
13986                        end_byte: child.end_byte(),
13987                        start_line: child.start_position().row + 1,
13988                        end_line: child.end_position().row + 1,
13989                    };
13990                    match open.pop() {
13991                        Some((start, namespace_len)) => {
13992                            debug_assert!(
13993                                start < close.start_byte,
13994                                "a close pairs with an open before it: {start} then {close:?}"
13995                            );
13996                            lexical_scope.truncate(namespace_len);
13997                            lexical_heads.truncate(namespace_len);
13998                            brace_closes.insert(start, close);
13999                        }
14000                        None => unpaired_closes.push(close),
14001                    }
14002                    continue;
14003                }
14004                _ => {}
14005            }
14006            // The namespace head and body precede their opening brace; their
14007            // own parsed namespace is not yet on the lexical stack. Compare
14008            // the items inside the body, not this header bookkeeping.
14009            if current.node.kind() != "namespace_definition"
14010                && lexical_scope != current.parsed_scope
14011            {
14012                match &mut current.run {
14013                    Some(run) if run.components == lexical_scope => run.end = child.end_byte(),
14014                    run => {
14015                        regions.extend(run.take());
14016                        *run = Some(PendingRegion {
14017                            start: child.start_byte(),
14018                            end: child.end_byte(),
14019                            components: lexical_scope.clone(),
14020                            heads: lexical_heads.clone(),
14021                        });
14022                    }
14023                }
14024            } else {
14025                regions.extend(current.run.take());
14026            }
14027            // Error-free subtrees are balanced. Keeping them intact also
14028            // leaves their valid nested namespaces to the ordinary ancestry
14029            // walk in restore_enclosing_namespaces.
14030            if child.has_error() {
14031                let parsed_scope = current.parsed_scope.clone();
14032                frames.push(frame(child, parsed_scope, source));
14033            }
14034        }
14035        // A close with nothing open is only possible when a `{` the source
14036        // writes is not a `{` token of the tree. The stack then paired some
14037        // earlier open with a close that belongs to an inner scope, and
14038        // nothing in the token stream says which pairs absorbed the loss: the
14039        // lost token sits at an unknown position before the close that
14040        // exposed it. Keep no pairing rather than an unprovable extent. An
14041        // open the file never closes is the opposite case and stays: it makes
14042        // no claim, so the levels it writes keep their heads either way.
14043        if !unpaired_closes.is_empty() {
14044            brace_closes.clear();
14045        }
14046        // Every surviving brace is paired now, so each level's head can grow to
14047        // the construct it writes. A level whose `{` never closes keeps its
14048        // head: the file ends inside the namespace and there is no real close
14049        // to reach for.
14050        let regions = regions
14051            .into_iter()
14052            .map(|region| {
14053                debug_assert_eq!(
14054                    region.components.len(),
14055                    region.heads.len(),
14056                    "every recovered namespace level carries the head that writes it: {:?}",
14057                    region.components
14058                );
14059                let component_closes: Vec<_> = region
14060                    .heads
14061                    .iter()
14062                    .map(|level| brace_closes.get(&level.open).copied())
14063                    .collect();
14064                let component_ranges = region
14065                    .heads
14066                    .iter()
14067                    .zip(&component_closes)
14068                    .map(|(level, close)| match close {
14069                        Some(close) => Range {
14070                            start_byte: level.head.start_byte,
14071                            end_byte: close.end_byte,
14072                            start_line: level.head.start_line,
14073                            end_line: close.end_line,
14074                        },
14075                        None => level.head,
14076                    })
14077                    .collect();
14078                RecoveredNamespaceRegion {
14079                    start: region.start,
14080                    end: region.end,
14081                    components: region.components,
14082                    component_ranges,
14083                    component_closes,
14084                }
14085            })
14086            .collect();
14087        Self {
14088            regions,
14089            brace_closes,
14090        }
14091    }
14092
14093    /// The real AST brace matching an opening brace in a damaged subtree.
14094    /// Missing tokens and braces inside comments or literals do not participate.
14095    ///
14096    /// `None` when this open has no close in the file, and for every open in a
14097    /// file whose brace token stream lost an opening token: a caller cannot
14098    /// treat a pairing the walk could not prove as an enclosing extent (#3328).
14099    pub fn matching_close_brace(&self, open: usize) -> Option<Range> {
14100        self.brace_closes.get(&open).copied()
14101    }
14102
14103    pub fn is_empty(&self) -> bool {
14104        self.regions.is_empty()
14105    }
14106
14107    /// The bytes this index holds, for the analyzer cache's weight.
14108    pub fn approximate_size(&self) -> usize {
14109        self.regions.iter().fold(
14110            self.brace_closes.len() * std::mem::size_of::<(usize, Range)>(),
14111            |total, region| {
14112                total
14113                    .saturating_add(std::mem::size_of::<RecoveredNamespaceRegion>())
14114                    .saturating_add(region.components.iter().map(String::len).sum::<usize>())
14115                    .saturating_add(region.component_ranges.len() * std::mem::size_of::<Range>())
14116                    .saturating_add(
14117                        region.component_closes.len() * std::mem::size_of::<Option<Range>>(),
14118                    )
14119            },
14120        )
14121    }
14122
14123    /// The innermost recovered region containing `byte`.
14124    pub fn region_at(&self, byte: usize) -> Option<&RecoveredNamespaceRegion> {
14125        self.regions
14126            .iter()
14127            .filter(|region| region.start <= byte && byte < region.end)
14128            .min_by_key(|region| region.end - region.start)
14129    }
14130
14131    /// The enclosing namespaces of `node`, outermost first, restoring the ones
14132    /// parse recovery dropped from its ancestor chain. The one answer both
14133    /// lookup directions and declaration collection use (issue #1537).
14134    pub fn enclosing_namespace_components(&self, node: Node<'_>, source: &str) -> Vec<String> {
14135        self.enclosing_namespace_levels(node, source)
14136            .into_iter()
14137            .map(|(component, _)| component)
14138            .collect()
14139    }
14140
14141    /// [`Self::enclosing_namespace_components`] with the construct that writes
14142    /// each level, for the declaration walk that has to record a range for it.
14143    ///
14144    /// A level the brace stack restored carries the range this index recorded
14145    /// for its head; a surviving `namespace_definition` ancestor carries its own
14146    /// node range, which is what the ordinary namespace visit records.
14147    pub fn enclosing_namespace_levels(&self, node: Node<'_>, source: &str) -> Vec<(String, Range)> {
14148        let mut parsed = Vec::new();
14149        let mut current = node.parent();
14150        while let Some(parent) = current {
14151            if parent.kind() == "namespace_definition"
14152                && let Some(name) = parent.child_by_field_name("name")
14153            {
14154                let mut components = Vec::new();
14155                if append_cpp_name_components(name, source, &mut components).is_some() {
14156                    let range = node_range(parent);
14157                    parsed.push((
14158                        parent.start_byte(),
14159                        components
14160                            .into_iter()
14161                            .map(|component| (component, range))
14162                            .collect(),
14163                        namespace_definition_has_real_braces(parent),
14164                    ));
14165                }
14166            }
14167            current = parent.parent();
14168        }
14169        parsed.reverse();
14170        self.splice_recovered_path(parsed, node.start_byte(), |region, active_components| {
14171            region
14172                .components
14173                .iter()
14174                .take(active_components)
14175                .cloned()
14176                .zip(
14177                    region
14178                        .component_ranges
14179                        .iter()
14180                        .take(active_components)
14181                        .copied(),
14182                )
14183                .collect()
14184        })
14185    }
14186
14187    /// [`Self::enclosing_namespace_components`] for a caller that has already
14188    /// climbed the ancestor chain: `parsed` lists the node's named
14189    /// `namespace_definition` ancestors outermost first, each with its start
14190    /// byte and whether its own opening and closing braces survived parsing.
14191    /// The answer follows the authority rule on [`OrphanedNamespaceScopeIndex`].
14192    pub fn restore_enclosing_namespaces(
14193        &self,
14194        parsed: Vec<(usize, Vec<String>, bool)>,
14195        node_start: usize,
14196    ) -> Vec<String> {
14197        self.splice_recovered_path(parsed, node_start, |region, active_components| {
14198            region
14199                .components
14200                .iter()
14201                .take(active_components)
14202                .cloned()
14203                .collect()
14204        })
14205    }
14206
14207    /// Splice a node's parsed namespace ancestors onto the path a recovered
14208    /// region restores, by the authority rule on [`OrphanedNamespaceScopeIndex`].
14209    /// `recovered` receives the region and the number of its outermost levels
14210    /// still open at the node, and reads those levels in whatever shape the
14211    /// caller needs.
14212    fn splice_recovered_path<T>(
14213        &self,
14214        parsed: Vec<(usize, Vec<T>, bool)>,
14215        node_start: usize,
14216        recovered: impl FnOnce(&RecoveredNamespaceRegion, usize) -> Vec<T>,
14217    ) -> Vec<T> {
14218        let Some(region) = self.region_at(node_start) else {
14219            return parsed
14220                .into_iter()
14221                .flat_map(|(_, levels, _)| levels)
14222                .collect();
14223        };
14224        debug_assert_eq!(
14225            region.components.len(),
14226            region.component_closes.len(),
14227            "every recovered namespace component carries its close proof: {region:?}"
14228        );
14229        if region.components.is_empty() {
14230            return parsed
14231                .into_iter()
14232                .filter(|(_, _, complete)| *complete)
14233                .flat_map(|(_, levels, _)| levels)
14234                .collect();
14235        }
14236        let active_components = region
14237            .component_closes
14238            .iter()
14239            .take_while(|close| {
14240                close
14241                    .as_ref()
14242                    .is_none_or(|close| node_start < close.end_byte)
14243            })
14244            .count();
14245        recovered(region, active_components)
14246            .into_iter()
14247            .chain(
14248                parsed
14249                    .into_iter()
14250                    .filter(|(start, _, _)| *start >= region.start)
14251                    .flat_map(|(_, levels, _)| levels),
14252            )
14253            .collect()
14254    }
14255}
14256
14257/// Whether a parsed namespace owns real AST tokens for both of its boundary
14258/// braces. Recovery may damage its body while leaving this boundary usable as
14259/// lexical ownership evidence.
14260pub(super) fn namespace_definition_has_real_braces(namespace: Node<'_>) -> bool {
14261    debug_assert_eq!(namespace.kind(), "namespace_definition");
14262    let Some(body) = namespace.child_by_field_name("body") else {
14263        return false;
14264    };
14265    let mut opening = false;
14266    let mut closing = false;
14267    let mut cursor = body.walk();
14268    for child in body.children(&mut cursor) {
14269        match child.kind() {
14270            "{" if !child.is_missing() => opening = true,
14271            "}" if !child.is_missing() => closing = true,
14272            _ => {}
14273        }
14274    }
14275    opening && closing
14276}
14277
14278/// The `namespace` keyword and name components of the namespace a stray `{`
14279/// opens, or `None` when the brace does not follow a namespace head.
14280///
14281/// When a namespace body holds a construct tree-sitter cannot parse, recovery
14282/// can collapse the whole `namespace Name { ... }` into one `ERROR` instead of
14283/// a `namespace_definition`: the head survives as the node's own `namespace`
14284/// keyword, name and `{` tokens, in that order, and everything the namespace
14285/// declares becomes a flat sibling of them (Catch2's `catch_decomposer.hpp`,
14286/// issue #3084). Read the head from those siblings so the brace stack names
14287/// the scope the brace opens. An anonymous namespace has no representable
14288/// name and keeps an opaque scope.
14289///
14290/// `preceding` is the open brace's preceding siblings, nearest last. The
14291/// caller already holds the container's child list; `Node::prev_sibling`
14292/// would re-descend from the root for each step (#3141).
14293fn recovered_namespace_open_components<'tree>(
14294    preceding: &[Node<'tree>],
14295    source: &str,
14296) -> Option<(Node<'tree>, Vec<String>)> {
14297    let mut head = Vec::new();
14298    for &sibling in preceding.iter().rev() {
14299        if sibling.kind() != "comment" {
14300            head.push(sibling);
14301            if head.len() == 2 {
14302                break;
14303            }
14304        }
14305    }
14306    let [name, keyword] = head[..] else {
14307        return None;
14308    };
14309    if keyword.kind() != "namespace" {
14310        return None;
14311    }
14312    let mut components = Vec::new();
14313    append_cpp_name_components(name, source, &mut components)?;
14314    (!components.is_empty()).then_some((keyword, components))
14315}
14316
14317/// The name components of the namespace whose body `body` is, or empty when
14318/// `body` is not the body of a named `namespace_definition` `parent`.
14319fn namespace_body_name_components(parent: Node<'_>, body: Node<'_>, source: &str) -> Vec<String> {
14320    let mut components = Vec::new();
14321    if body.kind() == "declaration_list"
14322        && parent.kind() == "namespace_definition"
14323        && parent.child_by_field_name("body") == Some(body)
14324        && let Some(name) = parent.child_by_field_name("name")
14325        && append_cpp_name_components(name, source, &mut components).is_none()
14326    {
14327        components.clear();
14328    }
14329    components
14330}
14331
14332#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14333pub enum RecoveredDeclaratorTypeContext {
14334    Declaration,
14335    FunctionDefinition,
14336    Parameter,
14337}
14338
14339/// Recognize a real type displaced into a qualified declarator by parser
14340/// recovery.
14341///
14342/// Tree-sitter parses `API Result *make(Arg);` as if `API` were the declared
14343/// type and `Result` were the scope of a qualified declarator with a missing
14344/// `::`. A template return such as `API Result<T> make()` uses a
14345/// `template_type` for the same recovered scope. The same recovery occurs for
14346/// macro-prefixed definitions, extern variables, and macro-decorated
14347/// parameters (`f(MACRO T* p)`, where the parameter's own `type` field takes
14348/// the macro). Keep this intentionally structural: the recovered scope must
14349/// have the grammar's missing separator, the qualified node must occupy the
14350/// declaration's declarator chain, a separate nonempty type must occupy the
14351/// normal type field, and the recovered name must unwrap to a real declarator
14352/// name.
14353pub fn recovered_macro_decorated_declarator_type(
14354    node: Node<'_>,
14355) -> Option<RecoveredDeclaratorTypeContext> {
14356    recovered_macro_decorated_type_node(node).map(|(_, context)| context)
14357}
14358
14359/// Return the declaration/function `type` displaced by a macro-shaped
14360/// qualified declarator, together with the enclosing declaration context.
14361/// Callers use the macro scope only as structural admission evidence; the
14362/// returned node is the real type reference to resolve and record.
14363pub fn recovered_macro_decorated_type_node(
14364    node: Node<'_>,
14365) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
14366    if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
14367        return None;
14368    }
14369    let qualified = node.parent()?;
14370    if qualified.kind() != "qualified_identifier"
14371        || qualified.child_by_field_name("scope") != Some(node)
14372        || !(0..qualified.child_count())
14373            .filter_map(|index| qualified.child(index))
14374            .any(|child| child.kind() == "::" && child.is_missing())
14375    {
14376        return None;
14377    }
14378    if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
14379        return None;
14380    }
14381
14382    let (declaration, context) = recovered_declarator_container(qualified)?;
14383    let type_node = declaration
14384        .child_by_field_name("type")
14385        .filter(|type_node| {
14386            *type_node != qualified
14387                && !type_node.is_missing()
14388                && type_node.start_byte() != type_node.end_byte()
14389        })?;
14390    Some((type_node, context))
14391}
14392
14393fn recovered_declarator_container(
14394    mut declarator: Node<'_>,
14395) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
14396    loop {
14397        let parent = declarator.parent()?;
14398        if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
14399            return Some((
14400                parent
14401                    .parent()
14402                    .filter(|declaration| declaration.kind() == "declaration")?,
14403                RecoveredDeclaratorTypeContext::Declaration,
14404            ));
14405        }
14406        if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
14407            return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
14408        }
14409        if parent.kind() == "function_definition"
14410            && has_field_child(parent, "declarator", declarator)
14411        {
14412            return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
14413        }
14414        // `f(MACRO T* p)` recovers exactly like `MACRO T *make(...)` does, one
14415        // level down: the parameter's `type` field takes the macro token and
14416        // the real type `T` becomes the recovered scope of the declarator.
14417        // Declining here left every xxhash `XXH_NOESCAPE` parameter with no
14418        // candidate at all (#1830).
14419        if matches!(
14420            parent.kind(),
14421            "parameter_declaration" | "optional_parameter_declaration"
14422        ) && has_field_child(parent, "declarator", declarator)
14423        {
14424            return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
14425        }
14426        if !matches!(
14427            parent.kind(),
14428            "array_declarator"
14429                | "function_declarator"
14430                | "parenthesized_declarator"
14431                | "pointer_declarator"
14432                | "pointer_type_declarator"
14433                | "reference_declarator"
14434        ) || !has_field_child(parent, "declarator", declarator)
14435        {
14436            return None;
14437        }
14438        declarator = parent;
14439    }
14440}
14441
14442fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
14443    let mut cursor = parent.walk();
14444    parent
14445        .children_by_field_name(field, &mut cursor)
14446        .any(|child| child == target)
14447}
14448
14449fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
14450    loop {
14451        if node.is_missing() || node.start_byte() == node.end_byte() {
14452            return false;
14453        }
14454        match node.kind() {
14455            "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
14456                return true;
14457            }
14458            "array_declarator"
14459            | "function_declarator"
14460            | "parenthesized_declarator"
14461            | "pointer_declarator"
14462            | "pointer_type_declarator"
14463            | "reference_declarator" => {
14464                let Some(declarator) = node.child_by_field_name("declarator") else {
14465                    return false;
14466                };
14467                node = declarator;
14468            }
14469            _ => return false,
14470        }
14471    }
14472}
14473
14474/// Aggregate-owner proof for a structurally recognized designated initializer.
14475pub enum DesignatedInitializerOwner {
14476    Resolved(CodeUnit),
14477    Unresolved,
14478}
14479
14480enum InitializerOwnerStep {
14481    Field(String),
14482    AggregateWrapper,
14483}
14484
14485/// Recognize a designated-initializer field and, when possible, resolve its
14486/// aggregate owner.
14487///
14488/// Covers both the grammar's ordinary `field_designator` shape and the exact
14489/// recovery used for `.field = value` after a preprocessor-split array
14490/// initializer. Ordered multi-component designators follow each preceding
14491/// field's declared aggregate type. `None` means the node is not a designator
14492/// at all; an unresolved designator remains classified so callers cannot fall
14493/// through to unrelated global/member heuristics.
14494pub fn designated_initializer_owner(
14495    analyzer: &CppGraphSource<'_>,
14496    visibility: &VisibilityIndex<'_>,
14497    file: &ProjectFile,
14498    source: &str,
14499    node: Node<'_>,
14500) -> Option<DesignatedInitializerOwner> {
14501    if let Some(designator) = node
14502        .parent()
14503        .filter(|parent| parent.kind() == "field_designator")
14504    {
14505        let pair = designator.parent()?;
14506        if pair.kind() != "initializer_pair" {
14507            return None;
14508        }
14509        let mut cursor = pair.walk();
14510        let designators = pair
14511            .children_by_field_name("designator", &mut cursor)
14512            .collect::<Vec<_>>();
14513        let position = designators
14514            .iter()
14515            .position(|candidate| same_node(*candidate, designator))?;
14516        let initializer = pair.parent()?;
14517        if initializer.kind() != "initializer_list" {
14518            return None;
14519        }
14520        let mut owner = initializer_list_owner(analyzer, visibility, file, source, initializer);
14521        for prior in &designators[..position] {
14522            let field = prior
14523                .child_by_field_name("field")
14524                .or_else(|| first_named_child_of_kind(*prior, "field_identifier"))?;
14525            owner = owner.and_then(|owner| {
14526                initializer_field_owner(analyzer, visibility, file, owner, node_text(field, source))
14527            });
14528        }
14529        return Some(classified_designated_owner(owner));
14530    }
14531
14532    let init_declarator = node.parent()?;
14533    if init_declarator.child_by_field_name("declarator") != Some(node)
14534        || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
14535    {
14536        return None;
14537    }
14538    Some(classified_designated_owner(declaration_owner(
14539        analyzer,
14540        visibility,
14541        file,
14542        source,
14543        init_declarator.parent()?,
14544    )))
14545}
14546
14547fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
14548    owner.map_or(
14549        DesignatedInitializerOwner::Unresolved,
14550        DesignatedInitializerOwner::Resolved,
14551    )
14552}
14553
14554fn initializer_list_owner(
14555    analyzer: &CppGraphSource<'_>,
14556    visibility: &VisibilityIndex<'_>,
14557    file: &ProjectFile,
14558    source: &str,
14559    initializer: Node<'_>,
14560) -> Option<CodeUnit> {
14561    let mut current = initializer;
14562    let mut steps = Vec::new();
14563    loop {
14564        let parent = current.parent()?;
14565        match parent.kind() {
14566            "initializer_pair" if parent.child_by_field_name("value") == Some(current) => {
14567                let designator = parent.child_by_field_name("designator")?;
14568                let step = designator
14569                    .child_by_field_name("field")
14570                    .or_else(|| first_named_child_of_kind(designator, "field_identifier"))
14571                    .map(|field| InitializerOwnerStep::Field(node_text(field, source).to_string()))
14572                    .unwrap_or(InitializerOwnerStep::AggregateWrapper);
14573                steps.push(step);
14574                current = parent.parent()?;
14575            }
14576            "initializer_list" => {
14577                current = parent;
14578            }
14579            "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
14580                let declaration = parent.parent()?;
14581                let owner = declaration_owner(analyzer, visibility, file, source, declaration)?;
14582                return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
14583            }
14584            "compound_literal_expression"
14585                if parent.child_by_field_name("value") == Some(current) =>
14586            {
14587                let type_node = parent.child_by_field_name("type")?;
14588                let owner =
14589                    resolve_designated_owner_type(analyzer, visibility, file, source, type_node)?;
14590                return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
14591            }
14592            "ERROR" => current = parent,
14593            _ => return None,
14594        }
14595    }
14596}
14597
14598fn apply_initializer_owner_steps(
14599    analyzer: &CppGraphSource<'_>,
14600    visibility: &VisibilityIndex<'_>,
14601    file: &ProjectFile,
14602    mut owner: CodeUnit,
14603    steps: Vec<InitializerOwnerStep>,
14604) -> Option<CodeUnit> {
14605    for step in steps.into_iter().rev() {
14606        if let InitializerOwnerStep::Field(field_name) = step {
14607            owner = initializer_field_owner(analyzer, visibility, file, owner, &field_name)?;
14608        }
14609    }
14610    Some(owner)
14611}
14612
14613fn initializer_field_owner(
14614    analyzer: &CppGraphSource<'_>,
14615    visibility: &VisibilityIndex<'_>,
14616    file: &ProjectFile,
14617    owner: CodeUnit,
14618    field_name: &str,
14619) -> Option<CodeUnit> {
14620    let fields = visibility
14621        .visible_members_for_owner_name(file, &owner, field_name)
14622        .into_iter()
14623        .filter(|field| field.is_field())
14624        .collect::<Vec<_>>();
14625    let field = match fields.as_slice() {
14626        [field] => *field,
14627        _ => return None,
14628    };
14629    field_declared_binding(analyzer, visibility, file, field)?.unit
14630}
14631
14632fn declaration_owner(
14633    analyzer: &CppGraphSource<'_>,
14634    visibility: &VisibilityIndex<'_>,
14635    file: &ProjectFile,
14636    source: &str,
14637    declaration: Node<'_>,
14638) -> Option<CodeUnit> {
14639    if !matches!(declaration.kind(), "declaration" | "field_declaration") {
14640        return None;
14641    }
14642    let type_node = declaration
14643        .child_by_field_name("type")
14644        .or_else(|| first_type_child(declaration))?;
14645    resolve_designated_owner_type(analyzer, visibility, file, source, type_node)
14646}
14647
14648fn resolve_designated_owner_type(
14649    analyzer: &CppGraphSource<'_>,
14650    visibility: &VisibilityIndex<'_>,
14651    file: &ProjectFile,
14652    source: &str,
14653    type_node: Node<'_>,
14654) -> Option<CodeUnit> {
14655    if let Some(owner) = anonymous_aggregate_owner(analyzer, file, type_node) {
14656        return Some(owner);
14657    }
14658    let type_name = normalize_type_text(node_text(type_node, source));
14659    visibility
14660        .resolve_type(file, &type_name)
14661        .filter(CodeUnit::is_class)
14662}
14663
14664pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
14665    let mut cursor = node.walk();
14666    node.named_children(&mut cursor).find(|child| {
14667        matches!(
14668            child.kind(),
14669            "type_identifier"
14670                | "primitive_type"
14671                | "qualified_identifier"
14672                | "scoped_type_identifier"
14673                | "struct_specifier"
14674                | "union_specifier"
14675                | "enum_specifier"
14676        )
14677    })
14678}
14679
14680pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
14681    visibility: &VisibilityIndex<'_>,
14682    file: &ProjectFile,
14683    source: &str,
14684    declarator: Node<'_>,
14685    type_text: Option<&str>,
14686    bindings: &LocalInferenceEngine<T>,
14687) -> bool {
14688    if !has_ancestor_kind(declarator, "compound_statement") {
14689        return false;
14690    }
14691    if declarator
14692        .child_by_field_name("declarator")
14693        .is_none_or(|declarator| declarator.kind() != "identifier")
14694    {
14695        return false;
14696    }
14697    if !type_text
14698        .and_then(|text| visibility.resolve_type(file, text))
14699        .is_some_and(|unit| unit.is_class())
14700    {
14701        return false;
14702    }
14703    declarator
14704        .child_by_field_name("parameters")
14705        .is_some_and(|parameters| {
14706            constructor_parameters_look_like_expressions(parameters, source, bindings)
14707        })
14708}
14709
14710fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
14711    parameters: Node<'_>,
14712    source: &str,
14713    bindings: &LocalInferenceEngine<T>,
14714) -> bool {
14715    let mut cursor = parameters.walk();
14716    parameters.named_children(&mut cursor).any(|parameter| {
14717        !matches!(
14718            parameter.kind(),
14719            "parameter_declaration" | "optional_parameter_declaration"
14720        ) || parameter_declaration_is_local_expression(parameter, source, bindings)
14721    })
14722}
14723
14724fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
14725    parameter: Node<'_>,
14726    source: &str,
14727    bindings: &LocalInferenceEngine<T>,
14728) -> bool {
14729    let text = node_text(parameter, source).trim();
14730    if text
14731        .chars()
14732        .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
14733        && bindings.is_shadowed(text)
14734    {
14735        return true;
14736    }
14737
14738    let Some(base) = parameter
14739        .child_by_field_name("type")
14740        .filter(|base| base.kind() == "type_identifier")
14741    else {
14742        return false;
14743    };
14744    let Some(subscript) = parameter
14745        .child_by_field_name("declarator")
14746        .filter(|declarator| declarator.kind() == "abstract_array_declarator")
14747    else {
14748        return false;
14749    };
14750    subscript.child_by_field_name("size").is_some()
14751        && bindings.is_shadowed(node_text(base, source).trim())
14752}
14753
14754pub fn is_declaration_name(node: Node<'_>) -> bool {
14755    let Some(parent) = node.parent() else {
14756        return false;
14757    };
14758    if parent
14759        .child_by_field_name("name")
14760        .is_some_and(|name| same_node(name, node))
14761    {
14762        if matches!(
14763            parent.kind(),
14764            "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
14765        ) {
14766            return cpp_tag_specifier_declares_name(parent);
14767        }
14768        if matches!(
14769            parent.kind(),
14770            "namespace_definition"
14771                | "namespace_alias_definition"
14772                | "alias_declaration"
14773                | "enumerator"
14774        ) {
14775            return true;
14776        }
14777    }
14778
14779    let mut current = Some(parent);
14780    while let Some(ancestor) = current {
14781        let type_definition = ancestor.kind() == "type_definition";
14782        let mut child_cursor = ancestor.walk();
14783        if ancestor.named_children(&mut child_cursor).any(|child| {
14784            declaration_declarator(ancestor, child).is_some_and(|declarator| {
14785                declarator_name_path_contains(declarator, node, type_definition)
14786            })
14787        }) {
14788            return true;
14789        }
14790        if matches!(
14791            ancestor.kind(),
14792            "declaration"
14793                | "field_declaration"
14794                | "parameter_declaration"
14795                | "optional_parameter_declaration"
14796                | "function_definition"
14797                | "type_definition"
14798                | "alias_declaration"
14799                | "template_instantiation"
14800                | "class_specifier"
14801                | "struct_specifier"
14802                | "union_specifier"
14803                | "enum_specifier"
14804        ) {
14805            return false;
14806        }
14807        current = ancestor.parent();
14808    }
14809    false
14810}
14811
14812/// Whether tree-sitter recovered a qualified friend-class type as an ordinary
14813/// declaration's declarator inside a malformed class body.
14814///
14815/// An export macro between `class` and the class name can make the containing
14816/// body parse as a function body. A source declaration such as
14817/// `friend class internal::Friend;` then retains this exact structure:
14818/// `declaration(type: friend, ERROR(class), declarator: internal::Friend)`.
14819/// The declarator is a type reference despite its field role.
14820pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
14821    if !matches!(
14822        node.kind(),
14823        "qualified_identifier" | "scoped_type_identifier"
14824    ) {
14825        return false;
14826    }
14827    let Some(declaration) = node
14828        .parent()
14829        .filter(|parent| parent.kind() == "declaration")
14830    else {
14831        return false;
14832    };
14833    if declaration.child_by_field_name("declarator") != Some(node)
14834        || !declaration
14835            .child_by_field_name("type")
14836            .is_some_and(|friend| {
14837                friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
14838            })
14839    {
14840        return false;
14841    }
14842    let mut cursor = declaration.walk();
14843    let mut errors = declaration
14844        .named_children(&mut cursor)
14845        .filter(|child| child.kind() == "ERROR");
14846    let Some(error) = errors.next() else {
14847        return false;
14848    };
14849    errors.next().is_none()
14850        && error.named_child_count() == 1
14851        && error.named_child(0).is_some_and(|class| {
14852            class.kind() == "identifier" && node_text(class, source) == "class"
14853        })
14854}
14855
14856pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
14857    if !matches!(node.kind(), "identifier" | "field_identifier") {
14858        return false;
14859    }
14860    if let Some(parent) = node.parent() {
14861        if parent.kind() == "call_expression"
14862            && parent.child_by_field_name("function") == Some(node)
14863        {
14864            return false;
14865        }
14866        if matches!(parent.kind(), "labeled_statement" | "goto_statement")
14867            && parent.child_by_field_name("label") == Some(node)
14868        {
14869            return false;
14870        }
14871    }
14872    if is_declaration_name(node) {
14873        return false;
14874    }
14875    let mut current = node.parent();
14876    while let Some(ancestor) = current {
14877        match ancestor.kind() {
14878            "preproc_ifdef" | "preproc_ifndef" => {
14879                if ancestor
14880                    .child_by_field_name("name")
14881                    .is_some_and(|name| node_range_contains(name, node))
14882                {
14883                    return false;
14884                }
14885            }
14886            "preproc_if" | "preproc_elif" => {
14887                if ancestor
14888                    .child_by_field_name("condition")
14889                    .is_some_and(|condition| node_range_contains(condition, node))
14890                {
14891                    return false;
14892                }
14893            }
14894            "preproc_else" => {}
14895            kind if kind.starts_with("preproc_") => return false,
14896            _ => {}
14897        }
14898        if matches!(
14899            ancestor.kind(),
14900            "translation_unit" | "function_definition" | "compound_statement"
14901        ) {
14902            break;
14903        }
14904        current = ancestor.parent();
14905    }
14906    true
14907}
14908
14909fn node_range_contains(outer: Node<'_>, inner: Node<'_>) -> bool {
14910    outer.start_byte() <= inner.start_byte() && inner.end_byte() <= outer.end_byte()
14911}
14912
14913fn recovered_c_reference_node(
14914    visibility: &VisibilityIndex<'_>,
14915    file: &ProjectFile,
14916    node: Node<'_>,
14917    source: &str,
14918) -> bool {
14919    if node.start_byte() >= node.end_byte()
14920        || node.is_error()
14921        || node.is_missing()
14922        || !matches!(
14923            node.kind(),
14924            "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
14925        )
14926        || recovered_c_macro_binding_role(node)
14927        || recovered_c_label_role(node)
14928    {
14929        return false;
14930    }
14931    // A declaration name can be an identifier child of a recovered ERROR
14932    // (for example `EFI_STATUS Encode()` in C files parsed as C++). Do not
14933    // let a same-named macro turn that binder into a reference. An
14934    // assignment whose C callee follows the recovered `explicit` token is
14935    // the one expression-shaped exception: the generic declarator walk sees
14936    // its function_declarator as a declaration path, but the ERROR sibling
14937    // proves it is a call.
14938    let name = node_text(node, source);
14939    let recovered_function_call = recovered_c_function_call(visibility, file, node, name);
14940    let recovered_macro_call = recovered_c_function_declarator_invocation(node)
14941        && visibility.macro_name_may_be_bound_at(file, name, node.start_byte());
14942    let recovered_parenthesized_reference = recovered_c_parenthesized_declarator_reference(node);
14943    if is_declaration_name(node)
14944        && !recovered_c_explicit_assignment_callee(visibility, file, node, name)
14945        && !recovered_function_call
14946        && !recovered_macro_call
14947        && !recovered_parenthesized_reference
14948    {
14949        return false;
14950    }
14951
14952    if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
14953        return true;
14954    }
14955    if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
14956        return true;
14957    }
14958    if recovered_parenthesized_reference {
14959        return true;
14960    }
14961    if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
14962        if recovered_function_call {
14963            return true;
14964        }
14965        return visibility
14966            .visible_identifier_candidates(file, name)
14967            .any(|candidate| candidate.is_class() || candidate.is_module());
14968    }
14969    let visible = visibility
14970        .visible_identifier_candidates(file, name)
14971        .next()
14972        .is_some();
14973    visible
14974        && (recovered_c_reference_anchor(node)
14975            || recovered_c_error_expression_leaf(node)
14976            || recovered_function_call)
14977}
14978
14979fn push_recovered_c_range(
14980    ranges: &mut Vec<Range>,
14981    seen: &mut HashSet<(usize, usize)>,
14982    start_byte: usize,
14983    end_byte: usize,
14984    node: Node<'_>,
14985    limit: usize,
14986) -> bool {
14987    if start_byte >= end_byte || !seen.insert((start_byte, end_byte)) {
14988        return true;
14989    }
14990    if ranges.len() >= limit {
14991        return false;
14992    }
14993    ranges.push(Range {
14994        start_byte,
14995        end_byte,
14996        start_line: node.start_position().row,
14997        end_line: node.end_position().row,
14998    });
14999    true
15000}
15001
15002/// A reference leaf can sit directly beneath an ERROR while its ERROR parent
15003/// is still attached to a real expression (most often a recovered macro call
15004/// argument). The expression parent is the structured proof; an unindexed
15005/// identifier beneath a bare recovery envelope has no such proof.
15006fn recovered_c_error_expression_leaf(node: Node<'_>) -> bool {
15007    let mut current = node.parent();
15008    while let Some(parent) = current {
15009        if parent.is_error() {
15010            let Some(anchor) = parent.parent() else {
15011                return false;
15012            };
15013            return anchor.kind().ends_with("_expression")
15014                || matches!(
15015                    anchor.kind(),
15016                    "argument_list"
15017                        | "return_statement"
15018                        | "expression_statement"
15019                        | "case_statement"
15020                        | "initializer_list"
15021                        | "field_designator"
15022                        | "enumerator"
15023                );
15024        }
15025        if matches!(
15026            parent.kind(),
15027            "translation_unit" | "function_definition" | "compound_statement"
15028        ) {
15029            return false;
15030        }
15031        current = parent.parent();
15032    }
15033    false
15034}
15035
15036/// C recovery may represent a call as `identifier > function_declarator >
15037/// function_declarator > ERROR > compound_statement`. This shape is only a
15038/// call when the malformed declarator is attached to a real function body and
15039/// the name is an indexed visible callable. A declaration's
15040/// `ERROR > function_declarator > declaration` shape deliberately fails this
15041/// test.
15042fn recovered_c_function_call(
15043    visibility: &VisibilityIndex<'_>,
15044    file: &ProjectFile,
15045    node: Node<'_>,
15046    name: &str,
15047) -> bool {
15048    if !matches!(
15049        node.kind(),
15050        "identifier" | "field_identifier" | "type_identifier"
15051    ) {
15052        return false;
15053    }
15054    // C++ keywords used as C arguments can leave only the call prefix under
15055    // ERROR, with the remaining arguments in a following expression statement.
15056    // Require a statement boundary in a real block, an opening parenthesis,
15057    // and a recovered argument. A bare ERROR identifier supplies no call role.
15058    let error_call_prefix = node.parent().is_some_and(|error| {
15059        error.is_error()
15060            && error
15061                .parent()
15062                .is_some_and(|parent| parent.kind() == "compound_statement")
15063    }) && node
15064        .prev_sibling()
15065        .is_none_or(|previous| previous.kind() == ";")
15066        && node.next_sibling().is_some_and(|open| {
15067            open.kind() == "("
15068                && open.next_named_sibling().is_some_and(|argument| {
15069                    argument.kind() == "parameter_declaration" && !argument.has_error()
15070                })
15071        });
15072    (error_call_prefix || recovered_c_function_declarator_invocation(node))
15073        && visibility
15074            .visible_identifier_candidates(file, name)
15075            .any(CodeUnit::is_function)
15076}
15077
15078/// Return whether a C identifier belongs to a call-shaped declarator that the
15079/// C++ grammar put under an `ERROR` node.
15080///
15081/// The malformed call can be direct (`f(arg)`) or nested in a parameter
15082/// declaration when one of its arguments looks like a type (`f(TYPE, value)`).
15083/// In both cases the CST retains the function-declarator and its enclosing
15084/// recovery envelope. We walk only those declarator/parameter nodes and stop
15085/// at a real expression-bearing boundary; declarations therefore cannot pass
15086/// this predicate merely because they have a parameter list.
15087fn recovered_c_function_declarator_invocation(node: Node<'_>) -> bool {
15088    let mut function_declarator = if node.parent().is_some_and(|parent| {
15089        parent.kind() == "function_declarator"
15090            && parent.child_by_field_name("declarator") == Some(node)
15091    }) {
15092        node.parent().expect("checked function declarator parent")
15093    } else {
15094        let Some(parameter) = node.parent().filter(|parent| {
15095            parent.kind() == "parameter_declaration"
15096                && parent.child_by_field_name("type") == Some(node)
15097        }) else {
15098            return false;
15099        };
15100        if !parameter
15101            .child_by_field_name("declarator")
15102            .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
15103        {
15104            return false;
15105        }
15106        let Some(parameters) = parameter
15107            .parent()
15108            .filter(|parent| parent.kind() == "parameter_list")
15109        else {
15110            return false;
15111        };
15112        let Some(function_declarator) = parameters
15113            .parent()
15114            .filter(|parent| parent.kind() == "function_declarator")
15115        else {
15116            return false;
15117        };
15118        function_declarator
15119    };
15120
15121    // Recovery may absorb the next statement's parenthesized cast into a
15122    // second function declarator. Follow only the original declarator chain.
15123    while let Some(parent) = function_declarator.parent().filter(|parent| {
15124        parent.kind() == "function_declarator"
15125            && parent.child_by_field_name("declarator") == Some(function_declarator)
15126    }) {
15127        function_declarator = parent;
15128    }
15129    let Some(mut current) = function_declarator
15130        .parent()
15131        .filter(|parent| parent.is_error())
15132    else {
15133        return false;
15134    };
15135    loop {
15136        let Some(parent) = current.parent() else {
15137            return false;
15138        };
15139        if matches!(
15140            parent.kind(),
15141            "translation_unit"
15142                | "compound_statement"
15143                | "preproc_if"
15144                | "preproc_ifdef"
15145                | "preproc_ifndef"
15146                | "preproc_else"
15147                | "preproc_elif"
15148        ) {
15149            return true;
15150        }
15151        if parent.kind() == "function_definition"
15152            && parent.child_by_field_name("declarator") == Some(current)
15153            && parent.named_child(0) == Some(current)
15154            && parent.child_by_field_name("body").is_some()
15155        {
15156            return true;
15157        }
15158        if parent.is_error()
15159            || matches!(
15160                parent.kind(),
15161                "parameter_declaration"
15162                    | "parameter_list"
15163                    | "function_declarator"
15164                    | "abstract_function_declarator"
15165                    | "parenthesized_declarator"
15166            )
15167        {
15168            current = parent;
15169            continue;
15170        }
15171        return false;
15172    }
15173}
15174
15175/// C permits an identifier named `typename`. The C++ grammar can recover an
15176/// assignment using that identifier as a declaration whose declarator is a
15177/// parenthesized argument list, for example `typename = f(ctx, value)`. Only
15178/// the argument retained beneath the nested `ERROR` is a reference; sibling
15179/// declarator identifiers remain binders/grammar artifacts.
15180fn recovered_c_parenthesized_declarator_reference(node: Node<'_>) -> bool {
15181    let Some(error) = node.parent().filter(|parent| parent.is_error()) else {
15182        return false;
15183    };
15184    if error.named_child_count() != 1 || error.named_child(0) != Some(node) {
15185        return false;
15186    }
15187    let Some(declarator) = error
15188        .parent()
15189        .filter(|parent| parent.kind() == "parenthesized_declarator")
15190    else {
15191        return false;
15192    };
15193    let Some(declaration) = declarator
15194        .parent()
15195        .filter(|parent| parent.kind() == "declaration")
15196    else {
15197        return false;
15198    };
15199    if declaration.child_by_field_name("declarator") != Some(declarator) {
15200        return false;
15201    }
15202    let Some(type_node) = declaration.child_by_field_name("type") else {
15203        return false;
15204    };
15205    type_node.kind() == "dependent_type"
15206        && type_node
15207            .child(0)
15208            .is_some_and(|keyword| keyword.kind() == "typename")
15209}
15210
15211fn recovered_c_explicit_assignment_callee(
15212    visibility: &VisibilityIndex<'_>,
15213    file: &ProjectFile,
15214    node: Node<'_>,
15215    name: &str,
15216) -> bool {
15217    let mut current = node;
15218    let error = loop {
15219        let Some(parent) = current.parent() else {
15220            return false;
15221        };
15222        if parent.is_error() {
15223            break parent;
15224        }
15225        current = parent;
15226    };
15227    let mut cursor = error.walk();
15228    let explicit_recovery_precedes_callee = error
15229        .named_children(&mut cursor)
15230        .take_while(|child| child.start_byte() < node.start_byte())
15231        .any(|child| child.kind() == "explicit_function_specifier");
15232    if !explicit_recovery_precedes_callee {
15233        return false;
15234    }
15235    visibility
15236        .visible_identifier_candidates(file, name)
15237        .any(CodeUnit::is_function)
15238}
15239
15240fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
15241    while let Some(parent) = node.parent() {
15242        if matches!(
15243            parent.kind(),
15244            "preproc_def" | "preproc_function_def" | "preproc_params"
15245        ) {
15246            return true;
15247        }
15248        if parent.is_error()
15249            || matches!(
15250                parent.kind(),
15251                "translation_unit" | "function_definition" | "compound_statement"
15252            )
15253        {
15254            return false;
15255        }
15256        node = parent;
15257    }
15258    false
15259}
15260
15261fn recovered_c_label_role(node: Node<'_>) -> bool {
15262    node.parent().is_some_and(|parent| {
15263        matches!(parent.kind(), "labeled_statement" | "goto_statement")
15264            && parent.child_by_field_name("label") == Some(node)
15265    })
15266}
15267
15268fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
15269    while let Some(parent) = node.parent() {
15270        if parent.is_error() {
15271            return false;
15272        }
15273        // A C macro call recovered as a function declarator can parse an
15274        // assignment-shaped argument as an optional parameter. Its
15275        // `default_value` field remains an expression role even though the
15276        // surrounding call shape is beneath ERROR.
15277        if parent.kind() == "optional_parameter_declaration"
15278            && parent
15279                .child_by_field_name("default_value")
15280                .is_some_and(|value| node_range_contains(value, node))
15281        {
15282            return true;
15283        }
15284        if parent.kind().ends_with("_expression")
15285            || matches!(
15286                parent.kind(),
15287                "argument_list"
15288                    | "return_statement"
15289                    | "expression_statement"
15290                    | "case_statement"
15291                    | "initializer_list"
15292                    | "init_declarator"
15293                    | "array_declarator"
15294                    | "field_designator"
15295                    | "enumerator"
15296            )
15297        {
15298            return true;
15299        }
15300        if matches!(
15301            parent.kind(),
15302            "translation_unit"
15303                | "function_definition"
15304                | "compound_statement"
15305                | "declaration"
15306                | "field_declaration"
15307                | "parameter_declaration"
15308        ) {
15309            return false;
15310        }
15311        node = parent;
15312    }
15313    false
15314}
15315
15316/// Whether a parameter declaration belongs to the callable scope whose body can
15317/// contain references to it.
15318///
15319/// Error recovery can wrap a macro-decorated class body in a synthetic outer
15320/// `function_definition`. Merely finding any callable ancestor would then leak
15321/// parameters from member prototypes into later member bodies. Require the
15322/// parameter to be inside that definition's own declarator instead.
15323pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
15324    let mut current = parameter.parent();
15325    while let Some(ancestor) = current {
15326        if ancestor.kind() == "lambda_expression" {
15327            return ancestor
15328                .child_by_field_name("declarator")
15329                .is_some_and(|declarator| {
15330                    declarator.start_byte() <= parameter.start_byte()
15331                        && parameter.end_byte() <= declarator.end_byte()
15332                });
15333        }
15334        if ancestor.kind() == "function_definition" {
15335            return ancestor
15336                .child_by_field_name("declarator")
15337                .is_some_and(|declarator| {
15338                    declarator.start_byte() <= parameter.start_byte()
15339                        && parameter.end_byte() <= declarator.end_byte()
15340                });
15341        }
15342        current = ancestor.parent();
15343    }
15344    false
15345}
15346
15347pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
15348    let mut current = node.parent();
15349    while let Some(ancestor) = current {
15350        if matches!(
15351            ancestor.kind(),
15352            "parameter_declaration" | "optional_parameter_declaration"
15353        ) {
15354            return ancestor
15355                .child_by_field_name("type")
15356                .is_some_and(|type_node| {
15357                    type_node.start_byte() <= node.start_byte()
15358                        && node.end_byte() <= type_node.end_byte()
15359                });
15360        }
15361        if matches!(
15362            ancestor.kind(),
15363            "function_definition" | "lambda_expression" | "compound_statement"
15364        ) {
15365            return false;
15366        }
15367        current = ancestor.parent();
15368    }
15369    false
15370}
15371
15372fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
15373    if specifier.child_by_field_name("body").is_some() {
15374        return true;
15375    }
15376    let mut current = specifier.parent();
15377    while let Some(ancestor) = current {
15378        match ancestor.kind() {
15379            "type_descriptor"
15380            | "parameter_declaration"
15381            | "optional_parameter_declaration"
15382            | "template_argument_list"
15383            | "cast_expression" => return false,
15384            "declaration" | "field_declaration" => {
15385                let mut cursor = ancestor.walk();
15386                return ancestor
15387                    .children_by_field_name("declarator", &mut cursor)
15388                    .next()
15389                    .is_none();
15390            }
15391            "translation_unit" => return true,
15392            _ => current = ancestor.parent(),
15393        }
15394    }
15395    false
15396}
15397
15398pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
15399    match node.kind() {
15400        "identifier"
15401        | "field_identifier"
15402        | "qualified_identifier"
15403        | "scoped_identifier"
15404        | "operator_name"
15405        | "destructor_name"
15406        | "literal_operator_name" => Some(node),
15407        "reference_declarator" | "parenthesized_declarator" => {
15408            node.named_child(0).and_then(declarator_name_node)
15409        }
15410        _ => node
15411            .child_by_field_name("declarator")
15412            .or_else(|| node.child_by_field_name("name"))
15413            .or_else(|| node.child_by_field_name("field"))
15414            .and_then(declarator_name_node),
15415    }
15416}
15417
15418fn declarator_name_path_contains(
15419    declarator: Node<'_>,
15420    candidate: Node<'_>,
15421    allow_type_identifier: bool,
15422) -> bool {
15423    let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
15424        return false;
15425    };
15426    let mut current = Some(declarator);
15427    while let Some(node) = current {
15428        if same_node(node, candidate) {
15429            return true;
15430        }
15431        if same_node(node, name) {
15432            return false;
15433        }
15434        current = node
15435            .child_by_field_name("declarator")
15436            .or_else(|| node.child_by_field_name("name"))
15437            .or_else(|| node.child_by_field_name("field"));
15438    }
15439    false
15440}
15441
15442fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
15443    match node.kind() {
15444        "identifier"
15445        | "field_identifier"
15446        | "operator_name"
15447        | "destructor_name"
15448        | "literal_operator_name" => Some(node),
15449        "type_identifier" if allow_type_identifier => Some(node),
15450        _ => node
15451            .child_by_field_name("declarator")
15452            .or_else(|| node.child_by_field_name("name"))
15453            .or_else(|| node.child_by_field_name("field"))
15454            .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
15455    }
15456}
15457
15458/// True when `node` is a component of a larger structured type node whose outer
15459/// range is the single reference surfaced to callers.
15460pub fn is_nested_type_node(node: Node<'_>) -> bool {
15461    node.parent().is_some_and(|parent| {
15462        matches!(
15463            parent.kind(),
15464            "qualified_identifier" | "scoped_type_identifier" | "template_type"
15465        )
15466    })
15467}
15468
15469pub struct OutOfLineMemberDefinitionOwners<'tree> {
15470    pub owners: Vec<(Node<'tree>, CodeUnit)>,
15471    innermost: Option<(Node<'tree>, CodeUnit)>,
15472}
15473
15474impl OutOfLineMemberDefinitionOwners<'_> {
15475    pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
15476        self.innermost.as_ref().map(|(node, owner)| (*node, owner))
15477    }
15478}
15479
15480pub struct QualifiedOwnerComponents<'tree> {
15481    pub nodes: Vec<Node<'tree>>,
15482    pub names: Vec<String>,
15483    pub global: bool,
15484}
15485
15486/// True when each structured qualifier on the callable-name path has a real
15487/// `::` token. A macro-prefixed return type can make tree-sitter insert a
15488/// zero-width missing separator and parse `TYPE Result<T> method()` as the
15489/// false qualified declarator `Result<T>::method`.
15490pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
15491    let mut stack = vec![node];
15492    let mut found_separator = false;
15493    while let Some(current) = stack.pop() {
15494        if !matches!(
15495            current.kind(),
15496            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
15497        ) {
15498            continue;
15499        }
15500        let mut current_has_separator = false;
15501        for child in children_iter(current) {
15502            if child.kind() == "::" {
15503                if child.is_missing() {
15504                    return false;
15505                }
15506                current_has_separator = true;
15507                found_separator = true;
15508            }
15509        }
15510        if !current_has_separator {
15511            return false;
15512        }
15513        for field in ["scope", "name"] {
15514            if let Some(child) = current.child_by_field_name(field)
15515                && matches!(
15516                    child.kind(),
15517                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
15518                )
15519            {
15520                stack.push(child);
15521            }
15522        }
15523    }
15524    found_separator
15525}
15526
15527pub fn qualified_owner_components<'tree>(
15528    node: Node<'tree>,
15529    source: &str,
15530) -> Option<QualifiedOwnerComponents<'tree>> {
15531    if !qualified_name_has_concrete_scope_separators(node) {
15532        return None;
15533    }
15534    let mut nodes = cpp_name_component_nodes(node)?;
15535    nodes.pop()?;
15536    if nodes.is_empty() {
15537        return None;
15538    }
15539    let names = nodes
15540        .iter()
15541        .map(|component| node_text(*component, source).to_string())
15542        .collect();
15543    Some(QualifiedOwnerComponents {
15544        nodes,
15545        names,
15546        global: is_globally_qualified_cpp_name(node),
15547    })
15548}
15549
15550pub fn out_of_line_member_definition_owner<'tree>(
15551    analyzer: &CppGraphSource<'_>,
15552    visibility: &VisibilityIndex<'_>,
15553    file: &ProjectFile,
15554    source: &str,
15555    node: Node<'tree>,
15556) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
15557    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
15558        || !has_ancestor_kind(node, "function_definition")
15559        || !is_function_declarator_name_root(node)
15560    {
15561        return None;
15562    }
15563    let qualified = qualified_owner_components(node, source)?;
15564    let lexical_scope = enclosing_namespace_components(node, source)?;
15565    let mut owners = Vec::new();
15566    let mut innermost = None;
15567
15568    for component_count in 1..=qualified.names.len() {
15569        if let LexicalTypeResolution::Resolved { unit, .. } = visibility
15570            .resolve_type_components_lexically(
15571                analyzer,
15572                file,
15573                &qualified.names[..component_count],
15574                qualified.global,
15575                &lexical_scope,
15576            )
15577            && !owners
15578                .iter()
15579                .any(|(_, existing)| same_visible_symbol(existing, &unit))
15580        {
15581            if component_count == qualified.names.len() {
15582                innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
15583            }
15584            owners.push((qualified.nodes[component_count - 1], unit));
15585        }
15586    }
15587
15588    // The C++ analyzer has already reconciled an indexed out-of-line callable
15589    // against the include-visible class table. Consult that canonical owner
15590    // chain only when ordinary lexical lookup could not recover the innermost
15591    // owner.  A one-segment qualifier is safe here only when the enclosing
15592    // indexed callable has an authoritative class owner and the parser's
15593    // namespace path is a (possibly sparse) subsequence of that owner path.
15594    // The latter is what lets macro-wrapped namespace sentinels recover a
15595    // missing `time_internal`/`cord_internal` component without guessing an
15596    // unrelated short name.
15597    if innermost.is_none() {
15598        let indexed_owner_components = visibility
15599            .indexed_enclosing_owner_scope(analyzer, file, node)
15600            .or_else(|| {
15601                // Retain the legacy rendered-name fallback for the existing
15602                // multi-segment path when an enclosing owner chain is not
15603                // available (for example, cache-loaded units without parent
15604                // links).  One-segment recovery must stay canonical-only.
15605                if qualified.names.len() <= 1 {
15606                    return None;
15607                }
15608                let range = Range {
15609                    start_byte: node.start_byte(),
15610                    end_byte: node.end_byte(),
15611                    start_line: node.start_position().row,
15612                    end_line: node.end_position().row,
15613                };
15614                let start = analyzer.enclosing_code_unit(file, &range)?;
15615                let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
15616                    brokk_bifrost_core::analyzer::Language::Cpp,
15617                    &cpp_name_for(&start),
15618                );
15619                components.pop();
15620                Some(components)
15621            });
15622        if let Some(indexed_owner_components) = indexed_owner_components
15623            && indexed_owner_components.len() > qualified.names.len()
15624            && indexed_owner_components.ends_with(&qualified.names)
15625            && indexed_namespace_path_is_recoverable(
15626                &lexical_scope,
15627                &indexed_owner_components,
15628                qualified.names.len(),
15629            )
15630            // A globally-qualified one-segment owner is an explicit request
15631            // for the top-level binding; do not reinterpret it as a missing
15632            // namespace component.  Existing multi-segment global lookups
15633            // retain their historical indexed recovery.
15634            && (qualified.names.len() > 1 || !qualified.global)
15635        {
15636            let namespace_count = indexed_owner_components.len() - qualified.names.len();
15637            for component_count in 1..=qualified.names.len() {
15638                let expected = &indexed_owner_components[..namespace_count + component_count];
15639                let owner_node = qualified.nodes[component_count - 1];
15640                for owner in visibility
15641                    .visible_identifier_candidates(file, &qualified.names[component_count - 1])
15642                    .filter(|candidate| candidate.is_class())
15643                    .filter(|candidate| {
15644                        canonical_cpp_scope_components(candidate) == expected
15645                            && visibility.external_type_candidate_visible_in_context(
15646                                analyzer, file, candidate, node,
15647                            )
15648                    })
15649                {
15650                    if component_count == qualified.names.len() && innermost.is_none() {
15651                        innermost = Some((owner_node, owner.clone()));
15652                    }
15653                    if !owners
15654                        .iter()
15655                        .any(|(_, existing)| same_symbol(existing, owner))
15656                    {
15657                        owners.push((owner_node, owner.clone()));
15658                    }
15659                }
15660            }
15661        }
15662    }
15663    (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
15664}
15665
15666fn is_function_declarator_name_root(node: Node<'_>) -> bool {
15667    let mut current = node;
15668    while let Some(parent) = current.parent() {
15669        if parent.kind() == "function_declarator" {
15670            return parent.child_by_field_name("declarator") == Some(current);
15671        }
15672        if matches!(
15673            parent.kind(),
15674            "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
15675        ) && parent.child_by_field_name("declarator") == Some(current)
15676        {
15677            current = parent;
15678            continue;
15679        }
15680        return false;
15681    }
15682    false
15683}
15684
15685pub fn append_cpp_name_components(
15686    node: Node<'_>,
15687    source: &str,
15688    out: &mut Vec<String>,
15689) -> Option<()> {
15690    out.extend(
15691        cpp_name_component_nodes(node)?
15692            .into_iter()
15693            .map(|component| node_text(component, source).to_string()),
15694    );
15695    Some(())
15696}
15697
15698pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
15699    let mut components = Vec::new();
15700    append_cpp_name_components(node, source, &mut components)?;
15701    Some(components)
15702}
15703
15704/// Resolve a structured type spelling from an object-like macro replacement
15705/// when definition-site source order has no answer.
15706///
15707/// Macro replacement tokens are looked up where the macro is expanded, so a
15708/// type declared later in the defining header can still be their destination.
15709/// Without expanding every invocation, accept only one include-visible logical
15710/// class or alias whose structured path ends in the replacement components.
15711/// An ordinary lexical answer always takes precedence at the call site.
15712pub fn unique_macro_replacement_type_candidate(
15713    analyzer: &CppGraphSource<'_>,
15714    visibility: &VisibilityIndex<'_>,
15715    file: &ProjectFile,
15716    components: &[String],
15717) -> Option<CodeUnit> {
15718    let terminal = components.last()?;
15719    let mut candidates = Vec::new();
15720    for candidate in visibility
15721        .visible_identifier_candidates(file, terminal)
15722        .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
15723        .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
15724    {
15725        if !candidates
15726            .iter()
15727            .any(|existing| same_logical_symbol(existing, candidate))
15728        {
15729            candidates.push(candidate.clone());
15730        }
15731    }
15732    (candidates.len() == 1).then(|| candidates.remove(0))
15733}
15734
15735/// The base scopes named by member using-declarations for `member` in one
15736/// class source range.
15737///
15738/// The grammar supplies the qualified identifier and each component. Keep
15739/// this interpretation shared between forward overload lookup and inverse
15740/// owner routing rather than reparsing a rendered `Base::member` string at
15741/// either call site.
15742pub fn cpp_member_using_declaration_scopes(source: &str, member: &str) -> Vec<String> {
15743    let mut parser = Parser::new();
15744    if parser
15745        .set_language(&tree_sitter_cpp::LANGUAGE.into())
15746        .is_err()
15747    {
15748        return Vec::new();
15749    }
15750    let Some(tree) = parser.parse(source, None) else {
15751        return Vec::new();
15752    };
15753    let mut scopes = Vec::new();
15754    let mut pending = vec![tree.root_node()];
15755    while let Some(node) = pending.pop() {
15756        if node.kind() == "using_declaration" {
15757            let Some(imported) = node.named_child(0) else {
15758                continue;
15759            };
15760            let Some(mut components) = cpp_type_name_components(imported, source) else {
15761                continue;
15762            };
15763            if components.pop().as_deref() == Some(member) && !components.is_empty() {
15764                scopes.push(components.join("::"));
15765            }
15766            continue;
15767        }
15768        push_named_children_reversed(node, &mut pending);
15769    }
15770    scopes
15771}
15772
15773/// Whether a structured using-declaration scope can name `qualified` as an
15774/// ancestor class. The boundary check prevents `Base` from matching
15775/// `OtherBase` while allowing a relative `Base` spelling to match `ns::Base`.
15776pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
15777    qualified == scope
15778        || qualified
15779            .strip_suffix(scope)
15780            .is_some_and(|prefix| prefix.ends_with("::"))
15781}
15782
15783/// Whether `node` is the direct structured type payload of a template
15784/// argument. This role remains meaningful even when a surrounding expression
15785/// is below tree-sitter recovery, because both the `template_argument_list`
15786/// and the `type_descriptor` retain their named fields.
15787pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
15788    let Some(type_descriptor) = node.parent() else {
15789        return false;
15790    };
15791    if type_descriptor.kind() != "type_descriptor"
15792        || type_descriptor.child_by_field_name("type") != Some(node)
15793    {
15794        return false;
15795    }
15796    let Some(arguments) = type_descriptor.parent() else {
15797        return false;
15798    };
15799    if arguments.kind() != "template_argument_list" {
15800        return false;
15801    }
15802    arguments.parent().is_some_and(|parent| {
15803        matches!(parent.kind(), "template_type" | "template_function")
15804            && parent.child_by_field_name("arguments") == Some(arguments)
15805    })
15806}
15807
15808pub fn cpp_template_reference_arguments(
15809    mut node: Node<'_>,
15810    source: &str,
15811) -> Option<Vec<CppTemplateExpression>> {
15812    loop {
15813        match node.kind() {
15814            "template_type" | "template_function" => {
15815                let arguments = node.child_by_field_name("arguments")?;
15816                let mut cursor = arguments.walk();
15817                return Some(
15818                    arguments
15819                        .named_children(&mut cursor)
15820                        .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
15821                        .map(|argument| CppTemplateExpression {
15822                            text: normalize_cpp_whitespace(node_text(argument, source)),
15823                            // One template term from a resolver query; see `ParentIndex::unindexed`.
15824                            term: cpp_template_term(
15825                                argument,
15826                                source,
15827                                &[],
15828                                &ParentIndex::unindexed(),
15829                            ),
15830                        })
15831                        .collect(),
15832                );
15833            }
15834            "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
15835                node = node
15836                    .child_by_field_name("name")
15837                    .or_else(|| node.child_by_field_name("type"))?;
15838            }
15839            _ => return None,
15840        }
15841    }
15842}
15843
15844fn cpp_reconcile_primary_template_parameters(
15845    candidates: &[(&CodeUnit, &CppTemplateMetadata)],
15846    preferred: &CodeUnit,
15847) -> Option<Vec<CppTemplateParameterMetadata>> {
15848    let canonical = candidates
15849        .iter()
15850        .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
15851    let mut merged = canonical
15852        .parameters
15853        .iter()
15854        .map(|parameter| CppTemplateParameterMetadata {
15855            name: parameter.name.clone(),
15856            kind: parameter.kind,
15857            variadic: parameter.variadic,
15858            default: None,
15859        })
15860        .collect::<Vec<_>>();
15861
15862    for (_, metadata) in candidates {
15863        if metadata.parameters.len() != merged.len() {
15864            return None;
15865        }
15866        let rename_bindings = metadata
15867            .parameters
15868            .iter()
15869            .zip(&merged)
15870            .map(|(parameter, canonical)| {
15871                (
15872                    parameter.name.clone(),
15873                    CppTemplateTerm::Parameter(canonical.name.clone()),
15874                )
15875            })
15876            .collect::<HashMap<_, _>>();
15877        for ((parameter, canonical), merged_parameter) in metadata
15878            .parameters
15879            .iter()
15880            .zip(&canonical.parameters)
15881            .zip(&mut merged)
15882        {
15883            if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
15884                return None;
15885            }
15886            let Some(default) = &parameter.default else {
15887                continue;
15888            };
15889            let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
15890            if let Some(existing) = &merged_parameter.default {
15891                if !cpp_template_terms_equal(&existing.term, &normalized_term) {
15892                    return None;
15893                }
15894            } else {
15895                merged_parameter.default = Some(CppTemplateExpression {
15896                    text: default.text.clone(),
15897                    term: normalized_term,
15898                });
15899            }
15900        }
15901    }
15902    Some(merged)
15903}
15904
15905pub fn cpp_bind_template_arguments(
15906    parameters: &[CppTemplateParameterMetadata],
15907    explicit_arguments: &[CppTemplateExpression],
15908) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
15909    let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
15910    if variadic_index.is_some_and(|index| {
15911        index + 1 != parameters.len()
15912            || parameters[index + 1..]
15913                .iter()
15914                .any(|parameter| parameter.variadic)
15915    }) {
15916        return None;
15917    }
15918    let fixed_count = variadic_index.unwrap_or(parameters.len());
15919    if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
15920        return None;
15921    }
15922    let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
15923    let mut expanded = explicit_arguments[..explicit_fixed_count]
15924        .iter()
15925        .map(cpp_clone_template_expression_iterative)
15926        .collect::<Vec<_>>();
15927    let mut bindings = HashMap::default();
15928    for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
15929        bindings.insert(
15930            parameter.name.clone(),
15931            cpp_clone_template_term_iterative(&argument.term),
15932        );
15933    }
15934    for parameter in &parameters[explicit_fixed_count..fixed_count] {
15935        let default = parameter.default.as_ref()?;
15936        let term = cpp_substitute_template_term(&default.term, &bindings)?;
15937        bindings.insert(parameter.name.clone(), term.clone());
15938        expanded.push(CppTemplateExpression {
15939            text: default.text.clone(),
15940            term,
15941        });
15942    }
15943    if let Some(index) = variadic_index {
15944        let packed_arguments = &explicit_arguments[explicit_fixed_count..];
15945        expanded.extend(
15946            packed_arguments
15947                .iter()
15948                .map(cpp_clone_template_expression_iterative),
15949        );
15950        bindings.insert(
15951            parameters[index].name.clone(),
15952            CppTemplateTerm::Node {
15953                kind: "parameter_pack".to_string(),
15954                children: packed_arguments
15955                    .iter()
15956                    .map(|argument| cpp_clone_template_term_iterative(&argument.term))
15957                    .collect(),
15958            },
15959        );
15960    }
15961    Some((expanded, bindings))
15962}
15963
15964fn cpp_specialization_matches(
15965    metadata: &CppTemplateMetadata,
15966    arguments: &[CppTemplateExpression],
15967) -> bool {
15968    if metadata.specialization_arguments.len() != arguments.len() {
15969        return false;
15970    }
15971    let parameter_names = metadata
15972        .parameters
15973        .iter()
15974        .map(|parameter| parameter.name.as_str())
15975        .collect::<HashSet<_>>();
15976    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
15977    for (pattern, argument) in metadata.specialization_arguments.iter().zip(arguments) {
15978        if !cpp_unify_template_term(
15979            &pattern.term,
15980            &argument.term,
15981            &parameter_names,
15982            &mut bindings,
15983        ) {
15984            return false;
15985        }
15986    }
15987    true
15988}
15989
15990fn cpp_specialization_more_specialized(
15991    candidate: &CppTemplateMetadata,
15992    other: &CppTemplateMetadata,
15993) -> bool {
15994    cpp_specialization_pattern_accepts(other, candidate)
15995        && !cpp_specialization_pattern_accepts(candidate, other)
15996}
15997
15998fn cpp_specialization_pattern_accepts(
15999    broader: &CppTemplateMetadata,
16000    narrower: &CppTemplateMetadata,
16001) -> bool {
16002    if broader.specialization_arguments.len() != narrower.specialization_arguments.len() {
16003        return false;
16004    }
16005    let parameter_names = broader
16006        .parameters
16007        .iter()
16008        .map(|parameter| parameter.name.as_str())
16009        .collect::<HashSet<_>>();
16010    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
16011    broader
16012        .specialization_arguments
16013        .iter()
16014        .zip(&narrower.specialization_arguments)
16015        .all(|(pattern, argument)| {
16016            cpp_unify_template_term(
16017                &pattern.term,
16018                &argument.term,
16019                &parameter_names,
16020                &mut bindings,
16021            )
16022        })
16023}
16024
16025pub fn cpp_substitute_template_term(
16026    term: &CppTemplateTerm,
16027    bindings: &HashMap<String, CppTemplateTerm>,
16028) -> Option<CppTemplateTerm> {
16029    enum Work<'a> {
16030        Visit(&'a CppTemplateTerm),
16031        Build { kind: String, child_count: usize },
16032    }
16033
16034    let mut work = vec![Work::Visit(term)];
16035    let mut substituted = Vec::new();
16036    while let Some(next) = work.pop() {
16037        match next {
16038            Work::Visit(CppTemplateTerm::Parameter(name)) => {
16039                substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
16040            }
16041            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
16042                substituted.push(CppTemplateTerm::Atom {
16043                    kind: kind.clone(),
16044                    text: text.clone(),
16045                });
16046            }
16047            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
16048                work.push(Work::Build {
16049                    kind: kind.clone(),
16050                    child_count: children.len(),
16051                });
16052                work.extend(children.iter().rev().map(Work::Visit));
16053            }
16054            Work::Build { kind, child_count } => {
16055                let children = substituted.split_off(substituted.len() - child_count);
16056                substituted.push(CppTemplateTerm::Node { kind, children });
16057            }
16058        }
16059    }
16060    substituted.pop()
16061}
16062
16063pub fn cpp_substitute_template_arguments(
16064    arguments: &[CppTemplateExpression],
16065    bindings: &HashMap<String, CppTemplateTerm>,
16066) -> Option<Vec<CppTemplateExpression>> {
16067    let mut substituted = Vec::new();
16068    for argument in arguments {
16069        let CppTemplateTerm::Node { kind, children } = &argument.term else {
16070            substituted.push(CppTemplateExpression {
16071                text: argument.text.clone(),
16072                term: cpp_substitute_template_term(&argument.term, bindings)?,
16073            });
16074            continue;
16075        };
16076        if kind != "parameter_pack_expansion" {
16077            substituted.push(CppTemplateExpression {
16078                text: argument.text.clone(),
16079                term: cpp_substitute_template_term(&argument.term, bindings)?,
16080            });
16081            continue;
16082        }
16083        let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
16084            return None;
16085        };
16086        if ellipsis != "..." {
16087            return None;
16088        }
16089
16090        let mut pack_names = Vec::new();
16091        let mut work = vec![pattern];
16092        while let Some(term) = work.pop() {
16093            match term {
16094                CppTemplateTerm::Parameter(name)
16095                    if matches!(
16096                        bindings.get(name),
16097                        Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
16098                    ) =>
16099                {
16100                    if !pack_names.contains(name) {
16101                        pack_names.push(name.clone());
16102                    }
16103                }
16104                CppTemplateTerm::Node { children, .. } => work.extend(children),
16105                CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
16106            }
16107        }
16108        let first_pack = pack_names.first()?;
16109        let CppTemplateTerm::Node {
16110            children: first_elements,
16111            ..
16112        } = bindings.get(first_pack)?
16113        else {
16114            return None;
16115        };
16116        let pack_len = first_elements.len();
16117        for pack_name in &pack_names {
16118            let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16119                return None;
16120            };
16121            if children.len() != pack_len {
16122                return None;
16123            }
16124        }
16125        for index in 0..pack_len {
16126            let mut element_bindings = bindings.clone();
16127            for pack_name in &pack_names {
16128                let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16129                    return None;
16130                };
16131                element_bindings.insert(
16132                    pack_name.clone(),
16133                    cpp_clone_template_term_iterative(&children[index]),
16134                );
16135            }
16136            substituted.push(CppTemplateExpression {
16137                text: argument.text.clone(),
16138                term: cpp_substitute_template_term(pattern, &element_bindings)?,
16139            });
16140        }
16141    }
16142    Some(substituted)
16143}
16144
16145fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
16146    enum Work<'a> {
16147        Visit(&'a CppTemplateTerm),
16148        Build { kind: String, child_count: usize },
16149    }
16150
16151    let mut work = vec![Work::Visit(term)];
16152    let mut cloned = Vec::new();
16153    while let Some(next) = work.pop() {
16154        match next {
16155            Work::Visit(CppTemplateTerm::Parameter(name)) => {
16156                cloned.push(CppTemplateTerm::Parameter(name.clone()));
16157            }
16158            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
16159                cloned.push(CppTemplateTerm::Atom {
16160                    kind: kind.clone(),
16161                    text: text.clone(),
16162                });
16163            }
16164            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
16165                work.push(Work::Build {
16166                    kind: kind.clone(),
16167                    child_count: children.len(),
16168                });
16169                work.extend(children.iter().rev().map(Work::Visit));
16170            }
16171            Work::Build { kind, child_count } => {
16172                let children = cloned.split_off(cloned.len() - child_count);
16173                cloned.push(CppTemplateTerm::Node { kind, children });
16174            }
16175        }
16176    }
16177    cloned
16178        .pop()
16179        .expect("template term traversal emits one root")
16180}
16181
16182fn cpp_clone_template_expression_iterative(
16183    expression: &CppTemplateExpression,
16184) -> CppTemplateExpression {
16185    CppTemplateExpression {
16186        text: expression.text.clone(),
16187        term: cpp_clone_template_term_iterative(&expression.term),
16188    }
16189}
16190
16191pub fn cpp_unify_template_term(
16192    pattern: &CppTemplateTerm,
16193    argument: &CppTemplateTerm,
16194    parameters: &HashSet<&str>,
16195    bindings: &mut HashMap<String, CppTemplateTerm>,
16196) -> bool {
16197    let mut work = vec![(pattern, argument)];
16198    while let Some((pattern, argument)) = work.pop() {
16199        match pattern {
16200            CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
16201                if let Some(bound) = bindings.get(name) {
16202                    if !cpp_template_terms_equal(bound, argument) {
16203                        return false;
16204                    }
16205                } else {
16206                    bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
16207                }
16208            }
16209            CppTemplateTerm::Atom {
16210                kind: pattern_kind,
16211                text: pattern_text,
16212            } => {
16213                if !matches!(
16214                    argument,
16215                    CppTemplateTerm::Atom { kind, text }
16216                        if kind == pattern_kind && text == pattern_text
16217                ) {
16218                    return false;
16219                }
16220            }
16221            CppTemplateTerm::Node {
16222                kind: pattern_kind,
16223                children: pattern_children,
16224            } => {
16225                let CppTemplateTerm::Node { kind, children } = argument else {
16226                    return false;
16227                };
16228                if kind != pattern_kind || children.len() != pattern_children.len() {
16229                    return false;
16230                }
16231                work.extend(pattern_children.iter().zip(children).rev());
16232            }
16233            CppTemplateTerm::Parameter(_) => return false,
16234        }
16235    }
16236    true
16237}
16238
16239fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
16240    let mut work = vec![(left, right)];
16241    while let Some((left, right)) = work.pop() {
16242        match (left, right) {
16243            (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
16244                if left != right {
16245                    return false;
16246                }
16247            }
16248            (
16249                CppTemplateTerm::Atom {
16250                    kind: left_kind,
16251                    text: left_text,
16252                },
16253                CppTemplateTerm::Atom {
16254                    kind: right_kind,
16255                    text: right_text,
16256                },
16257            ) => {
16258                if left_kind != right_kind || left_text != right_text {
16259                    return false;
16260                }
16261            }
16262            (
16263                CppTemplateTerm::Node {
16264                    kind: left_kind,
16265                    children: left_children,
16266                },
16267                CppTemplateTerm::Node {
16268                    kind: right_kind,
16269                    children: right_children,
16270                },
16271            ) => {
16272                if left_kind != right_kind || left_children.len() != right_children.len() {
16273                    return false;
16274                }
16275                work.extend(left_children.iter().zip(right_children).rev());
16276            }
16277            _ => return false,
16278        }
16279    }
16280    true
16281}
16282
16283pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
16284    let mut components = Vec::new();
16285    let mut stack = vec![node];
16286    while let Some(current) = stack.pop() {
16287        match current.kind() {
16288            "identifier"
16289            | "field_identifier"
16290            | "namespace_identifier"
16291            | "type_identifier"
16292            | "operator_name"
16293            | "destructor_name" => components.push(current),
16294            "template_type" | "template_function" => {
16295                stack.push(current.child_by_field_name("name")?);
16296            }
16297            "dependent_name" => stack.push(current.named_child(0)?),
16298            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
16299                stack.push(current.child_by_field_name("name")?);
16300                if let Some(scope) = current.child_by_field_name("scope") {
16301                    stack.push(scope);
16302                }
16303            }
16304            "nested_namespace_specifier" => {
16305                for index in (0..current.named_child_count()).rev() {
16306                    stack.push(current.named_child(index)?);
16307                }
16308            }
16309            _ => return None,
16310        }
16311    }
16312    Some(components)
16313}
16314
16315pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
16316    node.child_by_field_name("scope").is_none()
16317        && node.child(0).is_some_and(|child| child.kind() == "::")
16318}
16319
16320fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
16321    let mut namespaces = Vec::new();
16322    let mut current = node.parent();
16323    while let Some(parent) = current {
16324        if parent.kind() == "namespace_definition"
16325            && let Some(name) = parent.child_by_field_name("name")
16326        {
16327            let mut components = Vec::new();
16328            append_cpp_name_components(name, source, &mut components)?;
16329            namespaces.push(components);
16330        }
16331        current = parent.parent();
16332    }
16333    namespaces.reverse();
16334    Some(namespaces.into_iter().flatten().collect())
16335}
16336
16337/// Whether a parser-derived namespace path can be reconciled with an indexed
16338/// owner scope without inventing an unrelated short-name binding.
16339///
16340/// Macro namespace sentinels can make tree-sitter omit one or more namespace
16341/// definitions from the ancestor chain. Preserve the order of every namespace
16342/// that did survive parsing, but allow indexed components between them. An
16343/// empty path is accepted only when the declarator itself supplies a nested
16344/// owner suffix such as `Outer::Inner`: together with the indexed enclosing
16345/// owner chain, that suffix is structural evidence that a namespace was lost.
16346/// A one-segment owner at the translation-unit root remains insufficient.
16347fn indexed_namespace_path_is_recoverable(
16348    lexical_scope: &[String],
16349    indexed_owner_scope: &[String],
16350    explicit_owner_component_count: usize,
16351) -> bool {
16352    if lexical_scope.is_empty() {
16353        return explicit_owner_component_count > 1;
16354    }
16355    if lexical_scope.len() >= indexed_owner_scope.len() {
16356        return false;
16357    }
16358    let mut indexed = indexed_owner_scope.iter();
16359    lexical_scope
16360        .iter()
16361        .all(|component| indexed.any(|candidate| candidate == component))
16362}
16363
16364pub fn has_ancestor_kind(node: Node<'_>, kind: &str) -> bool {
16365    let mut current = node.parent();
16366    while let Some(parent) = current {
16367        if parent.kind() == kind {
16368            return true;
16369        }
16370        current = parent.parent();
16371    }
16372    false
16373}
16374
16375/// Whether a declaration type is initialized with a pointer cast.
16376///
16377/// This structured shape has an independent qualified occurrence in addition
16378/// to the cast descriptor below it. Other declarations must keep their normal
16379/// full-range occurrence only.
16380pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
16381    let mut current = Some(node);
16382    while let Some(candidate) = current {
16383        if candidate.kind() == "declaration" {
16384            let Some(type_node) = candidate.child_by_field_name("type") else {
16385                return false;
16386            };
16387            if !(type_node.start_byte() <= node.start_byte()
16388                && node.end_byte() <= type_node.end_byte())
16389            {
16390                return false;
16391            }
16392            let mut cursor = candidate.walk();
16393            return candidate.named_children(&mut cursor).any(|child| {
16394                child.kind() == "init_declarator"
16395                    && child
16396                        .child_by_field_name("value")
16397                        .is_some_and(|value| value.kind() == "cast_expression")
16398            });
16399        }
16400        current = candidate.parent();
16401    }
16402    false
16403}
16404
16405#[derive(Clone, Copy, PartialEq, Eq)]
16406pub(crate) enum QualifiedAliasReferenceKind {
16407    Ordinary,
16408    ConstructorWithExpressionArgument,
16409    ExhaustiveTemplate,
16410}
16411
16412/// Whether a qualified alias reference preserves the requested target.
16413///
16414/// The complete qualified spelling and its terminal identifier are both valid
16415/// occurrences when the visible alias path is structurally proven to name the
16416/// target. Template aliases use their bound arguments; ordinary aliases use
16417/// their structured primary chain.
16418pub(crate) fn qualified_alias_reference_preserves_target(
16419    node: Node<'_>,
16420    target: &CodeUnit,
16421    analyzer: &CppGraphSource<'_>,
16422    visibility: &VisibilityIndex<'_>,
16423    file: &ProjectFile,
16424    source: &str,
16425) -> Option<QualifiedAliasReferenceKind> {
16426    if !matches!(
16427        node.kind(),
16428        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
16429    ) {
16430        return None;
16431    }
16432    let components = cpp_type_name_components(node, source)?;
16433    let name = components.last()?;
16434    analyzer.type_alias_provider().and_then(|provider| {
16435        visibility
16436            .visible_identifier_candidates(file, name)
16437            .find_map(|candidate| {
16438                let proof = provider.is_type_alias(candidate)
16439                    && canonical_cpp_scope_components(candidate) == components
16440                    && visibility.external_type_candidate_visible_in_context(
16441                        analyzer, file, candidate, node,
16442                    )
16443                    && match cpp_template_reference_arguments(node, source) {
16444                        Some(arguments) => visibility.template_alias_arguments_preserve_target(
16445                            analyzer, file, candidate, &arguments, target,
16446                        ),
16447                        None => visibility.structured_alias_primary_preserves_target(
16448                            analyzer, file, candidate, target,
16449                        ),
16450                    };
16451                proof.then(|| {
16452                    if cpp_template_reference_arguments(node, source).is_some()
16453                        && visibility.is_exhaustive_same_fqn_type_declaration_family(
16454                            analyzer, file, candidate,
16455                        )
16456                    {
16457                        QualifiedAliasReferenceKind::ExhaustiveTemplate
16458                    } else if qualified_alias_constructor_has_expression_argument(node)
16459                        || qualified_alias_local_constructor_declaration(node)
16460                    {
16461                        QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
16462                    } else {
16463                        QualifiedAliasReferenceKind::Ordinary
16464                    }
16465                })
16466            })
16467    })
16468}
16469
16470pub(crate) fn qualified_alias_reference_requires_terminal(
16471    reference: Option<QualifiedAliasReferenceKind>,
16472) -> bool {
16473    matches!(
16474        reference,
16475        Some(
16476            QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
16477                | QualifiedAliasReferenceKind::ExhaustiveTemplate
16478        )
16479    )
16480}
16481
16482fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
16483    let Some(declaration) = node.parent().filter(|parent| {
16484        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
16485    }) else {
16486        return false;
16487    };
16488    let mut cursor = declaration.walk();
16489    declaration.named_children(&mut cursor).any(|child| {
16490        child.kind() == "init_declarator"
16491            && child
16492                .child_by_field_name("value")
16493                .filter(|value| value.kind() == "argument_list")
16494                .is_some_and(|arguments| {
16495                    let mut cursor = arguments.walk();
16496                    arguments.named_children(&mut cursor).any(|argument| {
16497                        let is_parameter = matches!(
16498                            argument.kind(),
16499                            "parameter_declaration" | "optional_parameter_declaration"
16500                        );
16501                        if is_parameter {
16502                            argument
16503                                .child_by_field_name("type")
16504                                .is_some_and(|type_node| {
16505                                    type_node.kind() == "type_identifier"
16506                                        && argument.child_by_field_name("declarator").is_none()
16507                                })
16508                        } else {
16509                            !argument.kind().ends_with("_literal")
16510                                && !matches!(argument.kind(), "true" | "false" | "nullptr")
16511                        }
16512                    })
16513                })
16514    })
16515}
16516
16517/// Tree-sitter represents a local C++ direct construction such as
16518/// `Alias value(argument)` as a function declarator. Restrict that recovery to
16519/// declarations inside a compound statement so namespace-scope function
16520/// declarations with the same qualified return type stay full-range only.
16521fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
16522    let Some(declaration) = node.parent().filter(|parent| {
16523        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
16524    }) else {
16525        return false;
16526    };
16527    if declaration
16528        .parent()
16529        .is_none_or(|parent| parent.kind() != "compound_statement")
16530    {
16531        return false;
16532    }
16533    let mut cursor = declaration.walk();
16534    declaration
16535        .named_children(&mut cursor)
16536        .any(|child| child.kind() == "function_declarator")
16537}
16538
16539/// Return the terminal identifier represented by a callable or type callee.
16540///
16541/// Qualified, scoped, template, and field wrappers are traversed through their
16542/// grammar fields so both function calls and type constructions emit the token
16543/// that names the referenced declaration.
16544pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
16545    loop {
16546        let next = match node.kind() {
16547            "qualified_identifier"
16548            | "scoped_identifier"
16549            | "template_method"
16550            | "template_function"
16551            | "template_type" => node.child_by_field_name("name"),
16552            "field_expression" => node.child_by_field_name("field"),
16553            _ => None,
16554        };
16555        let Some(next) = next else {
16556            return node;
16557        };
16558        node = next;
16559    }
16560}
16561
16562#[derive(Clone, Copy)]
16563pub struct RecoveredRelationalTemplateMemberCall<'tree> {
16564    pub receiver: Node<'tree>,
16565    pub member: Node<'tree>,
16566    pub arity: usize,
16567}
16568
16569/// Recover `receiver.member<argument>(call_arguments)` when tree-sitter chose
16570/// nested relational expressions instead of a `template_method` call.
16571///
16572/// The recovery uses only grammar fields: the selected field must be the left
16573/// side of `<`, that expression must be the left side of `>`, and the right
16574/// side of `>` must be the parenthesized call arguments. Semantic callers must
16575/// additionally prove the receiver owner and the member's template status.
16576pub fn recovered_relational_template_member_call(
16577    field: Node<'_>,
16578) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
16579    if field.kind() != "field_expression" {
16580        return None;
16581    }
16582    let receiver = field
16583        .child_by_field_name("argument")
16584        .or_else(|| field.child_by_field_name("object"))?;
16585    let member = field.child_by_field_name("field")?;
16586    let less = field.parent()?;
16587    if less.kind() != "binary_expression"
16588        || less.child_by_field_name("left") != Some(field)
16589        || less
16590            .child_by_field_name("operator")
16591            .is_none_or(|operator| operator.kind() != "<")
16592        || less.child_by_field_name("right").is_none()
16593    {
16594        return None;
16595    }
16596    let greater = less.parent()?;
16597    if greater.kind() != "binary_expression"
16598        || greater.child_by_field_name("left") != Some(less)
16599        || greater
16600            .child_by_field_name("operator")
16601            .is_none_or(|operator| operator.kind() != ">")
16602    {
16603        return None;
16604    }
16605    let arguments = greater.child_by_field_name("right")?;
16606    if arguments.kind() != "parenthesized_expression" {
16607        return None;
16608    }
16609    let arity = parenthesized_call_argument_arity(arguments)?;
16610    Some(RecoveredRelationalTemplateMemberCall {
16611        receiver,
16612        member,
16613        arity,
16614    })
16615}
16616
16617fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
16618    let expression = arguments.named_child(0)?;
16619    if expression.kind() != "comma_expression" {
16620        return Some(1);
16621    }
16622    let mut arity = 0usize;
16623    let mut stack = vec![expression];
16624    while let Some(node) = stack.pop() {
16625        if node.kind() == "comma_expression" {
16626            stack.push(node.child_by_field_name("right")?);
16627            stack.push(node.child_by_field_name("left")?);
16628        } else {
16629            arity += 1;
16630        }
16631    }
16632    Some(arity)
16633}
16634
16635/// Whether `node` is part of a call's callee expression, walking only through
16636/// the grammar wrappers that can structurally contain that callee.
16637pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
16638    while let Some(parent) = node.parent() {
16639        match parent.kind() {
16640            "call_expression" => {
16641                return parent
16642                    .child_by_field_name("function")
16643                    .or_else(|| parent.named_child(0))
16644                    == Some(node);
16645            }
16646            "qualified_identifier"
16647            | "scoped_identifier"
16648            | "template_function"
16649            | "template_type"
16650            | "field_expression" => node = parent,
16651            _ => return false,
16652        }
16653    }
16654    false
16655}
16656
16657pub fn type_reference_hit_node(node: Node<'_>) -> Node<'_> {
16658    if is_call_callee_node(node) {
16659        function_terminal_node(node)
16660    } else {
16661        node
16662    }
16663}
16664
16665pub fn normalize_type_text(value: &str) -> String {
16666    strip_tag_type_prefix(
16667        normalize_cpp_whitespace(value)
16668            .trim_start_matches("const ")
16669            .trim_end_matches('*')
16670            .trim_end_matches('&')
16671            .trim(),
16672    )
16673    .to_string()
16674}
16675
16676fn strip_tag_type_prefix(value: &str) -> &str {
16677    let value = value.trim_start_matches("const ");
16678    value
16679        .strip_prefix("struct ")
16680        .or_else(|| value.strip_prefix("class "))
16681        .or_else(|| value.strip_prefix("enum "))
16682        .unwrap_or(value)
16683        .trim()
16684}
16685
16686pub fn normalize_reference_name(value: &str) -> Option<String> {
16687    let normalized = normalize_cpp_reference_text(value);
16688    (!normalized.is_empty()).then_some(normalized)
16689}
16690
16691pub fn normalize_cpp_reference_text(value: &str) -> String {
16692    let mut text = normalize_cpp_whitespace(value)
16693        .trim_start_matches("new ")
16694        .trim()
16695        .to_string();
16696    if let Some(index) = text.find(['(', '{']) {
16697        text.truncate(index);
16698    }
16699    if let Some(index) = text.find('<') {
16700        text.truncate(index);
16701    }
16702    let normalized = text
16703        .trim()
16704        .trim_start_matches("const ")
16705        .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
16706        .trim_matches(':')
16707        .trim();
16708    strip_tag_type_prefix(normalized).to_string()
16709}
16710
16711pub fn cpp_name_for(unit: &CodeUnit) -> String {
16712    let short = unit.short_name().replace(['.', '$'], "::");
16713    if unit.package_name().is_empty() {
16714        short
16715    } else {
16716        format!("{}::{}", unit.package_name(), short)
16717    }
16718}
16719
16720/// Render an indexed C++ qualified name from its authoritative FqName
16721/// segments. Unlike the legacy `cpp_name_for` renderer, this preserves dots
16722/// that belong to a template argument (for example `Args...`).
16723fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
16724    let fq = unit.fq();
16725    if fq.is_empty() {
16726        return None;
16727    }
16728    let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
16729    Some(
16730        fq.segments()
16731            .iter()
16732            .map(|&segment| interner.resolve(segment).0)
16733            .collect::<Vec<_>>()
16734            .join("::"),
16735    )
16736}
16737
16738fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
16739    canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
16740        || unit.fq().is_empty() && cpp_name_for(unit) == expected
16741}
16742
16743/// Return the indexed C++ owner scope without reparsing its rendered name.
16744///
16745/// Template spellings are opaque within an indexed `FqName` segment.  In
16746/// particular, the ellipsis in a parameter pack (`Args...`) is part of the
16747/// `AtomicHook<...>` type segment; feeding the legacy all-`::` rendering back
16748/// through `parse_symbol_path` would mistake those dots for component
16749/// separators.  Cache-loaded/legacy units may still have an empty structured
16750/// name, so retain the parser only as that explicit fallback.
16751pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
16752    let fq = unit.fq();
16753    if !fq.is_empty() {
16754        let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
16755        let scope = fq
16756            .segments()
16757            .iter()
16758            .filter_map(|&segment| {
16759                let (text, kind) = interner.resolve(segment);
16760                matches!(
16761                    kind,
16762                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
16763                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
16764                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
16765                )
16766                .then(|| text.to_string())
16767            })
16768            .collect();
16769        return scope;
16770    }
16771    brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
16772        brokk_bifrost_core::analyzer::Language::Cpp,
16773        &cpp_name_for(unit),
16774    )
16775}
16776
16777// fqname-M4: the second stage splits on the individual chars '.', '-', '>'
16778// (not the substring "->"), which deliberately reduces an `operator->`-style
16779// terminal segment to an empty tail rather than keeping it intact; the shared
16780// structured splitter's cpp operator-token merge would keep `operator->`
16781// whole instead, changing this function's result — `name_matches_callable`'s
16782// `expected.starts_with("operator")` fallback exists specifically to
16783// compensate for that reduction, and a pinned regression test
16784// (`operator-> must not be reduced with terminal_name-style punctuation
16785// splitting`) asserts today's char-class behavior. Not equivalence-provable;
16786// revisit alongside that pinned test if it is ever relaxed.
16787pub fn terminal_name(value: &str) -> &str {
16788    value
16789        .rsplit("::")
16790        .next()
16791        .unwrap_or(value)
16792        .rsplit(['.', '-', '>'])
16793        .next()
16794        .unwrap_or(value)
16795        .trim()
16796}
16797
16798pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
16799    terminal_name(&normalize_cpp_reference_text(value)) == expected
16800}
16801
16802pub fn name_matches_callable(value: &str, expected: &str) -> bool {
16803    name_matches_terminal(value, expected)
16804        || expected.starts_with("operator")
16805            && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
16806}
16807
16808pub fn name_mentions(value: &str, expected: &str) -> bool {
16809    normalize_cpp_reference_text(value)
16810        .split("::")
16811        .any(|part| part == expected)
16812}
16813
16814pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
16815    let cpp_name = cpp_name_for(unit);
16816    if reference.contains("::") {
16817        return reference == cpp_name;
16818    }
16819    reference == cpp_name
16820        || terminal_name(reference) == unit.identifier()
16821            && (unit.package_name().is_empty() || reference == unit.identifier())
16822}
16823
16824pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
16825    match kind {
16826        TargetKind::Type
16827        | TargetKind::Constructor
16828        | TargetKind::Method
16829        | TargetKind::MemberField => true,
16830        TargetKind::FreeFunction => unit.is_function(),
16831        TargetKind::GlobalField => unit.is_field(),
16832        TargetKind::Macro => unit.is_macro(),
16833    }
16834}
16835
16836fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
16837    let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
16838    let target_name = cpp_name_for(target);
16839    if normalized.contains("::") {
16840        return normalized == target_name;
16841    }
16842    if let Some(namespace) = alias.namespace.as_deref() {
16843        return namespace_prefixes(namespace)
16844            .into_iter()
16845            .any(|prefix| format!("{prefix}::{normalized}") == target_name);
16846    }
16847    target.package_name().is_empty() && normalized == target.identifier()
16848}
16849
16850/// The declared return type text of a C++ function unit, with leading declaration specifiers
16851/// stripped, e.g. `T*` for `T* operator->()`.
16852pub fn cpp_function_return_type_text(
16853    analyzer: &CppGraphSource<'_>,
16854    function: &CodeUnit,
16855) -> Option<String> {
16856    let metadata = analyzer.signature_metadata(function);
16857    let first = metadata.first()?.return_type_text()?;
16858    metadata
16859        .iter()
16860        .all(|metadata| metadata.return_type_text() == Some(first))
16861        .then(|| first.to_owned())
16862}
16863
16864pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
16865    // fqname-M4: `cpp_name_for` is a bespoke all-`::` rendering of the unit's
16866    // name (it replaces every `.`/`$` in `short_name` with `::`), which is NOT
16867    // the same string `default_parent_fq_name`/`fq().parent()` would render:
16868    // the structured `FqName`'s native cpp display deliberately keeps `.` (not
16869    // `::`) between a trailing `Package` segment and a following `Type`
16870    // segment (see `separator` in `fq_name.rs`, landed for issue #1163), so
16871    // popping the unit's own `fq()` segment would NOT reproduce this
16872    // fully-`::`-joined string. Left as a split on the locally-built
16873    // all-colon string rather than the unit's structured name.
16874    cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
16875        namespace
16876            .strip_prefix("anonymous_namespace::")
16877            .unwrap_or(namespace)
16878            .to_string()
16879    })
16880}
16881
16882fn namespace_prefixes(namespace: &str) -> Vec<String> {
16883    // `namespace` is built by `cpp_name_for`/`cpp_namespace_for` with every
16884    // non-`::` separator already converted to `::`, so re-tokenizing it with
16885    // the shared structured splitter and progressively popping the last
16886    // component reproduces the `rsplit_once("::")` outward walk exactly (same
16887    // shape as `cpp_qualifier_lookup_tiers`'s namespace-chain walk).
16888    let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
16889        brokk_bifrost_core::analyzer::Language::Cpp,
16890        namespace,
16891    );
16892    let mut prefixes = Vec::new();
16893    while !parts.is_empty() {
16894        prefixes.push(parts.join("::"));
16895        parts.pop();
16896    }
16897    prefixes
16898}
16899
16900fn nearest_namespace_candidates(
16901    candidates: Vec<CodeUnit>,
16902    normalized: &str,
16903    lexical_namespace: Option<&str>,
16904) -> Vec<CodeUnit> {
16905    if normalized.contains("::") {
16906        return candidates;
16907    }
16908    if let Some(namespace) = lexical_namespace {
16909        for prefix in namespace_prefixes(namespace) {
16910            let scoped = candidates
16911                .iter()
16912                .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
16913                .cloned()
16914                .collect::<Vec<_>>();
16915            if !scoped.is_empty() {
16916                return scoped;
16917            }
16918        }
16919    }
16920    candidates
16921        .into_iter()
16922        .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
16923        .collect()
16924}
16925
16926pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
16927    let mut namespaces = Vec::new();
16928    let mut current = node.parent();
16929    while let Some(parent) = current {
16930        if parent.kind() == "namespace_definition"
16931            && let Some(name) = parent.child_by_field_name("name")
16932        {
16933            let namespace = normalize_cpp_reference_text(node_text(name, source));
16934            if !namespace.is_empty() {
16935                namespaces.push(namespace);
16936            }
16937        }
16938        current = parent.parent();
16939    }
16940    if namespaces.is_empty() {
16941        None
16942    } else {
16943        namespaces.reverse();
16944        Some(namespaces.join("::"))
16945    }
16946}
16947
16948/// Like [`precise_parent_of`], but drops module (namespace) parents. A namespace is a scope, not a
16949/// type or receiver, so namespace-scoped functions and constants resolve as free functions and
16950/// globals rather than members.
16951pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
16952    type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
16953}
16954
16955fn type_owner_resolution(
16956    analyzer: &CppGraphSource<'_>,
16957    code_unit: &CodeUnit,
16958) -> Option<ResolvedTypeOwner> {
16959    precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
16960}
16961
16962fn target_type_owner_resolution(
16963    analyzer: &CppGraphSource<'_>,
16964    code_unit: &CodeUnit,
16965) -> Option<ResolvedTypeOwner> {
16966    match type_owner_resolution(analyzer, code_unit) {
16967        Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
16968        Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
16969    }
16970}
16971
16972/// Recover method identity for an indexed out-of-line definition when the
16973/// ordinary parent edge is absent. Prefer the unique include-visible forward
16974/// declaration, then classify exact-FQN class declarations elsewhere in the
16975/// workspace. A unique complete declaration wins; otherwise multiple forward
16976/// declarations are one owner only when they all share one logical identity.
16977/// The qualified callable FQN proves that owner spelling even when its defining
16978/// header is outside the scan file's include closure, while unknown or competing
16979/// complete declarations remain ambiguous.
16980/// This is deliberately target-only: canonical declaration resolution must
16981/// continue to prefer the callable definition rather than replacing it with
16982/// the recovered owner.
16983fn target_forward_owner_resolution(
16984    analyzer: &CppGraphSource<'_>,
16985    code_unit: &CodeUnit,
16986) -> Option<ResolvedTypeOwner> {
16987    if !code_unit.is_function() {
16988        return None;
16989    }
16990    // A top-level free function has no owner at all, and `FqName::parent`
16991    // answers the empty name rather than `None` for a one-segment identity.
16992    // `default_parent_fq_name`, which this replaced, filtered that case out;
16993    // asking the relational store for the empty name is a batch error that
16994    // fails the whole target frontier.
16995    let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
16996    let cpp = analyzer.cpp?;
16997    let mut visible_files = HashSet::default();
16998    collect_include_closure(
16999        analyzer,
17000        cpp.include_target_index(),
17001        code_unit.source(),
17002        &mut visible_files,
17003        None,
17004    );
17005    let candidates = analyzer.workspace_definitions().exact(&owner_name);
17006    let visible_candidates = candidates
17007        .iter()
17008        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
17009        .cloned()
17010        .collect::<Vec<_>>();
17011    match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
17012        DirectOwnerResolution::UniqueFull(unit) => {
17013            return Some(ResolvedTypeOwner {
17014                unit,
17015                is_forward_declaration: false,
17016            });
17017        }
17018        DirectOwnerResolution::ForwardsOnly(forwards) => {
17019            return (forwards.len() == 1).then(|| ResolvedTypeOwner {
17020                unit: forwards.into_iter().next().unwrap(),
17021                is_forward_declaration: true,
17022            });
17023        }
17024        DirectOwnerResolution::Ambiguous => return None,
17025        DirectOwnerResolution::None => {}
17026    }
17027
17028    let candidates = candidates
17029        .into_iter()
17030        .filter(|candidate| candidate.is_class())
17031        .collect::<Vec<_>>();
17032    let (unit, is_forward_declaration) =
17033        match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
17034            DirectOwnerResolution::UniqueFull(unit) => (unit, false),
17035            DirectOwnerResolution::ForwardsOnly(forwards) => {
17036                (unique_logical_forward_owner(forwards)?, true)
17037            }
17038            DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
17039        };
17040    Some(ResolvedTypeOwner {
17041        unit,
17042        is_forward_declaration,
17043    })
17044}
17045
17046pub fn precise_parent_of(
17047    analyzer: &CppGraphSource<'_>,
17048    visibility: &VisibilityIndex<'_>,
17049    code_unit: &CodeUnit,
17050) -> Option<CodeUnit> {
17051    visibility.cached_precise_parent_of(analyzer, code_unit)
17052}
17053
17054fn precise_parent_resolution(
17055    analyzer: &CppGraphSource<'_>,
17056    code_unit: &CodeUnit,
17057) -> Option<ResolvedTypeOwner> {
17058    #[cfg(any(test, feature = "test-support"))]
17059    if let Some(cpp) = analyzer.cpp {
17060        cpp.record_cpp_parent_resolution_for_test();
17061    }
17062    if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
17063        return Some(ResolvedTypeOwner {
17064            unit,
17065            is_forward_declaration: false,
17066        });
17067    }
17068    let fallback = analyzer.parent_of(code_unit);
17069    if !code_unit.owner_is_type_scope() {
17070        return fallback.map(|unit| ResolvedTypeOwner {
17071            unit,
17072            is_forward_declaration: false,
17073        });
17074    }
17075    let owner_fq = code_unit
17076        .fq()
17077        .parent()
17078        .expect("a unit with an owner identifier has a structured parent");
17079    let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
17080    match same_source_owner(analyzer, code_unit, &owner_candidates) {
17081        DirectOwnerResolution::UniqueFull(owner) => {
17082            return Some(ResolvedTypeOwner {
17083                unit: owner,
17084                is_forward_declaration: false,
17085            });
17086        }
17087        DirectOwnerResolution::Ambiguous => return None,
17088        DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
17089    }
17090    match directly_included_owner(analyzer, code_unit, &owner_candidates) {
17091        DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
17092            unit: owner,
17093            is_forward_declaration: false,
17094        }),
17095        DirectOwnerResolution::Ambiguous => None,
17096        DirectOwnerResolution::ForwardsOnly(forwards) => {
17097            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17098                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17099                    unit: owner,
17100                    is_forward_declaration: false,
17101                }),
17102                FullOwnerResolution::None => {
17103                    unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
17104                        unit,
17105                        is_forward_declaration: true,
17106                    })
17107                }
17108                FullOwnerResolution::Ambiguous => None,
17109            }
17110        }
17111        DirectOwnerResolution::None => {
17112            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17113                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17114                    unit: owner,
17115                    is_forward_declaration: false,
17116                }),
17117                FullOwnerResolution::Ambiguous => None,
17118                FullOwnerResolution::None => fallback
17119                    .filter(|parent| {
17120                        parent.source() == code_unit.source()
17121                            && parent.fq() == &owner_fq
17122                            && (!parent.is_class()
17123                                || cpp_class_declaration_strength(analyzer, parent)
17124                                    == CppClassDeclarationStrength::Full)
17125                    })
17126                    .map(|unit| ResolvedTypeOwner {
17127                        unit,
17128                        is_forward_declaration: false,
17129                    }),
17130            }
17131        }
17132    }
17133}
17134
17135fn exact_structural_type_parent(
17136    analyzer: &CppGraphSource<'_>,
17137    code_unit: &CodeUnit,
17138) -> Option<CodeUnit> {
17139    if !code_unit.is_function() && !code_unit.is_field() {
17140        return None;
17141    }
17142    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
17143    let cpp = analyzer.cpp?;
17144    let parent = cpp.structural_parent_of(code_unit)?;
17145    (!parent.is_module()
17146        && parent.source() == code_unit.source()
17147        && parent.package_name() == code_unit.package_name()
17148        && parent.short_name() == encoded_owner)
17149        .then_some(parent)
17150}
17151
17152fn same_source_owner(
17153    analyzer: &CppGraphSource<'_>,
17154    code_unit: &CodeUnit,
17155    owner_candidates: &[CodeUnit],
17156) -> DirectOwnerResolution {
17157    let candidates = owner_candidates
17158        .iter()
17159        .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
17160        .cloned()
17161        .collect::<Vec<_>>();
17162    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17163    classify_direct_owner_candidates(analyzer, candidates.into_iter())
17164}
17165
17166fn visible_full_cpp_owner(
17167    analyzer: &CppGraphSource<'_>,
17168    code_unit: &CodeUnit,
17169    owner_candidates: &[CodeUnit],
17170) -> FullOwnerResolution {
17171    let Some(cpp) = analyzer.cpp else {
17172        return FullOwnerResolution::None;
17173    };
17174    let mut visible_files = HashSet::default();
17175    collect_include_closure(
17176        analyzer,
17177        cpp.include_target_index(),
17178        code_unit.source(),
17179        &mut visible_files,
17180        None,
17181    );
17182    let candidates = owner_candidates
17183        .iter()
17184        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
17185        .cloned()
17186        .collect::<Vec<_>>();
17187    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17188    let mut full_definition = None;
17189    for candidate in candidates {
17190        match cpp_class_declaration_strength(analyzer, &candidate) {
17191            CppClassDeclarationStrength::Full if full_definition.is_some() => {
17192                return FullOwnerResolution::Ambiguous;
17193            }
17194            CppClassDeclarationStrength::Full => full_definition = Some(candidate),
17195            CppClassDeclarationStrength::Forward => {}
17196            CppClassDeclarationStrength::Unknown => return FullOwnerResolution::Ambiguous,
17197        }
17198    }
17199    full_definition.map_or(FullOwnerResolution::None, FullOwnerResolution::Unique)
17200}
17201
17202pub enum DirectOwnerResolution {
17203    None,
17204    ForwardsOnly(Vec<CodeUnit>),
17205    UniqueFull(CodeUnit),
17206    Ambiguous,
17207}
17208
17209enum FullOwnerResolution {
17210    None,
17211    Unique(CodeUnit),
17212    Ambiguous,
17213}
17214
17215fn directly_included_owner(
17216    analyzer: &CppGraphSource<'_>,
17217    code_unit: &CodeUnit,
17218    owner_candidates: &[CodeUnit],
17219) -> DirectOwnerResolution {
17220    let Some(cpp) = analyzer.cpp else {
17221        return DirectOwnerResolution::None;
17222    };
17223    let direct_includes: HashSet<ProjectFile> = analyzer
17224        .canonical_include_paths(code_unit.source())
17225        .unwrap_or_default()
17226        .into_iter()
17227        .flat_map(|include| {
17228            resolve_include_targets_with_index(
17229                code_unit.source(),
17230                &include,
17231                cpp.include_target_index(),
17232            )
17233        })
17234        .collect();
17235    let candidates = owner_candidates
17236        .iter()
17237        .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
17238        .cloned()
17239        .collect::<Vec<_>>();
17240    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17241    classify_direct_owner_candidates(analyzer, candidates.into_iter())
17242}
17243
17244fn prefer_member_declaring_owners(
17245    analyzer: &CppGraphSource<'_>,
17246    member: &CodeUnit,
17247    candidates: Vec<CodeUnit>,
17248) -> Vec<CodeUnit> {
17249    let matching = candidates
17250        .iter()
17251        .filter(|owner| owner_declares_member(analyzer, owner, member))
17252        .cloned()
17253        .collect::<Vec<_>>();
17254    if matching.is_empty() {
17255        candidates
17256    } else {
17257        matching
17258    }
17259}
17260
17261fn owner_declares_member(
17262    analyzer: &CppGraphSource<'_>,
17263    owner: &CodeUnit,
17264    member: &CodeUnit,
17265) -> bool {
17266    analyzer.direct_children(owner).into_iter().any(|child| {
17267        child.kind() == member.kind()
17268            && child.identifier() == member.identifier()
17269            && child.signature() == member.signature()
17270    })
17271}
17272
17273fn classify_direct_owner_candidates(
17274    analyzer: &CppGraphSource<'_>,
17275    candidates: impl Iterator<Item = CodeUnit>,
17276) -> DirectOwnerResolution {
17277    collapse_owner_candidates(candidates.map(|candidate| {
17278        let strength = cpp_class_declaration_strength(analyzer, &candidate);
17279        (candidate, strength)
17280    }))
17281}
17282
17283pub fn collapse_owner_candidates(
17284    candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
17285) -> DirectOwnerResolution {
17286    let mut full_definition = None;
17287    let mut forwards = Vec::new();
17288    for (candidate, strength) in candidates {
17289        match strength {
17290            CppClassDeclarationStrength::Full if full_definition.is_some() => {
17291                return DirectOwnerResolution::Ambiguous;
17292            }
17293            CppClassDeclarationStrength::Full => full_definition = Some(candidate),
17294            CppClassDeclarationStrength::Forward => forwards.push(candidate),
17295            CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
17296        }
17297    }
17298    if let Some(owner) = full_definition {
17299        DirectOwnerResolution::UniqueFull(owner)
17300    } else if !forwards.is_empty() {
17301        DirectOwnerResolution::ForwardsOnly(forwards)
17302    } else {
17303        DirectOwnerResolution::None
17304    }
17305}
17306
17307#[cfg(any(test, feature = "test-support"))]
17308pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
17309    unique_logical_forward_owner(forwards)
17310}
17311
17312fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
17313    let first = forwards.pop()?;
17314    forwards
17315        .iter()
17316        .all(|forward| same_logical_symbol(forward, &first))
17317        .then_some(first)
17318}
17319
17320pub fn cpp_class_declaration_strength(
17321    analyzer: &CppGraphSource<'_>,
17322    candidate: &CodeUnit,
17323) -> CppClassDeclarationStrength {
17324    // The answer is a pure function of the unit's ranges and its file's tree,
17325    // and the inverse scan asks it once per declaration seed. On a translation
17326    // unit the parser could not fully recover, each ask re-derives the
17327    // export-macro recovery shapes from the file's `ERROR` subtrees, so without
17328    // this memo one file's scan is quadratic in its own size: 97% of Catch2's
17329    // 284 s inverse scan of `extras/catch_amalgamated.cpp` was in this call
17330    // (#1496).
17331    let Some(cpp) = analyzer.cpp else {
17332        return uncached_cpp_class_declaration_strength(analyzer, candidate);
17333    };
17334    if let Some(strength) = cpp.cached_class_declaration_strength(candidate) {
17335        return strength;
17336    }
17337    let strength = uncached_cpp_class_declaration_strength(analyzer, candidate);
17338    cpp.cache_class_declaration_strength(candidate, strength);
17339    strength
17340}
17341
17342fn uncached_cpp_class_declaration_strength(
17343    analyzer: &CppGraphSource<'_>,
17344    candidate: &CodeUnit,
17345) -> CppClassDeclarationStrength {
17346    if let Some(facts) = analyzer.declaration_source_properties(candidate) {
17347        if facts
17348            .iter()
17349            .any(|fact| fact.class_strength == CppClassDeclarationStrength::Full)
17350        {
17351            return CppClassDeclarationStrength::Full;
17352        }
17353        if facts
17354            .iter()
17355            .any(|fact| fact.class_strength == CppClassDeclarationStrength::Forward)
17356        {
17357            return CppClassDeclarationStrength::Forward;
17358        }
17359    }
17360    if let Some(cpp) = analyzer.cpp
17361        && let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source())
17362    {
17363        return cpp_class_declaration_strength_in_tree(
17364            analyzer,
17365            &cpp.recovered_export_class_index(analyzer.token, candidate.source()),
17366            candidate,
17367            prepared.source(),
17368            prepared.tree().root_node(),
17369        );
17370    }
17371    let Some(source) = analyzer.indexed_source(candidate.source()) else {
17372        return CppClassDeclarationStrength::Unknown;
17373    };
17374    let mut parser = Parser::new();
17375    if parser
17376        .set_language(&tree_sitter_cpp::LANGUAGE.into())
17377        .is_err()
17378    {
17379        return CppClassDeclarationStrength::Unknown;
17380    }
17381    let Some(tree) = parser.parse(&source, None) else {
17382        return CppClassDeclarationStrength::Unknown;
17383    };
17384    // This branch reparses a file the analyzer has no prepared tree for, so its
17385    // recovery index is that one tree's and cannot be shared.
17386    let recovered_export_classes =
17387        CppRecoveredExportClassIndex::build(tree.root_node(), source.as_str());
17388    cpp_class_declaration_strength_in_tree(
17389        analyzer,
17390        &recovered_export_classes,
17391        candidate,
17392        &source,
17393        tree.root_node(),
17394    )
17395}
17396
17397fn cpp_class_declaration_strength_in_tree(
17398    analyzer: &CppGraphSource<'_>,
17399    recovered_export_classes: &CppRecoveredExportClassIndex,
17400    candidate: &CodeUnit,
17401    source: &str,
17402    root: Node<'_>,
17403) -> CppClassDeclarationStrength {
17404    let ranges = analyzer.ranges(candidate);
17405    let mut saw_forward = false;
17406    for range in ranges {
17407        // The recovered export-macro shapes answer for their own ranges; only a
17408        // range no recovery claims is read as a plain specifier.
17409        match recovered_class_body_at(
17410            recovered_export_classes,
17411            root,
17412            source,
17413            candidate.identifier(),
17414            &range,
17415        ) {
17416            Some(true) => return CppClassDeclarationStrength::Full,
17417            Some(false) => {
17418                saw_forward = true;
17419                continue;
17420            }
17421            None => {}
17422        }
17423        // Only a node covering the range's start byte can be the specifier for
17424        // this range, so apply that test where nodes enter the stack rather
17425        // than where they leave it. Pushing first meant one ask enqueued every
17426        // sibling at every level it descended, which on a translation unit with
17427        // thousands of top-level declarations is a per-ask cost proportional to
17428        // the file (#1496).
17429        let covers_range_start = |node: &Node<'_>| {
17430            node.start_byte() <= range.start_byte && node.end_byte() >= range.start_byte
17431        };
17432        let mut stack = Vec::new();
17433        if covers_range_start(&root) {
17434            stack.push(root);
17435        }
17436        while let Some(node) = stack.pop() {
17437            if node.start_byte() == range.start_byte
17438                && node.end_byte() == range.end_byte
17439                && matches!(
17440                    node.kind(),
17441                    "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
17442                )
17443            {
17444                if cpp_class_node_has_body(node) {
17445                    return CppClassDeclarationStrength::Full;
17446                }
17447                saw_forward = true;
17448            }
17449            let mut cursor = node.walk();
17450            stack.extend(node.named_children(&mut cursor).filter(covers_range_start));
17451        }
17452    }
17453    if saw_forward {
17454        CppClassDeclarationStrength::Forward
17455    } else {
17456        CppClassDeclarationStrength::Unknown
17457    }
17458}
17459
17460fn cpp_class_node_has_body(node: Node<'_>) -> bool {
17461    node.child_by_field_name("body").is_some() || {
17462        let mut cursor = node.walk();
17463        node.named_children(&mut cursor).any(|child| {
17464            matches!(
17465                child.kind(),
17466                "declaration_list" | "field_declaration_list" | "enumerator_list"
17467            )
17468        })
17469    }
17470}
17471
17472#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17473enum CppCTagKind {
17474    Struct,
17475    Union,
17476}
17477
17478fn indexed_c_tag_kind(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CppCTagKind> {
17479    let declaration = analyzer.get_source(code_unit, false)?;
17480    let mut parser = Parser::new();
17481    parser
17482        .set_language(&tree_sitter_cpp::LANGUAGE.into())
17483        .ok()?;
17484    let tree = parser.parse(&declaration, None)?;
17485    let mut stack = vec![tree.root_node()];
17486    while let Some(node) = stack.pop() {
17487        let kind = match node.kind() {
17488            "struct_specifier" => CppCTagKind::Struct,
17489            "union_specifier" => CppCTagKind::Union,
17490            _ => {
17491                let mut cursor = node.walk();
17492                stack.extend(node.named_children(&mut cursor));
17493                continue;
17494            }
17495        };
17496        if node
17497            .child_by_field_name("name")
17498            .is_some_and(|name| node_text(name, &declaration) == code_unit.identifier())
17499        {
17500            return Some(kind);
17501        }
17502        let mut cursor = node.walk();
17503        stack.extend(node.named_children(&mut cursor));
17504    }
17505    None
17506}
17507
17508pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
17509    if !code_unit.owner_is_type_scope() {
17510        return None;
17511    }
17512    let owner_fq = code_unit.fq().parent()?;
17513    ctx.analyzer
17514        .workspace_definitions()
17515        .exact(&owner_fq)
17516        .into_iter()
17517        .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
17518}
17519
17520pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
17521    left.kind() == right.kind()
17522        && left.fq_name() == right.fq_name()
17523        && left.signature() == right.signature()
17524        && left.source() == right.source()
17525}
17526
17527pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
17528    same_symbol(left, right) || same_logical_symbol(left, right)
17529}
17530
17531pub fn same_visible_global_field_symbol(
17532    analyzer: &CppGraphSource<'_>,
17533    internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
17534    left: &CodeUnit,
17535    right: &CodeUnit,
17536) -> bool {
17537    if same_symbol(left, right) {
17538        return true;
17539    }
17540    if !same_logical_symbol(left, right) {
17541        return false;
17542    }
17543    if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
17544        || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
17545    {
17546        left.source() == right.source()
17547    } else {
17548        true
17549    }
17550}
17551
17552fn cpp_global_field_has_internal_linkage_cached(
17553    analyzer: &CppGraphSource<'_>,
17554    cache: &mut HashMap<CodeUnit, bool>,
17555    candidate: &CodeUnit,
17556) -> bool {
17557    if let Some(internal) = cache.get(candidate) {
17558        return *internal;
17559    }
17560    #[cfg(any(test, feature = "test-support"))]
17561    note_cpp_global_field_internal_linkage_classification_for_test();
17562    let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
17563    cache.insert(candidate.clone(), internal);
17564    internal
17565}
17566
17567#[cfg(any(test, feature = "test-support"))]
17568thread_local! {
17569    static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
17570}
17571
17572#[cfg(any(test, feature = "test-support"))]
17573fn note_cpp_global_field_internal_linkage_classification_for_test() {
17574    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
17575        count.set(count.get() + 1);
17576    });
17577}
17578
17579#[cfg(any(test, feature = "test-support"))]
17580pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
17581    body: impl FnOnce() -> T,
17582) -> (T, usize) {
17583    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
17584        count.set(0);
17585        let result = body();
17586        let observed = count.get();
17587        count.set(0);
17588        (result, observed)
17589    })
17590}
17591
17592pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
17593    left.kind() == right.kind()
17594        && left.fq_name() == right.fq_name()
17595        && left.signature() == right.signature()
17596}
17597
17598pub fn cpp_global_field_has_internal_linkage(
17599    analyzer: &CppGraphSource<'_>,
17600    candidate: &CodeUnit,
17601) -> bool {
17602    if !candidate.is_field() || candidate.short_name().contains('.') {
17603        return false;
17604    }
17605    let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
17606        return false;
17607    };
17608    match local_linkage {
17609        CppFieldLinkage::Internal => true,
17610        CppFieldLinkage::External => false,
17611        CppFieldLinkage::InternalUnlessExternalPeer => {
17612            !cpp_global_field_linkage_peers(analyzer, candidate)
17613                .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
17614                .any(|linkage| matches!(linkage, CppFieldLinkage::External))
17615        }
17616    }
17617}
17618
17619fn cpp_global_field_linkage_peers<'a>(
17620    analyzer: &CppGraphSource<'a>,
17621    candidate: &'a CodeUnit,
17622) -> impl Iterator<Item = CodeUnit> + 'a {
17623    let name = candidate.fq().clone();
17624    analyzer
17625        .workspace_definitions()
17626        .exact(&name)
17627        .into_iter()
17628        .filter(move |peer| {
17629            if peer == candidate {
17630                return false;
17631            }
17632            #[cfg(any(test, feature = "test-support"))]
17633            note_cpp_global_field_linkage_peer_inspection_for_test();
17634            same_logical_symbol(peer, candidate)
17635        })
17636}
17637
17638#[cfg(any(test, feature = "test-support"))]
17639thread_local! {
17640    static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
17641}
17642
17643#[cfg(any(test, feature = "test-support"))]
17644fn note_cpp_global_field_linkage_peer_inspection_for_test() {
17645    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
17646        count.set(count.get() + 1);
17647    });
17648}
17649
17650#[cfg(any(test, feature = "test-support"))]
17651pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
17652    body: impl FnOnce() -> T,
17653) -> (T, usize) {
17654    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
17655        count.set(0);
17656        let result = body();
17657        let observed = count.get();
17658        count.set(0);
17659        (result, observed)
17660    })
17661}
17662
17663fn cpp_global_field_declaration_linkage(
17664    analyzer: &CppGraphSource<'_>,
17665    candidate: &CodeUnit,
17666) -> Option<CppFieldLinkage> {
17667    analyzer.cpp_field_linkage(candidate)
17668}
17669
17670/// Agreed alias target spelling captured from the declaring AST type field.
17671pub fn cpp_canonical_alias_target_text(
17672    analyzer: &CppGraphSource<'_>,
17673    unit: &CodeUnit,
17674) -> Option<String> {
17675    let facts = analyzer.declaration_source_properties(unit)?;
17676    let first = facts.first()?.alias_target_text.as_ref()?;
17677    facts
17678        .iter()
17679        .all(|fact| fact.alias_target_text.as_ref() == Some(first))
17680        .then(|| first.clone())
17681}
17682
17683#[cfg(test)]
17684mod tests {
17685    #[test]
17686    fn issue_3089_statement_formal_at_end_of_replacement() {
17687        let parameters = vec!["handle".to_owned(), "block".to_owned()];
17688        for replacement in [
17689            "do { header_event_t* event; if ((handle)->active) block } while (0)",
17690            "do { header_event_t* event; block } while (0)",
17691        ] {
17692            assert!(
17693                super::VisibilityIndex::parse_macro_replacement_body(replacement, &parameters)
17694                    .is_some(),
17695                "{replacement}"
17696            );
17697        }
17698    }
17699    use super::*;
17700
17701    #[test]
17702    fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
17703        let source = "int size(void) { return sizeof(((Payload))); }\n";
17704        let mut parser = Parser::new();
17705        parser
17706            .set_language(&tree_sitter_cpp::LANGUAGE.into())
17707            .expect("C++ grammar");
17708        let tree = parser.parse(source, None).expect("fixture tree");
17709        let start = source.find("Payload").expect("sizeof operand");
17710        let node = tree
17711            .root_node()
17712            .named_descendant_for_byte_range(start, start + "Payload".len())
17713            .expect("focused operand");
17714        let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
17715        let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
17716
17717        assert_eq!(node.kind(), "identifier");
17718        assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
17719        assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
17720    }
17721
17722    fn parse_cpp(source: &str) -> Tree {
17723        let mut parser = Parser::new();
17724        parser
17725            .set_language(&tree_sitter_cpp::LANGUAGE.into())
17726            .expect("C++ grammar");
17727        parser.parse(source, None).expect("fixture tree")
17728    }
17729
17730    fn named_node_at<'tree>(tree: &'tree Tree, source: &str, needle: &str) -> Node<'tree> {
17731        let start = source.find(needle).expect("fixture needle");
17732        tree.root_node()
17733            .named_descendant_for_byte_range(start, start + needle.len())
17734            .expect("node at needle")
17735    }
17736
17737    fn prepared_cpp(source: &str) -> PreparedSyntaxTree {
17738        let mut parser = Parser::new();
17739        parser
17740            .set_language(&tree_sitter_cpp::LANGUAGE.into())
17741            .expect("C++ grammar");
17742        let tree = parser.parse(source, None).expect("fixture tree");
17743        PreparedSyntaxTree::new(
17744            PreparedSyntaxSource::Exact(Arc::from(source)),
17745            tree,
17746            compute_line_starts(source),
17747            LanguageDialect::Standard(Language::Cpp),
17748            PreparedSourceOrigin::Disk,
17749            None,
17750        )
17751    }
17752
17753    fn unresolved_include_before(source: &str, reference: &str) -> bool {
17754        let file = ProjectFile::new(std::env::temp_dir(), "issue-3078.cpp");
17755        let prepared = prepared_cpp(source);
17756        let facts = collect_structured_include_facts(&prepared);
17757        let include_targets = IncludeTargetIndex::build([&file]);
17758        has_unresolved_include_visible_before_in_prepared(
17759            &file,
17760            &prepared,
17761            &include_targets,
17762            &facts,
17763            source.find(reference).expect("reference fixture"),
17764        )
17765    }
17766
17767    #[test]
17768    fn unresolved_include_before_reference_is_visible() {
17769        let source = "#include \"missing.h\"\nint use = Missing;\n";
17770        assert!(unresolved_include_before(source, "Missing"));
17771    }
17772
17773    #[test]
17774    fn unresolved_include_after_reference_is_not_visible() {
17775        let source = "int use = Missing;\n#include \"missing.h\"\n";
17776        assert!(!unresolved_include_before(source, "Missing"));
17777    }
17778
17779    #[test]
17780    fn unresolved_include_in_incompatible_sibling_branch_is_not_visible() {
17781        let source = "#if FEATURE\n#include \"missing.h\"\n#else\nint use = Missing;\n#endif\n";
17782        assert!(!unresolved_include_before(source, "Missing"));
17783    }
17784
17785    #[test]
17786    fn unresolved_include_in_current_branch_is_visible() {
17787        let source =
17788            "#if FEATURE\n#include \"missing.h\"\nint use = Missing;\n#else\nint other;\n#endif\n";
17789        assert!(unresolved_include_before(source, "Missing"));
17790    }
17791
17792    /// Two macro-decorated class heads make tree-sitter close `detail` at the
17793    /// first class's `}`, `matchers` at the second's, and `app` at `detail`'s
17794    /// real `}`; the tail parses at translation-unit level and the two real
17795    /// closes for `matchers` and `app` land in a trailing ERROR (#1537).
17796    const STOLEN_BRACE_CASCADE: &str = r#"namespace app {
17797namespace matchers {
17798    namespace detail {
17799        class API [[nodiscard]] First {
17800        public:
17801            int value() const { return count_ + 1; }
17802        private:
17803            int count_;
17804        };
17805        class API [[nodiscard]] Second {
17806        public:
17807            int value() const { return count_ + 2; }
17808        private:
17809            int count_;
17810        };
17811    } // namespace detail
17812
17813    template <typename T>
17814    void tail_function(MatcherBase<T> const& value);
17815
17816    class TailClass {};
17817} // namespace matchers
17818} // namespace app
17819
17820struct AfterAll {};
17821"#;
17822
17823    #[test]
17824    fn orphaned_namespace_scope_index_restores_a_stolen_brace_cascade() {
17825        let source = STOLEN_BRACE_CASCADE;
17826        let tree = parse_cpp(source);
17827        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
17828
17829        let tail_class = named_node_at(&tree, source, "TailClass");
17830        assert!(
17831            !has_ancestor_kind(tail_class, "namespace_definition"),
17832            "the fixture must reproduce the recovery: the tail has no namespace ancestor"
17833        );
17834        let displaced = named_node_at(&tree, source, "Second");
17835        assert_eq!(
17836            enclosing_namespace_components(displaced, source),
17837            Some(vec!["app".to_string(), "matchers".to_string()]),
17838            "the fixture must displace the second class out of detail"
17839        );
17840
17841        let components = |needle: &str| {
17842            index.enclosing_namespace_components(named_node_at(&tree, source, needle), source)
17843        };
17844        assert_eq!(components("First"), ["app", "matchers", "detail"]);
17845        assert_eq!(components("Second"), ["app", "matchers", "detail"]);
17846        assert_eq!(components("MatcherBase<T>"), ["app", "matchers"]);
17847        assert_eq!(components("tail_function"), ["app", "matchers"]);
17848        assert_eq!(components("TailClass"), ["app", "matchers"]);
17849        assert!(components("AfterAll").is_empty());
17850    }
17851
17852    #[test]
17853    fn orphaned_namespace_scope_index_is_empty_without_lost_scopes() {
17854        let clean = "namespace a { namespace b { class C {}; } class D {}; }\n";
17855        let tree = parse_cpp(clean);
17856        assert!(!tree.root_node().has_error());
17857        assert!(OrphanedNamespaceScopeIndex::build(tree.root_node(), clean).is_empty());
17858
17859        // A namespace that merely contains a parse error closes where its
17860        // brace says; the declarations after it keep their parsed scope.
17861        let damaged = "namespace a { namespace b { UNKNOWN_MACRO(x) } class C {}; }\n";
17862        let tree = parse_cpp(damaged);
17863        assert!(tree.root_node().has_error());
17864        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), damaged);
17865        assert_eq!(
17866            index.enclosing_namespace_components(named_node_at(&tree, damaged, "class C"), damaged),
17867            ["a"]
17868        );
17869    }
17870
17871    /// A function-like `#define` in a class body, whose replacement returns a
17872    /// SFINAE type, makes tree-sitter give up on the whole enclosing namespace:
17873    /// `namespace app {` becomes an `ERROR` whose own `namespace`, name and `{`
17874    /// tokens are the only trace of the head, and every declaration the
17875    /// namespace holds becomes their flat sibling. This is Catch2's
17876    /// `catch_decomposer.hpp`, reduced (issue #3084).
17877    const COLLAPSED_NAMESPACE_HEAD: &str = r#"namespace app {
17878
17879    class Target {
17880        int value_;
17881    };
17882
17883    template <typename T>
17884    class Holder {
17885    public:
17886        explicit constexpr Holder( T lhs ): m_lhs( lhs ) {}
17887
17888#define HOLDER_DEFINE_OP( id, op )                                             \
17889    template <typename U>                                                      \
17890    constexpr friend auto operator op( Holder&& lhs, U&& rhs )                 \
17891        -> std::enable_if_t<is_##id##_comparable<T, U>::value, Target> {       \
17892        return Target{};                                                       \
17893    }
17894
17895        HOLDER_DEFINE_OP( equal, == )
17896#undef HOLDER_DEFINE_OP
17897        T m_lhs;
17898    };
17899
17900    class Tail {};
17901}
17902"#;
17903
17904    #[test]
17905    fn orphaned_namespace_scope_index_names_a_collapsed_namespace_head() {
17906        let source = COLLAPSED_NAMESPACE_HEAD;
17907        let tree = parse_cpp(source);
17908        let target = named_node_at(&tree, source, "class Target");
17909
17910        assert!(
17911            !has_ancestor_kind(target, "namespace_definition"),
17912            "the fixture must reproduce the collapse: the class has no namespace ancestor"
17913        );
17914        let head = target.parent().expect("the collapsed namespace envelope");
17915        assert_eq!(
17916            head.kind(),
17917            "ERROR",
17918            "the fixture must keep the namespace head in an ERROR node"
17919        );
17920
17921        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
17922        assert_eq!(
17923            index.enclosing_namespace_components(target, source),
17924            ["app"]
17925        );
17926        assert_eq!(
17927            index.enclosing_namespace_levels(target, source),
17928            vec![(
17929                "app".to_string(),
17930                Range {
17931                    start_byte: 0,
17932                    end_byte: source.len() - 1,
17933                    start_line: 1,
17934                    end_line: 25,
17935                },
17936            )],
17937            "the recovered namespace owns its full construct, not its first member or just its header"
17938        );
17939    }
17940
17941    /// A macro that opens a block: `APP_IF_ACTIVE` expands to `if (active) {`,
17942    /// so the `{` of a scope the source really opens is part of the macro's
17943    /// `preproc_arg` and never becomes a `{` token. The tree therefore holds
17944    /// one more real `}` than real `{`, and a file-global stack has to assign
17945    /// those closes to the wrong opens: the `}` on line 9 takes `check`'s body,
17946    /// `check`'s own `}` on line 11 takes `detail`, line 15 takes `app`, and
17947    /// line 16 arrives with nothing open. This is the state simdjson's
17948    /// `compile_time_json-inl.h` reaches eight times over (#3328).
17949    const MACRO_OPENED_SCOPE: &str = r#"#define APP_IF_ACTIVE if (active) {
17950
17951namespace app {
17952namespace detail {
17953
17954bool check(bool active) {
17955  APP_IF_ACTIVE
17956    return true;
17957  }
17958  return false;
17959}
17960
17961class Tail {};
17962
17963} // namespace detail
17964} // namespace app
17965"#;
17966
17967    /// simdjson's `include/simdjson/compile_time_json-inl.h` at `9b33047a`,
17968    /// reduced. The file's C++26 static reflection leaves the whole translation
17969    /// unit an `ERROR`, and inside it tree-sitter re-lexes `case '<punct>': {`:
17970    /// `case` becomes a type, the char literal's own punctuation opens a
17971    /// `compound_literal_expression`, and the literal's closing quote starts a
17972    /// character run that never terminates and consumes the statement's real
17973    /// `{`. The reduction keeps that mis-lex, so the tree loses a
17974    /// scope-opening token here as well and the three namespace opens end up
17975    /// paired with the closes one level in (#3328).
17976    const REFLECTION_CASE_LABEL_RECOVERY: &str = r#"namespace simdjson {
17977namespace compile_time {
17978namespace number_parsing {
17979consteval int leading_zeroes(uint64_t input_num, int last_bit = 0) {
17980    auto hex_to_u32 = [&] [[nodiscard]] () -> uint32_t {
17981      auto digit = [](uint8_t c) -> uint32_t {
17982      }
17983    }
17984    if (a) {
17985      while (a) {
17986      }
17987    }
17988    switch (c) {
17989    case '{': {
17990    }
17991    }
17992  }
17993  while (a) {
17994    cursor += field.second;
17995    if (a) {
17996    case '{': {
17997      if (a) {
17998      }
17999    }
18000    case 'n': {
18001      if (a) {
18002      }
18003    }
18004    }
18005  }
18006  if (a) {
18007    simdjson_consteval_error("Expected '}'");
18008  }
18009}
18010}
18011}
18012"#;
18013
18014    /// Real `{` and `}` tokens of a tree, the stream the index pairs.
18015    fn real_brace_token_counts(root: Node<'_>) -> (usize, usize) {
18016        let mut stack = vec![root];
18017        let mut counts = (0, 0);
18018        while let Some(node) = stack.pop() {
18019            match (node.kind(), node.is_missing()) {
18020                ("{", false) => counts.0 += 1,
18021                ("}", false) => counts.1 += 1,
18022                _ => {}
18023            }
18024            let mut cursor = node.walk();
18025            stack.extend(node.children(&mut cursor));
18026        }
18027        counts
18028    }
18029
18030    /// The open brace of `head`, which is the key its close lands under.
18031    fn namespace_open_byte(source: &str, head: &str) -> usize {
18032        let at = source.find(head).expect("fixture namespace head");
18033        at + head.len() - 1
18034    }
18035
18036    #[test]
18037    fn orphaned_namespace_scope_index_pairs_nothing_when_a_scope_open_is_lost() {
18038        let source = MACRO_OPENED_SCOPE;
18039        let tree = parse_cpp(source);
18040        let root = tree.root_node();
18041        assert_eq!(
18042            real_brace_token_counts(root),
18043            (4, 5),
18044            "the fixture must reproduce the loss: one scope the source opens \
18045             has no `{{` token, so the stream holds an extra real close"
18046        );
18047
18048        let index = OrphanedNamespaceScopeIndex::build(root, source);
18049        for head in ["namespace app {", "namespace detail {"] {
18050            assert_eq!(
18051                index.matching_close_brace(namespace_open_byte(source, head)),
18052                None,
18053                "`{head}` must not claim a close: the stack had to give it one that \
18054                 belongs to an inner scope (line 15 for `app`, line 11 for \
18055                 `detail`) once the macro's `{{` went missing"
18056            );
18057        }
18058    }
18059
18060    #[test]
18061    fn orphaned_namespace_scope_index_leaves_a_declaration_after_every_close_at_file_scope() {
18062        // The stack proves no pairing in this file, so every restored level
18063        // keeps its best-effort scope. None of it may reach a declaration the
18064        // file writes after its last close.
18065        let source = format!("{MACRO_OPENED_SCOPE}struct AfterAll {{}};\n");
18066        let tree = parse_cpp(&source);
18067        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), &source);
18068        let after_all = named_node_at(&tree, &source, "struct AfterAll");
18069        assert!(
18070            index
18071                .enclosing_namespace_components(after_all, &source)
18072                .is_empty()
18073        );
18074    }
18075
18076    #[test]
18077    fn orphaned_namespace_scope_index_pairs_nothing_on_the_reflection_recovery() {
18078        let source = REFLECTION_CASE_LABEL_RECOVERY;
18079        let tree = parse_cpp(source);
18080        let root = tree.root_node();
18081        let mut stack = vec![root];
18082        let mut mis_lexed_opens = Vec::new();
18083        while let Some(node) = stack.pop() {
18084            if node.kind() == "character"
18085                && &source[node.start_byte()..node.end_byte()] == "{"
18086                && node
18087                    .parent()
18088                    .is_some_and(|parent| parent.kind() != "char_literal")
18089            {
18090                mis_lexed_opens.push(node.start_position().row + 1);
18091            }
18092            let mut cursor = node.walk();
18093            stack.extend(node.children(&mut cursor));
18094        }
18095        assert!(
18096            !mis_lexed_opens.is_empty(),
18097            "the fixture must reproduce the mis-lex: a scope-opening `{{` \
18098             consumed as a `character` of a char literal recovery never closed"
18099        );
18100        assert_eq!(
18101            real_brace_token_counts(root),
18102            (17, 18),
18103            "which is what leaves the brace token stream short one open: \
18104             mis-lexed at {mis_lexed_opens:?}"
18105        );
18106
18107        let index = OrphanedNamespaceScopeIndex::build(root, source);
18108        for head in [
18109            "namespace simdjson {",
18110            "namespace compile_time {",
18111            "namespace number_parsing {",
18112        ] {
18113            assert_eq!(
18114                index.matching_close_brace(namespace_open_byte(source, head)),
18115                None,
18116                "`{head}` must not claim a close: every close after the lost open \
18117                 belongs one level in, so the stack paired the three heads on \
18118                 lines 1..3 with the closes on lines 33..35 instead of 34..36"
18119            );
18120        }
18121    }
18122
18123    #[test]
18124    fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
18125        let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
18126        assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
18127        assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
18128        assert!(indexed_namespace_path_is_recoverable(
18129            &["cache".to_string()],
18130            &indexed,
18131            1,
18132        ));
18133    }
18134
18135    #[test]
18136    fn sort_lookup_units_totally_orders_every_identity_field() {
18137        let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
18138        let base = CodeUnit::with_signature(
18139            file.clone(),
18140            CodeUnitType::Function,
18141            "scope",
18142            "value",
18143            Some("()".to_string()),
18144            false,
18145        );
18146        let different_kind = CodeUnit::with_signature(
18147            file.clone(),
18148            CodeUnitType::Field,
18149            "scope",
18150            "value",
18151            Some("()".to_string()),
18152            false,
18153        );
18154        let synthetic = base.with_synthetic(true);
18155
18156        let interner = segment_interner();
18157        let mut member_fq = FqName::new();
18158        member_fq.push(interner.intern("scope", SegmentKind::Package));
18159        member_fq.push(interner.intern("value", SegmentKind::Member));
18160        let different_package_boundary = CodeUnit::from_fq(
18161            file.clone(),
18162            CodeUnitType::Function,
18163            member_fq,
18164            0,
18165            Some("()".to_string()),
18166            false,
18167        );
18168
18169        let mut unknown_fq = FqName::new();
18170        unknown_fq.push(interner.intern("scope", SegmentKind::Package));
18171        unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
18172        let different_segment_kind = CodeUnit::from_fq(
18173            file,
18174            CodeUnitType::Function,
18175            unknown_fq,
18176            1,
18177            Some("()".to_string()),
18178            false,
18179        );
18180
18181        let input = vec![
18182            base,
18183            different_kind,
18184            synthetic,
18185            different_package_boundary,
18186            different_segment_kind,
18187        ];
18188        let mut expected = input.clone();
18189        sort_lookup_units(&mut expected);
18190        assert!(expected.windows(2).all(|pair| {
18191            let mut ordered = pair.to_vec();
18192            sort_lookup_units(&mut ordered);
18193            ordered == pair && pair[0] != pair[1]
18194        }));
18195
18196        let mut reversed = input.clone();
18197        reversed.reverse();
18198        sort_lookup_units(&mut reversed);
18199        assert_eq!(reversed, expected);
18200
18201        let mut rotated = input;
18202        rotated.rotate_left(2);
18203        sort_lookup_units(&mut rotated);
18204        assert_eq!(rotated, expected);
18205    }
18206
18207    #[test]
18208    fn displaced_preprocessor_terminator_bounds_the_real_guard() {
18209        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";
18210        let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
18211        let parse = |source: &str| {
18212            let mut parser = Parser::new();
18213            parser
18214                .set_language(&tree_sitter_cpp::LANGUAGE.into())
18215                .expect("C++ grammar");
18216            parser.parse(source, None).expect("fixture tree")
18217        };
18218
18219        let tree = parse(damaged);
18220        let root = tree.root_node();
18221        let target = damaged.find("target").expect("target byte");
18222        let declaration = root
18223            .descendant_for_byte_range(target, target + "target".len())
18224            .and_then(|mut node| {
18225                loop {
18226                    if node.kind() == "declaration" {
18227                        break Some(node);
18228                    }
18229                    node = node.parent()?;
18230                }
18231            })
18232            .expect("declaration after the displaced terminator");
18233        let conditional = declaration
18234            .parent()
18235            .filter(|node| node.kind() == "preproc_ifdef")
18236            .expect("damaged inner conditional");
18237        let outer = conditional
18238            .parent()
18239            .filter(|node| node.kind() == "preproc_ifdef")
18240            .expect("ordinary outer include guard");
18241        let terminator = cpp_displaced_preprocessor_terminator(conditional)
18242            .expect("structured displaced #endif");
18243        assert_eq!(node_text(terminator, damaged), "#endif");
18244        assert!(terminator.end_byte() <= declaration.start_byte());
18245        assert!(!preprocessor_conditional_contains_descendant(
18246            conditional,
18247            declaration
18248        ));
18249        assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
18250        assert!(preprocessor_conditional_contains_descendant(
18251            outer,
18252            declaration
18253        ));
18254
18255        let tree = parse(guarded);
18256        let conditional = tree
18257            .root_node()
18258            .named_child(0)
18259            .filter(|node| node.kind() == "preproc_ifdef")
18260            .expect("ordinary conditional");
18261        let declaration = conditional
18262            .named_children(&mut conditional.walk())
18263            .find(|node| node.kind() == "declaration")
18264            .expect("guarded declaration");
18265        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
18266        assert!(preprocessor_conditional_contains_descendant(
18267            conditional,
18268            declaration
18269        ));
18270
18271        let damaged_alternative = format!(
18272            "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
18273            "UNUSED(value)\n".repeat(64)
18274        );
18275        let tree = parse(&damaged_alternative);
18276        let conditional = tree
18277            .root_node()
18278            .named_child(0)
18279            .filter(|node| node.kind() == "preproc_ifdef")
18280            .expect("outer conditional with an alternative");
18281        assert!(conditional.has_error());
18282        assert!(conditional.child_by_field_name("alternative").is_some());
18283        assert!(
18284            conditional
18285                .child(conditional.child_count() - 1)
18286                .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
18287        );
18288        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
18289
18290        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";
18291        let tree = parse(split_declaration);
18292        let root = tree.root_node();
18293        let conditional = root
18294            .named_children(&mut root.walk())
18295            .find(|node| node.kind() == "preproc_ifdef" && node.start_position().row == 3)
18296            .expect("split declaration conditional");
18297        let target = split_declaration
18298            .find("static int target")
18299            .expect("target byte");
18300        let boundary =
18301            cpp_displaced_preprocessor_boundary(conditional).expect("split declaration boundary");
18302        assert!(boundary.end_byte <= target, "{boundary:?}");
18303        assert_eq!(boundary.end_line, 9, "{boundary:?}");
18304        let target_node = root
18305            .descendant_for_byte_range(target, target + "static".len())
18306            .expect("target node");
18307        assert!(!preprocessor_conditional_contains_descendant(
18308            conditional,
18309            target_node
18310        ));
18311    }
18312
18313    #[test]
18314    fn fragmented_reference_guard_is_recovered() {
18315        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";
18316        let mut parser = Parser::new();
18317        parser
18318            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18319            .expect("C++ grammar");
18320        let tree = parser.parse(source, None).expect("fixture tree");
18321        let start = source.rfind("helper").expect("reference byte");
18322        let node = tree
18323            .root_node()
18324            .descendant_for_byte_range(start, start + "helper".len())
18325            .expect("reference node");
18326        let mut expected = HashSet::default();
18327        expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
18328            vec![
18329                BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
18330                BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
18331            ],
18332        )));
18333        assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
18334    }
18335
18336    #[test]
18337    fn expression_defined_and_ifndef_guards_are_incompatible() {
18338        let source = "#if defined(WIN_MODE)\nint selected;\n#endif\n#ifndef WIN_MODE\nint rejected;\n#endif\n";
18339        let mut parser = Parser::new();
18340        parser
18341            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18342            .expect("C++ grammar");
18343        let tree = parser.parse(source, None).expect("fixture tree");
18344        let root = tree.root_node();
18345        let selected_start = source.find("selected").expect("selected declaration");
18346        let rejected_start = source.find("rejected").expect("rejected declaration");
18347        let selected = root
18348            .descendant_for_byte_range(selected_start, selected_start + "selected".len())
18349            .expect("selected node");
18350        let rejected = root
18351            .descendant_for_byte_range(rejected_start, rejected_start + "rejected".len())
18352            .expect("rejected node");
18353        let selected_guards =
18354            preprocessor_guard_environment(selected, source).expect("selected guards");
18355        let rejected_guards =
18356            preprocessor_guard_environment(rejected, source).expect("rejected guards");
18357
18358        assert!(
18359            merge_preprocessor_guards(&selected_guards, &rejected_guards).is_none(),
18360            "opposite spellings of one macro guard must contradict"
18361        );
18362    }
18363
18364    #[test]
18365    fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
18366        let source = r#"#ifdef _WIN32
18367#if defined(__cplusplus)
18368extern "C"
18369#endif
18370int platform_api(void);
18371#endif
18372
18373#ifdef _WIN32
18374static int entropy_target(void) { return 0; }
18375#else
18376#ifdef HAVE_COMMON_RANDOM
18377static int other_target(void) { return 0; }
18378#elif defined(HAVE_GETENTROPY)
18379static int entropy_target(void) { return 1; }
18380static int use_entropy(void) { return entropy_target(); }
18381#endif
18382#endif
18383"#;
18384        let mut parser = Parser::new();
18385        parser
18386            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18387            .expect("C++ grammar");
18388        let tree = parser.parse(source, None).expect("fixture tree");
18389        let start = source.rfind("entropy_target()").expect("reference");
18390        let node = tree
18391            .root_node()
18392            .descendant_for_byte_range(start, start + "entropy_target".len())
18393            .expect("reference node");
18394        let guards = preprocessor_guard_environment(node, source).expect("active C branch");
18395        assert!(
18396            guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
18397            "{guards:#?}"
18398        );
18399        assert!(
18400            guards.contains(&PreprocessorGuard::Undefined(
18401                "HAVE_COMMON_RANDOM".to_string()
18402            )),
18403            "{guards:#?}"
18404        );
18405        assert!(
18406            guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
18407            "{guards:#?}"
18408        );
18409        assert!(
18410            !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
18411            "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
18412        );
18413    }
18414
18415    #[test]
18416    fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
18417        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";
18418        let mut parser = Parser::new();
18419        parser
18420            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18421            .expect("C++ grammar");
18422        let tree = parser.parse(source, None).expect("fixture tree");
18423        let root = tree.root_node();
18424        let node_at = |text: &str, start: usize| {
18425            root.descendant_for_byte_range(start, start + text.len())
18426                .expect("token node")
18427        };
18428
18429        let key_start = source.find("case KEY").expect("case label") + "case ".len();
18430        let guard_start = source.find("ENABLE_KEYS").expect("guard name");
18431        assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
18432        assert!(!is_ordinary_macro_reference_node(node_at(
18433            "ENABLE_KEYS",
18434            guard_start,
18435        )));
18436    }
18437
18438    #[test]
18439    fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
18440        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";
18441        let mut parser = Parser::new();
18442        parser
18443            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18444            .expect("C++ grammar");
18445        let tree = parser.parse(source, None).expect("fixture tree");
18446        let root = tree.root_node();
18447        let definition_start = source.find("target(void)").expect("definition");
18448        let reference_start = source.rfind("target()").expect("reference");
18449        let definition = root
18450            .descendant_for_byte_range(definition_start, definition_start + "target".len())
18451            .expect("definition node");
18452        let reference = root
18453            .descendant_for_byte_range(reference_start, reference_start + "target".len())
18454            .expect("reference node");
18455        let required =
18456            preprocessor_guard_environment(definition, source).expect("definition guard");
18457        let active = preprocessor_guard_environment(reference, source).expect("reference guard");
18458        assert!(guard_requirements_hold_at_reference(
18459            &required,
18460            Some(&active)
18461        ));
18462    }
18463
18464    #[test]
18465    fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
18466        let source = "g_autoptr(FuChunkArray) self = make_array();";
18467        let mut parser = Parser::new();
18468        parser
18469            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18470            .expect("C++ grammar");
18471        let tree = parser.parse(source, None).expect("fixture tree");
18472        let statement = tree.root_node().named_child(0).expect("statement");
18473        let binding =
18474            recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
18475        assert_eq!(binding.name, "self");
18476        assert_eq!(binding.type_name, "FuChunkArray");
18477        assert_eq!(binding.pointer_depth, 1);
18478
18479        let near_miss = "holder(FuChunkArray) self = make_array();";
18480        let tree = parser.parse(near_miss, None).expect("near-miss tree");
18481        let statement = tree.root_node().named_child(0).expect("statement");
18482        assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
18483    }
18484
18485    #[test]
18486    fn boolean_guard_normalization_proves_equivalence_and_implication() {
18487        let windows = BooleanGuardExpression::Defined("WIN32".to_string());
18488        let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
18489        let negated_windows_branch =
18490            BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
18491        let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
18492        assert_eq!(negated_windows_branch, portable);
18493
18494        let missing_a = BooleanGuardExpression::Undefined("A".to_string());
18495        let missing_b = BooleanGuardExpression::Undefined("B".to_string());
18496        let missing_c = BooleanGuardExpression::Undefined("C".to_string());
18497        let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
18498        let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
18499        assert!(fallback_branch.implies(&fallback_declaration));
18500        assert!(
18501            BooleanGuardExpression::Truthy("FEATURE".to_string())
18502                .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
18503        );
18504        assert!(
18505            BooleanGuardExpression::Undefined("FEATURE".to_string())
18506                .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
18507        );
18508        assert!(
18509            !BooleanGuardExpression::Defined("FEATURE".to_string())
18510                .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
18511        );
18512        assert!(!fallback_declaration.implies(&fallback_branch));
18513    }
18514
18515    #[test]
18516    fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
18517        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";
18518        let mut parser = Parser::new();
18519        parser
18520            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18521            .expect("C++ grammar");
18522        let tree = parser.parse(source, None).expect("fixture tree");
18523        let root = tree.root_node();
18524        let call = |marker: &str| {
18525            let start = source.find(marker).expect("call marker");
18526            let mut node = root
18527                .descendant_for_byte_range(start, start + "helper".len())
18528                .expect("call name node");
18529            loop {
18530                if node.kind() == "call_expression" {
18531                    break node;
18532                }
18533                node = node.parent().expect("call expression ancestor");
18534            }
18535        };
18536        let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
18537        let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
18538        let keyword_call = call("helper(NULL, template); /* bound */");
18539        let keyword_arguments = keyword_call
18540            .child_by_field_name("arguments")
18541            .expect("keyword argument list");
18542        assert_eq!(
18543            recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
18544            1
18545        );
18546        assert_eq!(
18547            recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
18548            0
18549        );
18550
18551        let unbound_call = call("helper(NULL, template); /* unbound */");
18552        let unbound_arguments = unbound_call
18553            .child_by_field_name("arguments")
18554            .expect("unbound argument list");
18555        assert_eq!(
18556            recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
18557            0
18558        );
18559    }
18560
18561    #[test]
18562    fn c_function_declarator_recovery_accepts_invocations_not_binders() {
18563        let source = r#"#define MAKE(type) type *value
18564MAKE(int *);
18565typedef struct Item Item;
18566struct CPUX86State { struct { int ZMM_L(int); } xmm_regs[8]; };
18567void gen_op_movl(void *s, int first, int second) { }
18568const char *strZ(const char *value) { return value; }
18569int body(void *s) {
18570    MAKE(int *);
18571    gen_op_movl(s, offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)),
18572                offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)));
18573    execvp(strZ(value), UNCONSTIFY(char **, args));
18574}
18575 #define DEV_CHECK_PRESENCE(TYPE, MEMBER, DEVTYPE, PROPERTY, VALUE) \
18576    if (!((TYPE)target)->MEMBER) { check(DEVTYPE, PROPERTY, VALUE); }
18577int recovered_deviation(struct Deviation *d, struct Target *target, void *ctx) {
18578    if (d->units) {
18579        switch (target->nodetype) {
18580        case 1:
18581        case 2:
18582            break;
18583        default:
18584            AMEND_WRONG_NODETYPE("deviation", "replace", "units");
18585        }
18586        DEV_CHECK_PRESENCE(struct Item *, units, "replacing", "units", d->units);
18587        lysdict_remove(ctx, ((struct Item *)target)->units);
18588        DUP_STRING_GOTO(ctx, d->units, ((struct Item *)target)->units, ret, cleanup);
18589    }
18590     return 0;
18591 }
18592STATIC EFI_STATUS Encode () { return 0; }
18593"#;
18594        let tree = parse_cpp(source);
18595        let top_macro_start = source.find("MAKE(int *);").expect("top macro");
18596        let top_macro = tree
18597            .root_node()
18598            .named_descendant_for_byte_range(top_macro_start, top_macro_start + 4)
18599            .expect("top macro node");
18600        let body_macro_start = source
18601            .match_indices("MAKE(int *);")
18602            .nth(1)
18603            .expect("body macro")
18604            .0;
18605        let body_macro = tree
18606            .root_node()
18607            .named_descendant_for_byte_range(body_macro_start, body_macro_start + 4)
18608            .expect("body macro node");
18609        let function_call_start = source
18610            .find("gen_op_movl(s, offsetof(CPUX86State")
18611            .expect("function call");
18612        let function_call = tree
18613            .root_node()
18614            .named_descendant_for_byte_range(function_call_start, function_call_start + 11)
18615            .expect("function call node");
18616        let strz_start = source.find("strZ(value)").expect("nested function call");
18617        let strz = tree
18618            .root_node()
18619            .named_descendant_for_byte_range(strz_start, strz_start + 4)
18620            .expect("nested function call node");
18621        let recovered_call_start = source.find("lysdict_remove(ctx").expect("recovered call");
18622        let recovered_call = tree
18623            .root_node()
18624            .named_descendant_for_byte_range(
18625                recovered_call_start,
18626                recovered_call_start + "lysdict_remove".len(),
18627            )
18628            .expect("recovered call node");
18629        let binder_start = source.find("Encode").expect("binder");
18630        let binder = tree
18631            .root_node()
18632            .named_descendant_for_byte_range(binder_start, binder_start + 6)
18633            .expect("binder node");
18634
18635        assert!(recovered_c_function_declarator_invocation(top_macro));
18636        assert!(recovered_c_function_declarator_invocation(body_macro));
18637        assert!(recovered_c_function_declarator_invocation(function_call));
18638        assert!(recovered_c_function_declarator_invocation(strz));
18639        assert!(recovered_c_function_declarator_invocation(recovered_call));
18640        assert!(!recovered_c_function_declarator_invocation(binder));
18641    }
18642
18643    #[test]
18644    fn c_parenthesized_declarator_recovery_keeps_keyword_argument_and_rejects_siblings() {
18645        let source = r#"typedef int krb5_context;
18646int helper(int first, int second) { return first + second; }
18647static krb5_context ctx;
18648int main(int argc, char **argv) {
18649    int ccinitial;
18650    const char *collection_name, *typename;
18651    typename = helper(ctx, ccinitial);
18652    return 0;
18653}
18654"#;
18655        let tree = parse_cpp(source);
18656        let ctx = tree
18657            .root_node()
18658            .descendant_for_byte_range(
18659                source.find("ctx, ccinitial").expect("ctx argument"),
18660                source.find("ctx, ccinitial").expect("ctx argument") + 3,
18661            )
18662            .expect("ctx node");
18663        let ccinitial_start = source.find("ctx, ccinitial").expect("ctx argument") + 5;
18664        let ccinitial = tree
18665            .root_node()
18666            .descendant_for_byte_range(ccinitial_start, ccinitial_start + "ccinitial".len())
18667            .expect("sibling node");
18668        let typename = named_node_at(&tree, source, "typename = helper");
18669        let helper = named_node_at(&tree, source, "helper(ctx, ccinitial)");
18670
18671        assert_eq!(ctx.kind(), "identifier");
18672        assert!(recovered_c_parenthesized_declarator_reference(ctx));
18673        assert!(!recovered_c_parenthesized_declarator_reference(ccinitial));
18674        assert!(!recovered_c_parenthesized_declarator_reference(typename));
18675        assert!(!recovered_c_parenthesized_declarator_reference(helper));
18676    }
18677
18678    fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
18679        let mut parser = Parser::new();
18680        parser
18681            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18682            .expect("C++ grammar");
18683        let tree = parser.parse(source, None).expect("C++ fixture tree");
18684        let mut stack = vec![tree.root_node()];
18685        while let Some(node) = stack.pop() {
18686            if node.kind() == "enum_specifier" {
18687                return flattened_macro_namespace_components(node, source);
18688            }
18689            let mut cursor = node.walk();
18690            let children = node.named_children(&mut cursor).collect::<Vec<_>>();
18691            stack.extend(children.into_iter().rev());
18692        }
18693        None
18694    }
18695
18696    #[test]
18697    fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
18698        let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
18699namespace detail
18700{
18701enum class value_t { null };
18702}
18703NLOHMANN_JSON_NAMESPACE_END
18704NLOHMANN_JSON_NAMESPACE_BEGIN
18705namespace next
18706{
18707struct next_type {};
18708}
18709NLOHMANN_JSON_NAMESPACE_END
18710"#;
18711        assert_eq!(
18712            first_enum_flattened_namespace(complete),
18713            Some(vec!["detail".to_string()])
18714        );
18715
18716        let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
18717        assert_eq!(
18718            first_enum_flattened_namespace(&stale_end),
18719            Some(vec!["detail".to_string()]),
18720            "a stale end marker before the begin marker must not replace the intended namespace"
18721        );
18722
18723        let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
18724namespace detail
18725{
18726enum class value_t { null };
18727}
18728struct next_type {};
18729"#;
18730        assert_eq!(first_enum_flattened_namespace(incomplete), None);
18731    }
18732}
18733
18734/// Comparator laws for the total C++ lookup order introduced by #1876.
18735///
18736/// `sort_lookup_units` is the single tie-break the C++ resolver applies before
18737/// any "first wins" selection (template families in #1836, the visible
18738/// identifier index, the type-candidate lists). If its comparator is not a
18739/// total order over CodeUnit identity, some pair stays tied and the survivor
18740/// falls back to the order the units arrived in -- which is FxHash iteration
18741/// order over keys whose hash covers the absolute workspace root. That is the
18742/// exact mechanism behind #1836 and the #414 / #432 heisenbug, so the laws are
18743/// checked generatively rather than on one hand-picked list.
18744///
18745/// CodeUnit identity is `source`, `kind`, `fq`, `package_segment_count`,
18746/// `signature` and `synthetic` (see `impl PartialEq for CodeUnit`); a CodeUnit
18747/// carries no range, so declaration ranges are covered by the workspace-level
18748/// property in `tests/suite_analyzers/determinism_properties.rs` instead.
18749#[cfg(test)]
18750mod lookup_order_properties {
18751    use super::*;
18752    use proptest::prelude::*;
18753
18754    /// Segment spellings the C++ extractor and the shared renderer actually
18755    /// produce, including the `$`-joined nested spellings and non-ASCII
18756    /// identifiers that a byte-wise comparison has to keep apart.
18757    const ATOMS: [&str; 9] = ["a", "b", "A", "a$b", "a$", "$a", "ab", "naïve", "識別子"];
18758    const REL_PATHS: [&str; 3] = ["a.cpp", "b.cpp", "sub/a.cpp"];
18759    /// Two roots so the order is pinned across workspaces as well as inside
18760    /// one: the root path is precisely the byte string that used to leak into
18761    /// iteration order.
18762    const ROOT_NAMES: [&str; 2] = ["ws", "ws_much_longer_root_name"];
18763    const SIGNATURES: [Option<&str>; 3] = [None, Some("()"), Some("(int)")];
18764    const KINDS: [CodeUnitType; 6] = [
18765        CodeUnitType::Class,
18766        CodeUnitType::Function,
18767        CodeUnitType::Field,
18768        CodeUnitType::Module,
18769        CodeUnitType::Macro,
18770        CodeUnitType::FileScope,
18771    ];
18772
18773    /// Where one unit sits relative to another under the comparator that
18774    /// `sort_lookup_units` owns.
18775    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
18776    enum ProbedOrder {
18777        Before,
18778        Tied,
18779        After,
18780        /// Both directions reported "strictly first": the comparator is not
18781        /// dual, and no sort over it can be order-independent.
18782        Contradictory,
18783    }
18784
18785    impl ProbedOrder {
18786        fn mirror(self) -> Self {
18787            match self {
18788                ProbedOrder::Before => ProbedOrder::After,
18789                ProbedOrder::After => ProbedOrder::Before,
18790                other => other,
18791            }
18792        }
18793
18794        /// -1 / 0 / +1, so transitivity reads as the `<= 0` law.
18795        fn signum(self) -> i8 {
18796            match self {
18797                ProbedOrder::Before => -1,
18798                ProbedOrder::Tied => 0,
18799                ProbedOrder::After => 1,
18800                ProbedOrder::Contradictory => panic!("probed a non-dual comparator"),
18801            }
18802        }
18803    }
18804
18805    /// Read the comparator through its only caller.
18806    ///
18807    /// `sort_lookup_units` is a stable sort, so for a two-element slice the
18808    /// output says exactly whether the comparator put the second element
18809    /// strictly first. Sorting both arrangements of one pair therefore reports
18810    /// the comparator's verdict in both directions, including the contradictory
18811    /// case a single sort would hide.
18812    fn probe_order(left: &CodeUnit, right: &CodeUnit) -> ProbedOrder {
18813        if left == right {
18814            // A stable sort cannot distinguish two equal values, and `Equal` is
18815            // the only verdict a total order can give them.
18816            return ProbedOrder::Tied;
18817        }
18818        let mut forward = vec![left.clone(), right.clone()];
18819        sort_lookup_units(&mut forward);
18820        let mut backward = vec![right.clone(), left.clone()];
18821        sort_lookup_units(&mut backward);
18822        let left_first = backward[0] == *left;
18823        let right_first = forward[0] == *right;
18824        match (left_first, right_first) {
18825            (true, true) => ProbedOrder::Contradictory,
18826            (true, false) => ProbedOrder::Before,
18827            (false, true) => ProbedOrder::After,
18828            (false, false) => ProbedOrder::Tied,
18829        }
18830    }
18831
18832    /// `(kind, text)` per segment. `CodeUnit`'s own `Debug` prints interned
18833    /// segment IDs, which are process-local and say nothing about a failure.
18834    fn fq_segments(unit: &CodeUnit) -> Vec<(&'static str, &'static str)> {
18835        let interner = segment_interner();
18836        unit.fq()
18837            .segments()
18838            .iter()
18839            .map(|&id| {
18840                let (text, kind) = interner.resolve(id);
18841                (kind.name(), text)
18842            })
18843            .collect()
18844    }
18845
18846    fn code_unit_strategy() -> impl Strategy<Value = CodeUnit> {
18847        (
18848            0..ROOT_NAMES.len(),
18849            0..REL_PATHS.len(),
18850            0..KINDS.len(),
18851            prop::collection::vec((0..ATOMS.len(), 0..SegmentKind::ALL.len()), 1..=3),
18852            0..3usize,
18853            0..SIGNATURES.len(),
18854            any::<bool>(),
18855        )
18856            .prop_map(
18857                |(root, rel_path, kind, segments, package_prefix, signature, synthetic)| {
18858                    let source = ProjectFile::new(
18859                        std::env::temp_dir().join(ROOT_NAMES[root]),
18860                        REL_PATHS[rel_path],
18861                    );
18862                    let interner = segment_interner();
18863                    let mut fq = FqName::new();
18864                    for (atom, segment_kind) in &segments {
18865                        fq.push(interner.intern(ATOMS[*atom], SegmentKind::ALL[*segment_kind]));
18866                    }
18867                    // `from_fq` requires a non-empty declaration tail.
18868                    let package_segment_count = package_prefix % fq.len();
18869                    CodeUnit::from_fq(
18870                        source,
18871                        KINDS[kind],
18872                        fq,
18873                        package_segment_count,
18874                        SIGNATURES[signature].map(str::to_string),
18875                        synthetic,
18876                    )
18877                },
18878            )
18879    }
18880
18881    proptest! {
18882        #![proptest_config(ProptestConfig::with_cases(256))]
18883
18884        /// Reflexivity and duality: a unit ties with itself, and no pair is
18885        /// strictly first in both directions.
18886        #[test]
18887        fn lookup_order_is_reflexive_and_dual(
18888            left in code_unit_strategy(),
18889            right in code_unit_strategy(),
18890        ) {
18891            prop_assert_eq!(
18892                probe_order(&left, &left),
18893                ProbedOrder::Tied,
18894                "a unit must tie with itself: {:?}",
18895                left
18896            );
18897            let forward = probe_order(&left, &right);
18898            prop_assert_ne!(
18899                forward,
18900                ProbedOrder::Contradictory,
18901                "comparator put each of these strictly first: left={:?} right={:?}",
18902                left,
18903                right
18904            );
18905            prop_assert_eq!(
18906                probe_order(&right, &left),
18907                forward.mirror(),
18908                "compare(b, a) must reverse compare(a, b): left={:?} right={:?}",
18909                left,
18910                right
18911            );
18912        }
18913
18914        /// Transitivity: `a <= b` and `b <= c` imply `a <= c`.
18915        #[test]
18916        fn lookup_order_is_transitive(
18917            a in code_unit_strategy(),
18918            b in code_unit_strategy(),
18919            c in code_unit_strategy(),
18920        ) {
18921            let ab = probe_order(&a, &b);
18922            let bc = probe_order(&b, &c);
18923            let ac = probe_order(&a, &c);
18924            for (probed, pair) in [(ab, "a,b"), (bc, "b,c"), (ac, "a,c")] {
18925                prop_assert_ne!(
18926                    probed,
18927                    ProbedOrder::Contradictory,
18928                    "comparator is not dual over {}: a={:?} b={:?} c={:?}",
18929                    pair,
18930                    a,
18931                    b,
18932                    c
18933                );
18934            }
18935            if ab.signum() <= 0 && bc.signum() <= 0 {
18936                prop_assert!(
18937                    ac.signum() <= 0,
18938                    "transitivity broken: a<=b ({:?}) and b<=c ({:?}) but a?c is {:?}; \
18939                     a={:?} b={:?} c={:?}",
18940                    ab,
18941                    bc,
18942                    ac,
18943                    a,
18944                    b,
18945                    c
18946                );
18947            }
18948        }
18949
18950        /// The property #1876 exists for: only identical identities may tie.
18951        /// A tie between distinct units is the residual hash-order dependence.
18952        #[test]
18953        fn lookup_order_separates_distinct_identities(
18954            left in code_unit_strategy(),
18955            right in code_unit_strategy(),
18956        ) {
18957            if probe_order(&left, &right) == ProbedOrder::Tied {
18958                prop_assert_eq!(
18959                    &left,
18960                    &right,
18961                    "distinct identities tied, so their order is whatever order they \
18962                     arrived in: left_segments={:?} right_segments={:?}",
18963                    fq_segments(&left),
18964                    fq_segments(&right)
18965                );
18966            }
18967        }
18968
18969        /// The consequence the resolver relies on: the sorted list is a
18970        /// function of the SET of units, not of the order they were pushed in.
18971        #[test]
18972        fn lookup_sort_is_permutation_invariant(
18973            units in prop::collection::vec(code_unit_strategy(), 1..=8),
18974        ) {
18975            let mut sorted = units.clone();
18976            sort_lookup_units(&mut sorted);
18977            for rotation in 0..units.len() {
18978                for reversed in [false, true] {
18979                    let mut permuted = units.clone();
18980                    permuted.rotate_left(rotation);
18981                    if reversed {
18982                        permuted.reverse();
18983                    }
18984                    sort_lookup_units(&mut permuted);
18985                    prop_assert_eq!(
18986                        &permuted,
18987                        &sorted,
18988                        "sorting a permutation gave a different list \
18989                         (rotation={}, reversed={}): input={:?}",
18990                        rotation,
18991                        reversed,
18992                        units
18993                    );
18994                }
18995            }
18996        }
18997    }
18998}