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,
8    enclosing_cpp_namespace_nodes, node_text, normalize_cpp_whitespace, recovered_class_body_at,
9    recovered_function_like_field_declarator, 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, qualified_name_parts,
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;
22use crate::structural::is_cpp_preprocessor_conditional;
23pub use brokk_bifrost_core::analyzer::cpp_facts::{
24    CppClassDeclarationStrength, CppEnumOwnerKind as EnumOwnerKind,
25};
26use brokk_bifrost_core::analyzer::cpp_facts::{
27    CppDeclaredFieldTypeFact as DeclaredFieldTypeFact,
28    CppStructuredAliasTarget as StructuredAliasTarget,
29};
30use brokk_bifrost_core::analyzer::fq_name::{FqName, SegmentKind, segment_interner};
31use brokk_bifrost_core::analyzer::model::{
32    CallableArity, CallableLinkage, CodeUnitType, CppFieldLinkage, CppTemplateExpression,
33    CppTemplateMetadata, CppTemplateParameterMetadata, CppTemplateTerm, Language, LanguageDialect,
34    SignatureMetadata, StructuredTypeName,
35};
36use brokk_bifrost_core::analyzer::pool_memo::PoolSafeMemo;
37use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
38#[cfg(test)]
39use brokk_bifrost_core::analyzer::prepared_syntax::{PreparedSourceOrigin, PreparedSyntaxSource};
40use brokk_bifrost_core::analyzer::query_token::QueryToken;
41use brokk_bifrost_core::analyzer::semantic_diagnostics::contains_node as node_range_contains;
42use brokk_bifrost_core::analyzer::structural::adapter_helpers::{field_name_in_parent, node_range};
43use brokk_bifrost_core::analyzer::tree_walk::{
44    ParentIndex, child_by_field_or_named_child_of_kinds, children_iter, first_named_child_of_kind,
45    named_children_iter, node_for_exact_range, push_named_children_reversed,
46};
47use brokk_bifrost_core::analyzer::usages::common::same_node;
48use brokk_bifrost_core::analyzer::usages::local_inference::LocalInferenceEngine;
49use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
50use brokk_bifrost_core::cancellation::CancellationToken;
51use brokk_bifrost_core::hash::{HashMap, HashSet};
52#[cfg(test)]
53use brokk_bifrost_core::text_utils::compute_line_starts;
54use std::borrow::Cow;
55#[cfg(any(test, feature = "test-support"))]
56use std::cell::Cell;
57use std::cell::OnceCell;
58use std::cmp::Ordering as CmpOrdering;
59use std::collections::BTreeSet;
60use std::hash::Hash;
61use std::sync::atomic::{AtomicUsize, Ordering};
62use std::sync::{Arc, Mutex, OnceLock, RwLock};
63use std::time::{Duration, Instant};
64use tree_sitter::{Node, Parser, Tree};
65
66const PREPROCESSOR_CONTEXT_CACHE_CAP: usize = 8192;
67const CALLABLE_METADATA_CACHE_CAP: usize = 512;
68const MACRO_LEXICAL_INDEX_CACHE_CAP: usize = 64;
69
70#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
71struct PreprocessorContextNodeKey {
72    id: usize,
73    start_byte: usize,
74    end_byte: usize,
75}
76
77/// Query-local memo for malformed-preprocessor boundary discovery.
78///
79/// The boundary is a property of one conditional AST node, but the resolver
80/// asks for it once per declaration below that node and once in each of the
81/// guard/family passes. Keeping this memo on the C++ visitor avoids repeating
82/// the bounded subtree scans without retaining anything after one translation
83/// unit is parsed. A full cache falls back to the uncached computation, so its
84/// memory is explicitly capped and its admission cannot affect answers.
85#[derive(Default)]
86pub(crate) struct PreprocessorContextCache {
87    displaced_boundaries: HashMap<PreprocessorContextNodeKey, Option<usize>>,
88}
89
90impl PreprocessorContextCache {
91    fn displaced_boundary_end(&mut self, conditional: Node<'_>) -> Option<usize> {
92        let key = PreprocessorContextNodeKey {
93            id: conditional.id(),
94            start_byte: conditional.start_byte(),
95            end_byte: conditional.end_byte(),
96        };
97        if let Some(boundary) = self.displaced_boundaries.get(&key) {
98            return *boundary;
99        }
100        let boundary =
101            cpp_displaced_preprocessor_boundary(conditional).map(|boundary| boundary.end_byte);
102        if self.displaced_boundaries.len() < PREPROCESSOR_CONTEXT_CACHE_CAP {
103            self.displaced_boundaries.insert(key, boundary);
104        }
105        boundary
106    }
107}
108
109#[cfg(any(test, feature = "test-support"))]
110thread_local! {
111    static BOUNDED_VISIBILITY_DECLARATION_READ_COUNT: Cell<usize> = const { Cell::new(0) };
112    static BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT: Cell<usize> = const { Cell::new(0) };
113}
114
115#[derive(Clone, Copy, PartialEq, Eq)]
116pub enum TargetKind {
117    Type,
118    Constructor,
119    FreeFunction,
120    Method,
121    GlobalField,
122    MemberField,
123    Macro,
124}
125
126pub enum LexicalTypeResolution {
127    Resolved {
128        unit: CodeUnit,
129        components: Vec<String>,
130        candidates: Vec<CodeUnit>,
131    },
132    Ambiguous,
133    Missing,
134}
135
136#[derive(Clone, Copy)]
137enum TypeCandidateResolution<'a> {
138    Canonical,
139    PreserveAlias,
140    PreserveTarget(&'a CodeUnit),
141}
142
143/// Why a name did not reduce to one indexed type declaration.
144///
145/// The two answers are not interchangeable. `Ambiguous` means the index holds
146/// several declarations and the caller must choose; `Unresolvable` means the
147/// index holds none, which is a boundary the workspace cannot see past. A
148/// `using`/`typedef` alias to a template parameter or to a standard-library
149/// type is unresolvable, and reporting it as ambiguity produced an `ambiguous`
150/// answer with an empty candidate list (#1828).
151#[derive(Clone, Copy, Debug, PartialEq, Eq)]
152enum TypeCandidateFailure {
153    Ambiguous,
154    Unresolvable,
155}
156
157impl TypeCandidateFailure {
158    fn lexical_resolution(self) -> LexicalTypeResolution {
159        match self {
160            Self::Ambiguous => LexicalTypeResolution::Ambiguous,
161            Self::Unresolvable => LexicalTypeResolution::Missing,
162        }
163    }
164}
165
166pub enum LexicalCallableValueResolution {
167    Type(CodeUnit),
168    FreeFunction(CodeUnit),
169    Ambiguous,
170    Missing,
171}
172
173pub enum UsingEnumMemberResolution {
174    Resolved { owner: CodeUnit, member: CodeUnit },
175    Ambiguous,
176    Missing,
177}
178
179pub enum NamespaceValueResolution {
180    Resolved,
181    Ambiguous,
182    Missing,
183}
184
185#[derive(Clone, Debug, PartialEq, Eq)]
186pub enum OrdinaryMacroReferenceResolution {
187    Resolved(CodeUnit),
188    Ambiguous,
189    Missing,
190}
191
192#[derive(Clone, Debug, PartialEq, Eq)]
193pub enum RecoveredCReferenceRanges {
194    Complete(Vec<Range>),
195    LimitExceeded,
196}
197
198pub fn resolve_namespace_value(
199    analyzer: &CppGraphSource<'_>,
200    visibility: &VisibilityIndex<'_>,
201    file: &ProjectFile,
202    namespace: &str,
203    name: &str,
204    before_byte: usize,
205) -> NamespaceValueResolution {
206    let mut matches = Vec::new();
207    for candidate in visibility.visible_identifier_candidates(file, name) {
208        if type_owner_of(analyzer, candidate).is_some()
209            || candidate.package_name() != namespace
210            || (candidate.source() == file
211                && !analyzer
212                    .ranges(candidate)
213                    .iter()
214                    .any(|range| range.start_byte < before_byte))
215            || matches
216                .iter()
217                .any(|existing| same_visible_symbol(existing, candidate))
218        {
219            continue;
220        }
221        matches.push(candidate.clone());
222        if matches.len() > 1 {
223            return NamespaceValueResolution::Ambiguous;
224        }
225    }
226    matches
227        .pop()
228        .map(|_| NamespaceValueResolution::Resolved)
229        .unwrap_or(NamespaceValueResolution::Missing)
230}
231
232pub(crate) struct ScopedUsingEnumOwners {
233    scopes: Vec<Vec<CodeUnit>>,
234}
235
236/// Same-file class and namespace imports collected by the targeted scanner's AST prepass.
237/// Cross-file and inherited class imports are deliberately not inferred without persisted
238/// evidence; a missing imported enumerator therefore remains unproven rather than being
239/// misresolved.
240pub(crate) struct SemanticUsingEnumOwners {
241    class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
242    namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
243}
244
245pub(crate) enum SemanticUsingEnumMemberResolution {
246    Class(UsingEnumMemberResolution),
247    Namespace(UsingEnumMemberResolution),
248    Missing,
249}
250
251impl SemanticUsingEnumOwners {
252    pub(crate) fn new() -> Self {
253        Self {
254            class_imports: HashMap::default(),
255            namespace_imports: HashMap::default(),
256        }
257    }
258
259    pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
260        let imports = self.class_imports.entry(class).or_default();
261        if !imports
262            .iter()
263            .any(|existing| same_visible_symbol(existing, &enum_owner))
264        {
265            imports.push(enum_owner);
266        }
267    }
268
269    pub fn import_namespace(
270        &mut self,
271        namespace: Vec<String>,
272        declaration_byte: usize,
273        enum_owner: CodeUnit,
274    ) {
275        let imports = self.namespace_imports.entry(namespace).or_default();
276        if !imports
277            .iter()
278            .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
279        {
280            imports.push((declaration_byte, enum_owner));
281        }
282    }
283
284    pub fn resolve_member(
285        &self,
286        visibility: &VisibilityIndex<'_>,
287        file: &ProjectFile,
288        class: Option<&CodeUnit>,
289        namespace: &[String],
290        before_byte: usize,
291        name: &str,
292    ) -> SemanticUsingEnumMemberResolution {
293        if let Some(class) = class
294            && let Some((_, imports)) = self
295                .class_imports
296                .iter()
297                .find(|(owner, _)| same_visible_symbol(owner, class))
298        {
299            let resolution =
300                resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
301            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
302                return SemanticUsingEnumMemberResolution::Class(resolution);
303            }
304        }
305        for prefix_len in (0..=namespace.len()).rev() {
306            let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
307                continue;
308            };
309            let owners = imports
310                .iter()
311                .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
312                .map(|(_, owner)| owner);
313            let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
314            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
315                return SemanticUsingEnumMemberResolution::Namespace(resolution);
316            }
317        }
318        SemanticUsingEnumMemberResolution::Missing
319    }
320}
321
322fn resolve_using_enum_member_for_owners<'a>(
323    visibility: &VisibilityIndex<'_>,
324    file: &ProjectFile,
325    owners: impl IntoIterator<Item = &'a CodeUnit>,
326    name: &str,
327) -> UsingEnumMemberResolution {
328    let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
329    for owner in owners {
330        for member in visibility.visible_members_for_owner_name(file, owner, name) {
331            if !member.is_field()
332                || matches.iter().any(|(existing_owner, existing_member)| {
333                    same_visible_symbol(existing_owner, owner)
334                        && same_visible_symbol(existing_member, member)
335                })
336            {
337                continue;
338            }
339            matches.push((owner.clone(), member.clone()));
340        }
341    }
342    match matches.len() {
343        0 => UsingEnumMemberResolution::Missing,
344        1 => {
345            let (owner, member) = matches.pop().expect("one using-enum match");
346            UsingEnumMemberResolution::Resolved { owner, member }
347        }
348        _ => UsingEnumMemberResolution::Ambiguous,
349    }
350}
351
352impl ScopedUsingEnumOwners {
353    pub(crate) fn new() -> Self {
354        Self {
355            scopes: vec![Vec::new()],
356        }
357    }
358
359    pub fn enter_scope(&mut self) {
360        self.scopes.push(Vec::new());
361    }
362
363    pub fn exit_scope(&mut self) {
364        brokk_bifrost_core::analyzer::usages::exit_nested_scope(&mut self.scopes);
365    }
366
367    pub fn import(&mut self, owner: CodeUnit) {
368        let scope = self
369            .scopes
370            .last_mut()
371            .expect("using-enum scope stack is never empty");
372        if !scope
373            .iter()
374            .any(|existing| same_visible_symbol(existing, &owner))
375        {
376            scope.push(owner);
377        }
378    }
379
380    pub fn resolve_member(
381        &self,
382        visibility: &VisibilityIndex<'_>,
383        file: &ProjectFile,
384        name: &str,
385    ) -> UsingEnumMemberResolution {
386        for scope in self.scopes.iter().rev() {
387            let resolution =
388                resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
389            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
390                return resolution;
391            }
392        }
393        UsingEnumMemberResolution::Missing
394    }
395}
396
397#[derive(Clone)]
398pub struct TargetSpec {
399    pub target: CodeUnit,
400    pub kind: TargetKind,
401    pub owner: Option<CodeUnit>,
402    pub member_name: String,
403    pub callable_arity: Option<CallableArity>,
404    pub activated_callable_arities: Vec<ActivatedCallableArity>,
405    pub param_types: Option<Vec<String>>,
406    pub enum_owner_kind: EnumOwnerKind,
407    pub owner_is_forward_declaration: bool,
408    pub callable_has_definition_body: bool,
409}
410
411#[derive(Clone, Copy)]
412pub struct ActivatedCallableArity {
413    pub activation_byte: usize,
414    pub arity: CallableArity,
415}
416
417#[derive(Debug, PartialEq, Eq, Hash)]
418pub struct TypeScanKey {
419    target: LogicalSymbolKey,
420    member_name: String,
421}
422
423#[derive(Clone, Debug, PartialEq, Eq, Hash)]
424struct LogicalSymbolKey {
425    kind: CodeUnitType,
426    fq_name: String,
427    signature: Option<String>,
428}
429
430struct ResolvedTypeOwner {
431    unit: CodeUnit,
432    is_forward_declaration: bool,
433}
434
435impl TargetSpec {
436    pub fn type_scan_key(&self) -> Option<TypeScanKey> {
437        (self.kind == TargetKind::Type).then(|| TypeScanKey {
438            target: logical_symbol_key(&self.target),
439            member_name: self.member_name.clone(),
440        })
441    }
442
443    pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
444        if target.is_class() {
445            return Some(Self::new(
446                target.clone(),
447                TargetKind::Type,
448                Some(target.clone()),
449                target.identifier().to_string(),
450                None,
451                None,
452            ));
453        }
454
455        if target.is_field() {
456            // A namespace (module) is not a receiver: a namespace-scoped constant such as
457            // `example::DefaultPrefix` is referenced unqualified from inside the namespace and
458            // qualified from outside, exactly like a global. Treating a module owner as a
459            // member-field owner makes the receiver/owner-context match reject every valid
460            // reference, so resolve it as a global field instead.
461            let owner = type_owner_of(analyzer, target);
462            let kind = if owner.is_some() {
463                TargetKind::MemberField
464            } else {
465                TargetKind::GlobalField
466            };
467            let enum_owner_kind = owner
468                .as_ref()
469                .map(|owner| classify_enum_owner(analyzer, owner))
470                .unwrap_or(EnumOwnerKind::NonEnum);
471            let mut spec = Self::new(
472                target.clone(),
473                kind,
474                owner,
475                target.identifier().to_string(),
476                None,
477                None,
478            );
479            spec.enum_owner_kind = enum_owner_kind;
480            return Some(spec);
481        }
482
483        if target.is_function() {
484            // Free functions declared inside a namespace have a module owner; that namespace is
485            // not a call receiver, so resolve them as free functions rather than methods.
486            let owner_resolution = target_type_owner_resolution(analyzer, target);
487            let owner_is_forward_declaration = owner_resolution
488                .as_ref()
489                .is_some_and(|owner| owner.is_forward_declaration);
490            let owner = owner_resolution.map(|owner| owner.unit);
491            let kind = if owner.as_ref().is_some_and(|owner| {
492                target.identifier() == owner.identifier()
493                    || analyzer
494                        .cpp
495                        .and_then(|cpp| cpp.template_metadata(owner))
496                        .is_some_and(|metadata| metadata.primary_name == target.identifier())
497            }) {
498                TargetKind::Constructor
499            } else if owner.is_some() {
500                TargetKind::Method
501            } else {
502                TargetKind::FreeFunction
503            };
504            let mut spec = Self::new(
505                target.clone(),
506                kind,
507                owner,
508                target.identifier().to_string(),
509                cpp_callable_arity(analyzer, target),
510                cpp_callable_parameter_types(analyzer, target),
511            );
512            spec.owner_is_forward_declaration = owner_is_forward_declaration;
513            spec.callable_has_definition_body =
514                callable_target_has_definition_body(analyzer, target);
515            return Some(spec);
516        }
517
518        if target.is_macro() {
519            return Some(Self::new(
520                target.clone(),
521                TargetKind::Macro,
522                None,
523                target.identifier().to_string(),
524                None,
525                None,
526            ));
527        }
528
529        None
530    }
531
532    pub fn with_visible_callable_arities<'a>(
533        &'a self,
534        analyzer: &CppGraphSource<'_>,
535        cpp: &dyn CppSource,
536        visibility: &VisibilityIndex<'_>,
537        file: &ProjectFile,
538        prepared: &PreparedSyntaxTree,
539    ) -> Cow<'a, Self> {
540        let macro_parameter_arity =
541            visibility.callable_parameter_macro_arity(&self.target, self.param_types.as_deref());
542        let activated_callable_arities =
543            visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
544        if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
545            return Cow::Borrowed(self);
546        }
547        let mut effective = self.clone();
548        if let Some(macro_parameter_arity) = macro_parameter_arity {
549            effective.callable_arity = Some(macro_parameter_arity);
550        }
551        effective.activated_callable_arities = activated_callable_arities;
552        Cow::Owned(effective)
553    }
554
555    pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
556        let base = self.callable_arity?;
557        Some(
558            self.activated_callable_arities
559                .iter()
560                .filter(|candidate| candidate.activation_byte <= byte)
561                .fold(base, |arity, candidate| {
562                    merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
563                }),
564        )
565    }
566
567    pub fn new(
568        target: CodeUnit,
569        kind: TargetKind,
570        owner: Option<CodeUnit>,
571        member_name: String,
572        callable_arity: Option<CallableArity>,
573        param_types: Option<Vec<String>>,
574    ) -> Self {
575        Self {
576            target,
577            kind,
578            owner,
579            member_name,
580            callable_arity,
581            activated_callable_arities: Vec::new(),
582            param_types,
583            enum_owner_kind: EnumOwnerKind::NonEnum,
584            owner_is_forward_declaration: false,
585            callable_has_definition_body: false,
586        }
587    }
588}
589
590fn callable_target_has_definition_body(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> bool {
591    analyzer
592        .signature_metadata(target)
593        .iter()
594        .any(|metadata| !metadata.is_declaration_only())
595}
596
597fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
598    LogicalSymbolKey {
599        kind: unit.kind(),
600        fq_name: unit.fq_name(),
601        signature: unit.signature().map(str::to_string),
602    }
603}
604
605fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
606    let Some(facts) = analyzer.declaration_source_properties(owner) else {
607        return EnumOwnerKind::NonEnum;
608    };
609    let Some(first) = facts.first() else {
610        return EnumOwnerKind::NonEnum;
611    };
612    if facts.iter().all(|fact| fact.enum_kind == first.enum_kind) {
613        first.enum_kind
614    } else {
615        EnumOwnerKind::NonEnum
616    }
617}
618
619#[derive(Clone, PartialEq, Eq, Hash)]
620pub struct CppScanBinding {
621    pub unit: Option<CodeUnit>,
622    pub type_name: Option<String>,
623    pub indirection: i32,
624}
625
626impl CppScanBinding {
627    pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
628        Self::from_type_name(cpp_name_for(&unit), Some(unit), indirection)
629    }
630
631    pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
632        Self {
633            type_name: Some(type_name),
634            unit,
635            indirection,
636        }
637    }
638
639    pub fn as_arg_type(&self) -> Option<CppArgType> {
640        let name = self
641            .type_name
642            .clone()
643            .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
644        Some(CppArgType {
645            name,
646            unit: self.unit.clone(),
647            indirection: self.indirection,
648            pointee_const: false,
649        })
650    }
651}
652
653type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
654pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
655pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
656type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
657type MacroEnvironmentCheckpointCell = Arc<OnceLock<MacroEnvironmentCheckpoints>>;
658type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
659type MacroLexicalTemplateCache =
660    HashMap<(ProjectFile, usize), Option<crate::graph::macro_lexical::MacroTemplate>>;
661
662type MacroLocalBindingTemplateCache =
663    HashMap<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>;
664type MacroReplacementBodyCache = HashMap<(ProjectFile, usize), Option<Arc<ParsedReplacementBody>>>;
665type MacroTypeParameterCache = HashMap<(ProjectFile, usize), Option<Arc<[usize]>>>;
666type StructuredIncludeFactCell = Arc<OnceLock<Arc<[StructuredIncludeFact]>>>;
667
668struct StructuredIncludeFact {
669    start_byte: usize,
670    end_byte: usize,
671    path: String,
672}
673
674#[derive(Clone, Default)]
675pub struct MacroEnvironment {
676    bindings: HashMap<String, MacroBinding>,
677    known_undefined_names: HashSet<String>,
678    /// Names the translation unit's compile command proves defined (#2011):
679    /// the `-D`s that survive command ordering, intersected across every
680    /// configuration naming the TU. Seeded once at TU start. An explicit
681    /// `#undef` seen later lands in `known_undefined_names` and wins.
682    build_proven_defines: HashSet<String>,
683    unknown_names: bool,
684    applied_pragma_once_files: HashSet<ProjectFile>,
685    maybe_applied_pragma_once_files: HashSet<ProjectFile>,
686}
687
688/// How many macro events one checkpoint window may cover.
689///
690/// A request for an environment replays only the events between the nearest
691/// earlier checkpoint and its own frontier, so one file's whole scan costs its
692/// event count (the checkpoint build) plus this many applications per request,
693/// whatever order the requests arrive in. The forward cursor this replaced was
694/// optimal for one worker reading one file in byte order and quadratic for the
695/// inverse, which asks many workers for positions that move backwards (#1496).
696pub const MACRO_ENVIRONMENT_CHECKPOINT_STRIDE: usize = 32;
697
698/// One event prefix of a file whose environment the index keeps.
699struct MacroEnvironmentCheckpoint {
700    /// How many of the file's events this environment has applied.
701    frontier: usize,
702    environment: Arc<MacroEnvironment>,
703}
704
705/// The replay checkpoints for one file's macro events, ascending by frontier
706/// and always starting at frontier zero (the compile-proven defines alone).
707///
708/// Checkpoints use an adaptive stride no greater than
709/// [`MACRO_ENVIRONMENT_CHECKPOINT_STRIDE`], and one lands directly after every
710/// `#include` event. Applying an include event replays the included file's
711/// complete event list, so keeping one there holds that unbounded cost out of
712/// every later replay window.
713struct MacroEnvironmentCheckpoints {
714    checkpoints: Vec<MacroEnvironmentCheckpoint>,
715}
716
717impl MacroEnvironmentCheckpoints {
718    /// The latest checkpoint at or before `frontier`.
719    fn at_or_before(&self, frontier: usize) -> &MacroEnvironmentCheckpoint {
720        let index = self
721            .checkpoints
722            .partition_point(|checkpoint| checkpoint.frontier <= frontier);
723        assert!(
724            index > 0,
725            "a checkpoint vector starts at frontier zero, which precedes every request"
726        );
727        &self.checkpoints[index - 1]
728    }
729}
730
731fn clone_or_default_file_cell<T: Default + Clone>(
732    cells: &Mutex<HashMap<ProjectFile, T>>,
733    file: &ProjectFile,
734    poison_message: &str,
735) -> T {
736    cells
737        .lock()
738        .expect(poison_message)
739        .entry(file.clone())
740        .or_default()
741        .clone()
742}
743
744impl MacroEnvironment {
745    fn binding(&self, name: &str) -> Option<&MacroBinding> {
746        self.bindings.get(name)
747    }
748
749    fn may_bind(&self, name: &str) -> bool {
750        self.bindings.contains_key(name) || self.unknown_names
751    }
752
753    fn insert(&mut self, name: String, binding: MacroBinding) {
754        self.known_undefined_names.remove(&name);
755        self.bindings.insert(name, binding);
756    }
757
758    fn remove(&mut self, name: &str) {
759        self.bindings.remove(name);
760        self.known_undefined_names.insert(name.to_string());
761    }
762
763    fn remove_known_undefined(&mut self, name: &str) {
764        self.known_undefined_names.remove(name);
765    }
766
767    fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
768        for binding in self.bindings.values_mut() {
769            *binding = MacroBinding::uncertain_from(binding, source, byte);
770        }
771        self.known_undefined_names.clear();
772        // An untracked include could `#undef` a command-line define, so the
773        // may-hold filter must stop treating the build facts as decisive from
774        // here on. The additive proof path keeps its facts: they still hold at
775        // the include chain's activation point.
776        self.build_proven_defines.clear();
777        self.unknown_names = true;
778    }
779
780    fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
781        guards.iter().all(|guard| self.guard_may_hold(guard))
782    }
783
784    fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
785        let Some(expression) = guard.as_boolean_expression() else {
786            return true;
787        };
788        self.boolean_guard_may_hold(&expression)
789    }
790
791    fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
792        match expression {
793            BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
794            BooleanGuardExpression::Undefined(name) => {
795                self.bindings
796                    .get(name)
797                    .is_none_or(|binding| !binding.is_exact())
798                    && (!self.build_proven_defines.contains(name)
799                        || self.known_undefined_names.contains(name))
800            }
801            BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
802            BooleanGuardExpression::Opaque(_)
803            | BooleanGuardExpression::NegatedOpaque(_)
804            | BooleanGuardExpression::Constant(true) => true,
805            BooleanGuardExpression::Constant(false) => false,
806            BooleanGuardExpression::All(expressions) => expressions
807                .iter()
808                .all(|expression| self.boolean_guard_may_hold(expression)),
809            BooleanGuardExpression::Any(expressions) => expressions
810                .iter()
811                .any(|expression| self.boolean_guard_may_hold(expression)),
812        }
813    }
814}
815
816fn any_visible_source_file_matches(
817    visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
818    file: &ProjectFile,
819    mut predicate: impl FnMut(&ProjectFile) -> bool,
820) -> bool {
821    match visible_source_files_by_root.get(file) {
822        Some(files) => files.iter().any(predicate),
823        None => predicate(file),
824    }
825}
826
827#[derive(Clone)]
828pub enum EffectiveUsingTarget {
829    Ordinary {
830        name: String,
831        target_components: Vec<String>,
832        global: bool,
833    },
834    Namespace {
835        namespace_components: Vec<String>,
836        global: bool,
837    },
838}
839
840#[derive(Clone)]
841pub struct OrdinaryTypeImport {
842    pub target: EffectiveUsingTarget,
843    pub source: ProjectFile,
844    pub declaration_byte: usize,
845    pub scope_start: usize,
846    pub scope_end: usize,
847    pub scope_depth: usize,
848    pub block_scope: bool,
849    pub lexical_depth: usize,
850    pub declaration_namespace: Vec<String>,
851    pub namespace_scope: Option<Vec<String>>,
852    pub resolved_target_components: Option<Vec<String>>,
853    pub required_guards: HashSet<PreprocessorGuard>,
854}
855
856#[derive(Clone)]
857pub struct ConditionalIncludeProjection {
858    pub activation_byte: usize,
859    pub required_guards: HashSet<PreprocessorGuard>,
860    /// The subset of `required_guards` a lone `#if` contributes: a branch no
861    /// sibling `#else` completes, so no configuration is obliged to take it.
862    /// [`IncludePathAdmission::Compatible`] still demands those.
863    pub partial_guards: HashSet<PreprocessorGuard>,
864}
865
866#[derive(Default)]
867pub struct SourceUsingIndex {
868    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
869    pub directives: Vec<OrdinaryTypeImport>,
870}
871
872#[derive(Default)]
873pub struct ProjectUsingIndex {
874    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
875    pub directives: Vec<OrdinaryTypeImport>,
876}
877
878type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
879
880pub struct EffectiveUsingIndex {
881    projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
882}
883
884impl EffectiveUsingIndex {
885    fn new(_root: ProjectFile) -> Self {
886        Self {
887            projected_by_name: Mutex::new(HashMap::default()),
888        }
889    }
890
891    pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
892        self.projected_by_name
893            .lock()
894            .expect("C++ effective-using projection cache poisoned")
895            .entry(name.to_string())
896            .or_default()
897            .clone()
898    }
899}
900
901pub enum OrdinaryTypeImportResolution {
902    Resolved {
903        target: CodeUnit,
904        target_components: Vec<String>,
905        lexical_depth: usize,
906        is_direct: bool,
907    },
908    Ambiguous {
909        lexical_depth: usize,
910    },
911    Missing,
912}
913
914type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
915type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
916type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
917type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
918type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
919type ParserAliasTargetMatchCell = Arc<OnceLock<bool>>;
920type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
921type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
922
923/// One callable declaration's inputs to [`VisibilityIndex::same_logical_callable`],
924/// read from its declaration syntax rather than from its persisted signature
925/// string: the comparable shape of each parameter, and the trailing identity
926/// suffix that shape does not carry.
927struct ExtractedComparable {
928    shapes: Vec<CppComparableSlot>,
929    suffix: String,
930}
931
932/// How many alias hops [`VisibilityIndex::same_logical_callable`] follows
933/// before giving up on a written type name. A visited set already stops a
934/// cycle; this stops an adversarially long chain from costing a lookup per hop.
935const MAX_COMPARABLE_ALIAS_HOPS: usize = 32;
936
937/// Which comparison a conditional `#include` path's guards face before the
938/// header it reaches counts as visible at a reference.
939///
940/// `Proven` is the build's rule: `compile_commands.json` covers the
941/// reference's translation unit, so the reference's active guards plus the
942/// defines the build proves must imply every guard on the path, and a
943/// platform macro the build does not prove keeps the header invisible
944/// (#2011).
945///
946/// `Compatible` is the rule for a translation unit no build describes. A
947/// complete `#if`/`#else` family is a case analysis every configuration takes
948/// one branch of, so a branch of one is admitted unless its guards contradict
949/// the reference's own -- the rule the resolver already applies to a
950/// declaration's own guards (#2988). A lone `#if` is a different claim: no
951/// configuration is obliged to take it, so its guards stay strictly required
952/// and an unprovable one still reports `missing_compile_context` (#2011).
953///
954/// Without the first half, libuv's `uv/threadpool.h`, which `uv.h` includes
955/// from both arms of `#if defined(_WIN32)`, and `uv/unix.h`, which it includes
956/// from the `#else` arm, are invisible to every unguarded reference in the
957/// project (#3088).
958#[derive(Clone, Copy, Debug, PartialEq, Eq)]
959enum IncludePathAdmission {
960    Proven,
961    Compatible,
962}
963
964impl IncludePathAdmission {
965    fn admits(
966        self,
967        required: &HashSet<PreprocessorGuard>,
968        partial: &HashSet<PreprocessorGuard>,
969        reference_guards: Option<&HashSet<PreprocessorGuard>>,
970    ) -> bool {
971        match self {
972            Self::Proven => guard_requirements_hold_at_reference(required, reference_guards),
973            Self::Compatible => {
974                guard_requirements_hold_at_reference(partial, reference_guards)
975                    && guards_compatible_at_reference(required, reference_guards)
976            }
977        }
978    }
979}
980
981/// Per-query C++ visibility facts.
982///
983/// The analyzer is *borrowed*, never cloned: `TreeSitterAnalyzer::clone` gives
984/// the clone a fresh, empty `QueryReadCache` on purpose (clones cross
985/// generations and overlays, where another generation's hydrated states would
986/// be wrong). An index that owned a clone would therefore see an inactive read
987/// cache for every `prepared_syntax` call it makes, re-reading and re-parsing
988/// the same source from the store once per candidate instead of once per query
989/// — the #1175 blow-up, where one scan re-parsed a 4.8 MB generated header
990/// tens of thousands of times.
991pub struct VisibilityIndex<'a> {
992    cpp: &'a dyn CppSource,
993    /// Proof that the request scope the index was built under is still open.
994    /// The index is a per-query object whose lifetime is inside the scope's,
995    /// so carrying the token here instead of on ninety method signatures is
996    /// the same guarantee for far less plumbing (issue #2414 step 3).
997    token: QueryToken<'a>,
998    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
999    visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
1000    global_field_internal_linkage: HashMap<CodeUnit, bool>,
1001    visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
1002    alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
1003    visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
1004    parser_alias_target_matches:
1005        RwLock<HashMap<(ProjectFile, String, LogicalSymbolKey), ParserAliasTargetMatchCell>>,
1006    ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
1007    project_using_index: OnceLock<ProjectUsingIndex>,
1008    callable_reference_specs:
1009        Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
1010    structured_include_fact_cells: Mutex<HashMap<ProjectFile, StructuredIncludeFactCell>>,
1011    include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
1012    compile_proven_guard_cells: Mutex<HashMap<ProjectFile, Arc<HashSet<PreprocessorGuard>>>>,
1013    include_path_admission_cells: Mutex<HashMap<ProjectFile, IncludePathAdmission>>,
1014    conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
1015    #[cfg(any(test, feature = "test-support"))]
1016    conditional_include_projection_index_build_count: AtomicUsize,
1017    #[cfg(any(test, feature = "test-support"))]
1018    conditional_include_projection_state_count: AtomicUsize,
1019    #[cfg(any(test, feature = "test-support"))]
1020    conditional_include_target_state_count: AtomicUsize,
1021    #[cfg(any(test, feature = "test-support"))]
1022    include_activation_build_count: AtomicUsize,
1023    #[cfg(any(test, feature = "test-support"))]
1024    using_donor_activation_count: AtomicUsize,
1025    #[cfg(any(test, feature = "test-support"))]
1026    using_namespace_lookup_count: AtomicUsize,
1027    #[cfg(any(test, feature = "test-support"))]
1028    using_name_candidate_inspection_count: AtomicUsize,
1029    #[cfg(any(test, feature = "test-support"))]
1030    callable_reference_spec_build_count: AtomicUsize,
1031    #[cfg(any(test, feature = "test-support"))]
1032    alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
1033    #[cfg(any(test, feature = "test-support"))]
1034    visible_parser_alias_name_set_build_count: AtomicUsize,
1035    parser_alias_fallback_calls: AtomicUsize,
1036    parser_alias_fallback_files: AtomicUsize,
1037    parser_alias_source_parses: AtomicUsize,
1038    parser_alias_fallback_elapsed_micros: AtomicUsize,
1039    field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
1040    structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
1041    callable_comparables: Mutex<HashMap<CodeUnit, Option<Arc<ExtractedComparable>>>>,
1042    /// Complete callable metadata is expensive for reconciled C++ members:
1043    /// each read can rebuild the declaration/definition evidence. Reuse it
1044    /// across the member, arity, identity, and navigation phases of this
1045    /// visibility query, with an explicit cap so the query never retains a
1046    /// workspace-sized unit table.
1047    callable_metadata: Mutex<HashMap<CodeUnit, Arc<Vec<SignatureMetadata>>>>,
1048    comparable_name_declarations: Mutex<HashMap<StructuredTypeName, Option<CodeUnit>>>,
1049    indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
1050    indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
1051    precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1052    c_tag_kind_cache: Mutex<HashMap<CodeUnit, Option<CppCTagKind>>>,
1053    c_tag_complete_definition_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1054    macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
1055    macro_event_name_sets: Mutex<HashMap<ProjectFile, Arc<HashSet<String>>>>,
1056    pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
1057    // The environment at selected event prefixes of each file, built once and read by every
1058    // worker. The authoritative differential shares this index across target workers whose
1059    // frontiers interleave arbitrarily and move backwards, so a forward cursor -- per worker or
1060    // not -- replayed a file's events once per backward request. Checkpoints answer any position
1061    // with a binary search and at most one stride of replay, and being immutable they need no
1062    // per-worker copy (#1496).
1063    macro_environment_checkpoints: Mutex<HashMap<ProjectFile, MacroEnvironmentCheckpointCell>>,
1064    macro_replacements: Mutex<MacroReplacementCache>,
1065    macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
1066    pub(crate) macro_lexical_templates: Mutex<MacroLexicalTemplateCache>,
1067    macro_lexical_indexes:
1068        Mutex<HashMap<ProjectFile, Arc<crate::graph::macro_lexical::MacroLexicalIndex>>>,
1069    macro_replacement_bodies: Mutex<MacroReplacementBodyCache>,
1070    macro_type_parameters: Mutex<MacroTypeParameterCache>,
1071    callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
1072    #[cfg(any(test, feature = "test-support"))]
1073    pub macro_replacement_parse_count: AtomicUsize,
1074    #[cfg(any(test, feature = "test-support"))]
1075    pub macro_event_application_count: AtomicUsize,
1076    /// How many files had their checkpoint vector built. One build per file
1077    /// per query even when workers race for the same file.
1078    #[cfg(any(test, feature = "test-support"))]
1079    pub macro_environment_checkpoint_build_count: AtomicUsize,
1080    /// How many requests landed off a checkpoint and so had to copy one and
1081    /// replay the events after it.
1082    #[cfg(any(test, feature = "test-support"))]
1083    pub macro_environment_copy_count: AtomicUsize,
1084    #[cfg(any(test, feature = "test-support"))]
1085    pub macro_environment_request_count: AtomicUsize,
1086    cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
1087    cpp_template_families: HashMap<String, Vec<CodeUnit>>,
1088    #[cfg(any(test, feature = "test-support"))]
1089    qualified_candidate_inspections: AtomicUsize,
1090    #[cfg(any(test, feature = "test-support"))]
1091    target_preserving_type_resolution_count: AtomicUsize,
1092    #[cfg(any(test, feature = "test-support"))]
1093    visibility_identifier_lookup_count: usize,
1094    #[cfg(any(test, feature = "test-support"))]
1095    visibility_identifier_batch_count: usize,
1096}
1097
1098impl Drop for VisibilityIndex<'_> {
1099    fn drop(&mut self) {
1100        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_none() {
1101            return;
1102        }
1103        #[cfg(any(test, feature = "test-support"))]
1104        eprintln!(
1105            "BIFROST_CPP_MACRO_STATS requests={} copies={} checkpoint_builds={} applications={}",
1106            self.macro_environment_request_count.load(Ordering::Relaxed),
1107            self.macro_environment_copy_count.load(Ordering::Relaxed),
1108            self.macro_environment_checkpoint_build_count
1109                .load(Ordering::Relaxed),
1110            self.macro_event_application_count.load(Ordering::Relaxed),
1111        );
1112        let calls = self.parser_alias_fallback_calls.load(Ordering::Relaxed);
1113        if calls == 0 {
1114            return;
1115        }
1116        eprintln!(
1117            "BIFROST_CPP_ALIAS_FALLBACK_STATS calls={} files={} source_parses={} elapsed_ms={}",
1118            calls,
1119            self.parser_alias_fallback_files.load(Ordering::Relaxed),
1120            self.parser_alias_source_parses.load(Ordering::Relaxed),
1121            self.parser_alias_fallback_elapsed_micros
1122                .load(Ordering::Relaxed)
1123                / 1_000,
1124        );
1125    }
1126}
1127
1128#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1129pub enum PreprocessorGuard {
1130    Defined(String),
1131    Undefined(String),
1132    Boolean(BooleanGuardExpression),
1133    Expression(String),
1134    NegatedExpression(String),
1135    Constant(bool),
1136}
1137
1138#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1139pub enum BooleanGuardExpression {
1140    Defined(String),
1141    Undefined(String),
1142    Truthy(String),
1143    Falsy(String),
1144    Opaque(String),
1145    NegatedOpaque(String),
1146    All(Vec<BooleanGuardExpression>),
1147    Any(Vec<BooleanGuardExpression>),
1148    Constant(bool),
1149}
1150
1151impl BooleanGuardExpression {
1152    fn negated(&self) -> Self {
1153        match self {
1154            Self::Defined(name) => Self::Undefined(name.clone()),
1155            Self::Undefined(name) => Self::Defined(name.clone()),
1156            Self::Truthy(name) => Self::Falsy(name.clone()),
1157            Self::Falsy(name) => Self::Truthy(name.clone()),
1158            Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
1159            Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
1160            Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
1161            Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
1162            Self::Constant(value) => Self::Constant(!value),
1163        }
1164    }
1165
1166    fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
1167        Self::normalized(expressions, true)
1168    }
1169
1170    fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
1171        Self::normalized(expressions, false)
1172    }
1173
1174    fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
1175        let mut normalized = Vec::new();
1176        for expression in expressions {
1177            match expression {
1178                Self::All(nested) if conjunction => normalized.extend(nested),
1179                Self::Any(nested) if !conjunction => normalized.extend(nested),
1180                Self::Constant(value) if value == conjunction => {}
1181                Self::Constant(value) => return Self::Constant(value),
1182                expression => normalized.push(expression),
1183            }
1184        }
1185        normalized.sort_unstable();
1186        normalized.dedup();
1187        match normalized.len() {
1188            0 => Self::Constant(conjunction),
1189            1 => normalized.pop().expect("one Boolean guard expression"),
1190            _ if conjunction => Self::All(normalized),
1191            _ => Self::Any(normalized),
1192        }
1193    }
1194
1195    fn implies(&self, required: &Self) -> bool {
1196        if self == required
1197            || matches!(self, Self::Constant(false))
1198            || matches!(required, Self::Constant(true))
1199        {
1200            return true;
1201        }
1202        if matches!(
1203            (self, required),
1204            (Self::Truthy(active), Self::Defined(required))
1205                | (Self::Undefined(active), Self::Falsy(required))
1206                if active == required
1207        ) {
1208            return true;
1209        }
1210        match self {
1211            Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
1212            Self::All(active) => match required {
1213                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1214                _ => active.iter().any(|expression| expression.implies(required)),
1215            },
1216            _ => match required {
1217                Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
1218                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1219                _ => false,
1220            },
1221        }
1222    }
1223
1224    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1225        match self {
1226            Self::Defined(name)
1227            | Self::Undefined(name)
1228            | Self::Truthy(name)
1229            | Self::Falsy(name) => name == macro_name,
1230            // Opaque expressions have structured conditional ownership but no
1231            // structured macro operands, so any mutation may change them.
1232            Self::Opaque(_) | Self::NegatedOpaque(_) => true,
1233            Self::All(expressions) | Self::Any(expressions) => expressions
1234                .iter()
1235                .any(|expression| expression.may_depend_on_macro(macro_name)),
1236            Self::Constant(_) => false,
1237        }
1238    }
1239
1240    pub fn heap_size(&self) -> usize {
1241        match self {
1242            Self::Defined(value)
1243            | Self::Undefined(value)
1244            | Self::Truthy(value)
1245            | Self::Falsy(value)
1246            | Self::Opaque(value)
1247            | Self::NegatedOpaque(value) => value.len(),
1248            Self::All(expressions) | Self::Any(expressions) => {
1249                expressions
1250                    .iter()
1251                    .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
1252                        size.saturating_add(std::mem::size_of::<Self>())
1253                            .saturating_add(expression.heap_size())
1254                    })
1255            }
1256            Self::Constant(_) => 0,
1257        }
1258    }
1259}
1260
1261impl PreprocessorGuard {
1262    fn as_boolean_expression(&self) -> Option<BooleanGuardExpression> {
1263        match self {
1264            Self::Defined(name) => Some(BooleanGuardExpression::Defined(name.clone())),
1265            Self::Undefined(name) => Some(BooleanGuardExpression::Undefined(name.clone())),
1266            Self::Boolean(expression) => Some(expression.clone()),
1267            Self::Constant(value) => Some(BooleanGuardExpression::Constant(*value)),
1268            Self::Expression(_) | Self::NegatedExpression(_) => None,
1269        }
1270    }
1271
1272    fn negated(&self) -> Self {
1273        match self {
1274            Self::Defined(name) => Self::Undefined(name.clone()),
1275            Self::Undefined(name) => Self::Defined(name.clone()),
1276            Self::Boolean(expression) => Self::Boolean(expression.negated()),
1277            Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1278            Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1279            Self::Constant(value) => Self::Constant(!value),
1280        }
1281    }
1282
1283    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1284        match self {
1285            Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1286            Self::Boolean(expression) => expression.may_depend_on_macro(macro_name),
1287            // These expressions could not be lowered to a Boolean operand
1288            // tree, so their dependencies remain unknown.
1289            Self::Expression(_) | Self::NegatedExpression(_) => true,
1290            Self::Constant(_) => false,
1291        }
1292    }
1293}
1294
1295#[derive(Clone, PartialEq, Eq)]
1296pub enum MacroDefinition {
1297    Object {
1298        replacement: String,
1299    },
1300    Function {
1301        parameters: Vec<String>,
1302        replacement: String,
1303    },
1304    VariadicFunction {
1305        parameters: Vec<String>,
1306        replacement: String,
1307    },
1308    Unsupported,
1309}
1310
1311#[derive(Clone, Debug, PartialEq, Eq)]
1312pub enum MacroIncludeProtection {
1313    MacroGuard(String),
1314    PragmaOnce,
1315    None,
1316}
1317
1318enum ParsedMacroReplacement {
1319    Parsed { source: String, tree: Tree },
1320    Unsupported,
1321}
1322
1323/// The sentinel that gives a function-like macro replacement a parseable
1324/// statement context. The replacement text is copied in verbatim, so the only
1325/// bytes ahead of it are this prefix.
1326const MACRO_BODY_SENTINEL_PREFIX: &str = "void __bifrost_macro_body() { ";
1327
1328/// A function-like macro replacement parsed inside a sentinel function body.
1329///
1330/// Tree-sitter keeps a `#define NAME(a) ...` replacement as one opaque
1331/// `preproc_arg`. Wrapping that exact byte slice in a function body recovers
1332/// its statements, declarations, and member calls as ordinary C++ structure.
1333/// The slice is copied verbatim at [`Self::body_offset`], so a node range in
1334/// [`Self::tree`] maps back onto the defining `preproc_arg` by subtracting
1335/// that offset.
1336pub struct ParsedReplacementBody {
1337    pub source: String,
1338    pub tree: Tree,
1339    pub body_offset: usize,
1340    pub parameters: Vec<String>,
1341    /// For a normal parse this is an identity map. A constrained statement
1342    /// recovery may insert separators after a macro formal; each boundary in
1343    /// this map points back to the corresponding byte in the original
1344    /// replacement, keeping source-backed ranges exact after substitution.
1345    original_offsets: Box<[usize]>,
1346}
1347
1348impl ParsedReplacementBody {
1349    /// The sentinel function body holding the replacement's statements.
1350    pub fn statements(&self) -> Option<Node<'_>> {
1351        first_descendant_of_kind(self.tree.root_node(), "function_definition")?
1352            .child_by_field_name("body")
1353    }
1354
1355    /// The byte range `node` occupies in the file that defines the macro.
1356    ///
1357    /// `replacement_start` is the logical replacement span's start byte.
1358    /// The origin map accounts for continuation whitespace and any synthetic
1359    /// statement separators inserted while parsing the replacement.
1360    pub fn file_range(&self, node: Node<'_>, replacement_start: usize) -> std::ops::Range<usize> {
1361        assert!(
1362            node.start_byte() >= self.body_offset,
1363            "synthetic sentinel node cannot be mapped to a macro replacement"
1364        );
1365        assert!(
1366            node.end_byte() >= self.body_offset,
1367            "synthetic sentinel node cannot be mapped to a macro replacement"
1368        );
1369        let start_offset = node.start_byte() - self.body_offset;
1370        let end_offset = node.end_byte() - self.body_offset;
1371        assert!(start_offset <= end_offset);
1372        let start_origin = *self
1373            .original_offsets
1374            .get(start_offset)
1375            .expect("replacement node start must have a source mapping");
1376        let end_origin = *self
1377            .original_offsets
1378            .get(end_offset)
1379            .expect("replacement node end must have a source mapping");
1380        assert!(start_origin <= end_origin);
1381        let start = replacement_start + start_origin;
1382        let end = replacement_start + end_origin;
1383        start..end
1384    }
1385
1386    /// Whether the replacement names the variadic argument pack.
1387    ///
1388    /// `__VA_ARGS__` parses as an ordinary identifier, so the sentinel tree
1389    /// gives no error for it even though the expansion it stands for is
1390    /// unknown at the definition. Reject it from the parsed tree rather than
1391    /// by scanning the replacement text.
1392    fn expands_variadic_arguments(&self) -> bool {
1393        let mut stack = vec![self.tree.root_node()];
1394        while let Some(node) = stack.pop() {
1395            if matches!(
1396                node.kind(),
1397                "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
1398            ) && node_text(node, &self.source) == "__VA_ARGS__"
1399            {
1400                return true;
1401            }
1402            push_named_children_reversed(node, &mut stack);
1403        }
1404        false
1405    }
1406}
1407
1408/// Parse the value of a C++ integer-literal token when it fits in `i128`.
1409pub fn parse_cpp_integer_literal(text: &str) -> Option<i128> {
1410    let compact = text.chars().filter(|ch| *ch != '\'').collect::<String>();
1411    let (radix, digits_start, digit_matches): (u32, usize, fn(char) -> bool) =
1412        if compact.starts_with("0x") || compact.starts_with("0X") {
1413            (16, 2, |ch| ch.is_ascii_hexdigit())
1414        } else if compact.starts_with("0b") || compact.starts_with("0B") {
1415            (2, 2, |ch| matches!(ch, '0' | '1'))
1416        } else if compact.starts_with('0') && compact.len() > 1 {
1417            (8, 0, |ch| matches!(ch, '0'..='7'))
1418        } else {
1419            (10, 0, |ch| ch.is_ascii_digit())
1420        };
1421    let digit_len = compact[digits_start..]
1422        .chars()
1423        .take_while(|ch| digit_matches(*ch))
1424        .map(char::len_utf8)
1425        .sum::<usize>();
1426    if digit_len == 0 {
1427        return None;
1428    }
1429    let digits_end = digits_start + digit_len;
1430    if !compact[digits_end..]
1431        .chars()
1432        .all(|ch| matches!(ch, 'u' | 'U' | 'l' | 'L' | 'z' | 'Z'))
1433    {
1434        return None;
1435    }
1436    i128::from_str_radix(&compact[digits_start..digits_end], radix).ok()
1437}
1438
1439#[derive(Clone)]
1440enum MacroLocalBindingTypeTemplate {
1441    Parameter(usize),
1442    Fixed(String),
1443}
1444
1445#[derive(Clone)]
1446struct MacroLocalBindingTemplate {
1447    name: String,
1448    declared_type: MacroLocalBindingTypeTemplate,
1449    pointer_depth: i32,
1450}
1451
1452/// A local declaration contributed by one structurally known function-like macro.
1453///
1454/// `type_node` points into the invocation syntax when the replacement's type
1455/// is one of the macro parameters. Consumers can therefore use their normal
1456/// lexical type resolver without parsing replacement text themselves.
1457/// `proven_unit` carries a structured C-tag resolution when the grammar splits
1458/// an explicit `struct` or `union` argument across recovery nodes.
1459pub struct MacroLocalBinding<'tree> {
1460    pub name: String,
1461    pub type_name: String,
1462    pub type_node: Option<Node<'tree>>,
1463    pub pointer_depth: i32,
1464    pub proven_unit: Option<CodeUnit>,
1465}
1466
1467/// The source-backed declaration introduced by a function-like macro. The
1468/// declaration belongs to the macro definition, while the reference range
1469/// returned by [`VisibilityIndex::macro_lexical_references`] belongs to the
1470/// definition body, invocation argument, or caller scope where substitution
1471/// makes the binding visible.
1472#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1473pub enum MacroLexicalBindingKind {
1474    Parameter,
1475    Local,
1476}
1477
1478#[derive(Clone, Debug, PartialEq, Eq)]
1479pub struct MacroLexicalBinding {
1480    pub definition: ProjectFile,
1481    pub kind: MacroLexicalBindingKind,
1482    pub name: String,
1483    pub name_range: std::ops::Range<usize>,
1484    pub declaration_range: std::ops::Range<usize>,
1485}
1486
1487/// Lexical macro references are query-local facts. A cancelled or bounded
1488/// walk is kept distinct from a complete empty enumeration so authoritative
1489/// inverse callers can preserve their completeness status.
1490#[derive(Clone, Debug, Default, PartialEq, Eq)]
1491pub struct MacroLexicalReferences {
1492    pub references: Vec<(std::ops::Range<usize>, MacroLexicalBinding)>,
1493    pub truncated: bool,
1494    pub cancelled: bool,
1495}
1496
1497fn macro_replacement_type_parameters(
1498    body: &ParsedReplacementBody,
1499    parameters: &[String],
1500) -> Option<Vec<usize>> {
1501    let mut found = Vec::new();
1502    let mut stack = vec![body.tree.root_node()];
1503    while let Some(node) = stack.pop() {
1504        let type_position = node.kind() == "type_identifier"
1505            || (node.kind() == "identifier"
1506                && node.parent().is_some_and(|parent| {
1507                    parent.kind() == "type_descriptor"
1508                        && parent.child_by_field_name("type") == Some(node)
1509                }));
1510        let offsetof_type_position = node.kind() == "identifier"
1511            && node
1512                .parent()
1513                .filter(|parent| parent.kind() == "argument_list")
1514                .and_then(|arguments| arguments.parent())
1515                .is_some_and(|call| {
1516                    call.kind() == "call_expression"
1517                        && call
1518                            .child_by_field_name("function")
1519                            .is_some_and(|function| {
1520                                function.kind() == "identifier"
1521                                    && node_text(function, &body.source) == "offsetof"
1522                            })
1523                        && call
1524                            .child_by_field_name("arguments")
1525                            .is_some_and(|arguments| {
1526                                argument_children(arguments).next() == Some(node)
1527                            })
1528                });
1529        if (type_position || offsetof_type_position)
1530            && let Some(index) = parameters
1531                .iter()
1532                .position(|parameter| parameter == node_text(node, &body.source))
1533            && !found.contains(&index)
1534        {
1535            found.push(index);
1536        }
1537        push_named_children_reversed(node, &mut stack);
1538    }
1539    (!found.is_empty()).then_some(found)
1540}
1541
1542fn macro_replacement_type_parameter(
1543    body: &ParsedReplacementBody,
1544    parameters: &[String],
1545) -> Option<usize> {
1546    let mut parameters = macro_replacement_type_parameters(body, parameters)?;
1547    (parameters.len() == 1).then(|| parameters.pop().unwrap())
1548}
1549
1550pub(crate) fn macro_type_argument_node<'tree>(
1551    node: Node<'tree>,
1552    source: &str,
1553) -> Option<Node<'tree>> {
1554    match node.kind() {
1555        "type_descriptor" => {
1556            let type_child = node
1557                .child_by_field_name("type")
1558                .or_else(|| first_type_child(node))?;
1559            for index in (0..node.named_child_count()).rev() {
1560                let child = node.named_child(index)?;
1561                if child != type_child
1562                    && matches!(
1563                        child.kind(),
1564                        "identifier"
1565                            | "type_identifier"
1566                            | "qualified_identifier"
1567                            | "scoped_type_identifier"
1568                    )
1569                {
1570                    return Some(child);
1571                }
1572            }
1573            macro_type_argument_node(type_child, source)
1574        }
1575        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => {
1576            node.child_by_field_name("name")
1577        }
1578        "identifier" if matches!(node_text(node, source), "struct" | "union") => node
1579            .next_named_sibling()
1580            .filter(|sibling| sibling.is_error() && sibling.named_child_count() == 1)
1581            .and_then(|error| error.named_child(0))
1582            .filter(|name| matches!(name.kind(), "identifier" | "type_identifier")),
1583        _ => cpp_name_component_nodes(node).is_some().then_some(node),
1584    }
1585}
1586
1587fn c_function_macro_argument<'tree>(
1588    node: Node<'tree>,
1589) -> Option<(Node<'tree>, usize, Node<'tree>)> {
1590    let mut current = node;
1591    while let Some(parent) = current.parent() {
1592        if parent.kind() == "argument_list" {
1593            let call = parent.parent().filter(|call| {
1594                call.kind() == "call_expression"
1595                    && call.child_by_field_name("arguments") == Some(parent)
1596                    && call
1597                        .child_by_field_name("function")
1598                        .is_some_and(|function| {
1599                            function.kind() == "identifier"
1600                                && !node_range_contains(function, current)
1601                        })
1602            })?;
1603            let mut actuals = argument_children(parent).enumerate();
1604            let (index, argument) = actuals.find(|(_, argument)| {
1605                node_range_contains(*argument, node)
1606                    && (argument.start_byte() == current.start_byte()
1607                        || argument.end_byte() == current.end_byte())
1608            })?;
1609            return Some((call, index, argument));
1610        }
1611        current = parent;
1612    }
1613    None
1614}
1615
1616/// Recover GLib's `g_autoptr(T) name = value` declaration from the CST shape
1617/// produced by tree-sitter-cpp for C source. The grammar retains the macro
1618/// invocation as the assignment's left operand and the declared name as one
1619/// adjacent `ERROR(identifier)` node, so no macro text splitting is needed.
1620fn recognized_c_macro_declarator_binding<'tree>(
1621    statement: Node<'tree>,
1622    source: &str,
1623) -> Option<MacroLocalBinding<'tree>> {
1624    let assignment = match statement.kind() {
1625        "assignment_expression" => statement,
1626        "expression_statement" if statement.named_child_count() == 1 => statement.named_child(0)?,
1627        _ => return None,
1628    };
1629    if assignment.kind() != "assignment_expression" {
1630        return None;
1631    }
1632    let call = assignment.child_by_field_name("left")?;
1633    if call.kind() != "call_expression" {
1634        return None;
1635    }
1636    let function = call.child_by_field_name("function")?;
1637    if function.kind() != "identifier" || node_text(function, source) != "g_autoptr" {
1638        return None;
1639    }
1640    let arguments = call.child_by_field_name("arguments")?;
1641    let mut actuals = argument_children(arguments);
1642    let type_node = actuals.next()?;
1643    if actuals.next().is_some()
1644        || !matches!(
1645            type_node.kind(),
1646            "identifier"
1647                | "type_identifier"
1648                | "qualified_identifier"
1649                | "scoped_type_identifier"
1650                | "template_type"
1651        )
1652    {
1653        return None;
1654    }
1655    let name_node = (0..assignment.named_child_count())
1656        .filter_map(|index| assignment.named_child(index))
1657        .filter(|child| child.kind() == "ERROR")
1658        .filter_map(|error| {
1659            (error.named_child_count() == 1)
1660                .then(|| error.named_child(0))
1661                .flatten()
1662        })
1663        .find(|node| node.kind() == "identifier")?;
1664    let name = node_text(name_node, source).trim();
1665    let type_name = node_text(type_node, source).trim();
1666    if name.is_empty() || type_name.is_empty() {
1667        return None;
1668    }
1669    Some(MacroLocalBinding {
1670        name: name.to_string(),
1671        type_name: type_name.to_string(),
1672        type_node: Some(type_node),
1673        pointer_depth: 1,
1674        proven_unit: None,
1675    })
1676}
1677
1678#[derive(Clone, PartialEq, Eq)]
1679pub struct MacroBinding {
1680    source: ProjectFile,
1681    declaration_byte: usize,
1682    definition: MacroDefinition,
1683    exact: bool,
1684}
1685
1686impl MacroBinding {
1687    fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1688        Self {
1689            source: source.clone(),
1690            declaration_byte,
1691            definition: MacroDefinition::Unsupported,
1692            exact: false,
1693        }
1694    }
1695
1696    fn is_exact(&self) -> bool {
1697        self.exact
1698    }
1699
1700    fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1701        Self {
1702            source: source.clone(),
1703            declaration_byte,
1704            definition: current.definition.clone(),
1705            exact: false,
1706        }
1707    }
1708}
1709
1710/// The preprocessor conditionals whose truth decides whether one macro event
1711/// applies, by the start byte of each conditional node. Empty means the event
1712/// is unconditional.
1713///
1714/// [`VisibilityIndex::macro_event_condition_value`] needs exactly the
1715/// conditional ancestors that structurally contain the event, and deciding
1716/// containment means asking [`cpp_displaced_preprocessor_boundary`] for an
1717/// `#endif` tree-sitter displaced into error recovery -- a walk of the
1718/// conditional's whole subtree. That answer is a fact about the tree alone, so
1719/// it is settled once, when the events are collected, instead of on every
1720/// replay of the event (#1496).
1721type OwningPreprocessorConditionals = Box<[usize]>;
1722
1723#[derive(Clone)]
1724pub enum MacroEvent {
1725    Define {
1726        name: String,
1727        binding: MacroBinding,
1728        byte: usize,
1729        conditionals: OwningPreprocessorConditionals,
1730    },
1731    Undef {
1732        name: String,
1733        byte: usize,
1734        conditionals: OwningPreprocessorConditionals,
1735    },
1736    Include {
1737        targets: Vec<ProjectFile>,
1738        byte: usize,
1739        conditionals: OwningPreprocessorConditionals,
1740    },
1741    Invalidate {
1742        byte: usize,
1743    },
1744}
1745
1746impl MacroEvent {
1747    pub fn byte(&self) -> usize {
1748        match self {
1749            Self::Define { byte, .. }
1750            | Self::Undef { byte, .. }
1751            | Self::Include { byte, .. }
1752            | Self::Invalidate { byte } => *byte,
1753        }
1754    }
1755}
1756
1757#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1758pub enum CallArityEvidence {
1759    Exact(usize),
1760    Unknown,
1761}
1762
1763impl CallArityEvidence {
1764    pub fn exact(self) -> Option<usize> {
1765        match self {
1766            Self::Exact(arity) => Some(arity),
1767            Self::Unknown => None,
1768        }
1769    }
1770
1771    pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1772        self.exact().map(|arity| expected.accepts(arity))
1773    }
1774}
1775
1776struct CppAlias {
1777    name: String,
1778    target: String,
1779    namespace: Option<String>,
1780}
1781
1782type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1783
1784/// Why template-argument resolution failed. Definition diagnostics render
1785/// each mode differently; graph scans only care that the resolution is
1786/// unproven and match `Err(_)`.
1787#[derive(Debug, Clone, PartialEq, Eq)]
1788pub enum CppTemplateResolutionError {
1789    /// A template alias expansion revisited `alias`.
1790    AliasCycle { alias: CodeUnit },
1791    /// The explicit arguments do not bind to the declared template parameters.
1792    ArgumentBinding,
1793    /// Bound arguments do not substitute into the alias target's arguments.
1794    Substitution,
1795    /// No visible primary template declaration could be selected and
1796    /// reconciled for the specialization family.
1797    PrimarySelection,
1798    /// More than one applicable specialization remains and none is strictly
1799    /// more specialized than every other candidate.
1800    AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1801}
1802
1803/// The ambiguity candidates, deduplicated to one representative per visible
1804/// symbol so a diagnostic lists each contender once.
1805fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1806    let mut distinct: Vec<CodeUnit> = Vec::new();
1807    for unit in units {
1808        if !distinct
1809            .iter()
1810            .any(|existing| same_visible_symbol(existing, unit))
1811        {
1812            distinct.push(unit.clone());
1813        }
1814    }
1815    distinct
1816}
1817
1818/// Enumerate macro lexical references while constructing visibility only if
1819/// the bounded source walk finds a definition or identifier call candidate.
1820pub fn macro_lexical_references_with_visibility<'visibility, 'source: 'visibility, Factory>(
1821    visibility: Factory,
1822    file: &ProjectFile,
1823    root: Node<'_>,
1824    source: &str,
1825    max_references: usize,
1826    cancelled: impl FnMut() -> bool,
1827) -> MacroLexicalReferences
1828where
1829    Factory: FnOnce() -> &'visibility VisibilityIndex<'source>,
1830{
1831    crate::graph::macro_lexical::all_references(
1832        visibility,
1833        file,
1834        root,
1835        source,
1836        max_references,
1837        cancelled,
1838    )
1839}
1840
1841impl<'a> VisibilityIndex<'a> {
1842    pub fn cpp(&self) -> &'a dyn CppSource {
1843        self.cpp
1844    }
1845
1846    /// The request-scope proof this index was built with (issue #2414 step 3).
1847    pub fn token(&self) -> QueryToken<'a> {
1848        self.token
1849    }
1850
1851    /// Build the source-backed macro lexical index once per file in this
1852    /// visibility query. The entry cap keeps the query from retaining a
1853    /// workspace-sized collection when a batch visits many translation units.
1854    pub(crate) fn macro_lexical_index(
1855        &self,
1856        file: &ProjectFile,
1857        root: Node<'_>,
1858        source: &str,
1859    ) -> Arc<crate::graph::macro_lexical::MacroLexicalIndex> {
1860        if let Some(index) = self
1861            .macro_lexical_indexes
1862            .lock()
1863            .expect("macro lexical index cache poisoned")
1864            .get(file)
1865            .cloned()
1866        {
1867            debug_assert_eq!(
1868                index.source_len,
1869                source.len(),
1870                "macro lexical index for {file:?} was built from a different source"
1871            );
1872            return index;
1873        }
1874        let index = Arc::new(crate::graph::macro_lexical::build_index(
1875            self, file, root, source,
1876        ));
1877        let mut cache = self
1878            .macro_lexical_indexes
1879            .lock()
1880            .expect("macro lexical index cache poisoned");
1881        if let Some(existing) = cache.get(file).cloned() {
1882            return existing;
1883        }
1884        if cache.len() < MACRO_LEXICAL_INDEX_CACHE_CAP {
1885            cache.insert(file.clone(), Arc::clone(&index));
1886        }
1887        index
1888    }
1889
1890    /// The complete signature rows for one callable, shared by every
1891    /// candidate fact that the visibility query asks for. C++ reconciled
1892    /// members make this read substantially more expensive than an ordinary
1893    /// metadata lookup, so keep the answer on this per-query index and cap it
1894    /// explicitly.
1895    fn callable_metadata(&self, unit: &CodeUnit) -> Arc<Vec<SignatureMetadata>> {
1896        if let Some(metadata) = self
1897            .callable_metadata
1898            .lock()
1899            .expect("C++ callable metadata cache poisoned")
1900            .get(unit)
1901            .cloned()
1902        {
1903            return metadata;
1904        }
1905        let metadata = Arc::new(self.cpp.stored_signature_metadata(unit));
1906        let mut cache = self
1907            .callable_metadata
1908            .lock()
1909            .expect("C++ callable metadata cache poisoned");
1910        if cache.len() < CALLABLE_METADATA_CACHE_CAP {
1911            cache.insert(unit.clone(), Arc::clone(&metadata));
1912        }
1913        metadata
1914    }
1915
1916    /// Callable linkage backed by the query-local metadata memo.
1917    pub fn callable_linkage(&self, unit: &CodeUnit) -> Option<CallableLinkage> {
1918        crate::identity::cpp_callable_linkage_from_metadata(
1919            self.callable_metadata(unit).iter().cloned(),
1920        )
1921    }
1922
1923    /// Callable arity backed by the same query-local metadata memo.
1924    pub fn callable_arity(&self, unit: &CodeUnit) -> Option<CallableArity> {
1925        brokk_bifrost_core::analyzer::model::first_callable_arity(
1926            self.callable_metadata(unit).iter().cloned(),
1927        )
1928    }
1929
1930    /// Callable parameter types backed by the same stored-metadata memo.
1931    /// Parameter spellings are source-owned facts and do not depend on the
1932    /// modifier joins that full `signature_metadata` performs for an
1933    /// out-of-line member.
1934    pub fn callable_parameter_types(&self, unit: &CodeUnit) -> Option<Vec<String>> {
1935        brokk_bifrost_core::analyzer::model::first_callable_parameter_types(
1936            self.callable_metadata(unit).iter().cloned(),
1937        )
1938    }
1939
1940    /// Whether `file` has a structured unresolved include that is active before
1941    /// the reference at `before_byte`.
1942    ///
1943    /// Include facts are collected from the prepared tree once per visibility
1944    /// query. The reference position is still evaluated for each call because
1945    /// preprocessor visibility depends on the reference's own conditional
1946    /// context.
1947    pub fn has_unresolved_include_visible_before(
1948        &self,
1949        file: &ProjectFile,
1950        before_byte: usize,
1951    ) -> bool {
1952        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
1953            return false;
1954        };
1955        let facts = self.structured_include_facts(file, prepared.as_ref());
1956        has_unresolved_include_visible_before_in_prepared(
1957            file,
1958            prepared.as_ref(),
1959            self.cpp.include_target_index(),
1960            &facts,
1961            before_byte,
1962        )
1963    }
1964
1965    fn structured_include_facts(
1966        &self,
1967        file: &ProjectFile,
1968        prepared: &PreparedSyntaxTree,
1969    ) -> Arc<[StructuredIncludeFact]> {
1970        let cell = self
1971            .structured_include_fact_cells
1972            .lock()
1973            .expect("C++ structured include-fact cache poisoned")
1974            .entry(file.clone())
1975            .or_default()
1976            .clone();
1977        Arc::clone(cell.get_or_init(|| collect_structured_include_facts(prepared)))
1978    }
1979
1980    /// A [`VisibilityIndex`] over a caller-supplied visible-declaration map,
1981    /// bypassing the include-closure walk [`Self::build`] performs.
1982    ///
1983    /// The resolver's own unit tests drive the type-resolution paths against a
1984    /// hand-written visibility table; they live in `brokk-bifrost-analysis`
1985    /// because they need a real `CppAnalyzer`, so the struct literal they used
1986    /// to write inline is here instead of thirty-three public fields.
1987    #[cfg(any(test, feature = "test-support"))]
1988    pub fn from_visible_files_for_test(
1989        cpp: &'a dyn CppSource,
1990        token: QueryToken<'a>,
1991        visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1992    ) -> Self {
1993        let visible_source_files_by_root = visible_by_file
1994            .iter()
1995            .map(|(file, visible)| {
1996                (
1997                    file.clone(),
1998                    visible
1999                        .iter()
2000                        .map(|unit| unit.source().clone())
2001                        .chain(std::iter::once(file.clone()))
2002                        .collect(),
2003                )
2004            })
2005            .collect();
2006        let mut global_field_internal_linkage = HashMap::default();
2007        Self {
2008            cpp,
2009            token,
2010            visible_by_identifier: build_visible_identifier_index(
2011                &CppGraphSource::from_source(cpp, token),
2012                &visible_by_file,
2013                &visible_source_files_by_root,
2014                &mut global_field_internal_linkage,
2015            ),
2016            global_field_internal_linkage,
2017            visible_by_file,
2018            visible_source_files_by_root,
2019            alias_cells: Mutex::new(HashMap::default()),
2020            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2021            parser_alias_target_matches: RwLock::new(HashMap::default()),
2022            ordinary_type_import_cells: Mutex::new(HashMap::default()),
2023            project_using_index: OnceLock::new(),
2024            callable_reference_specs: Mutex::new(HashMap::default()),
2025            structured_include_fact_cells: Mutex::new(HashMap::default()),
2026            include_activation_cells: Mutex::new(HashMap::default()),
2027            compile_proven_guard_cells: Mutex::new(HashMap::default()),
2028            include_path_admission_cells: Mutex::new(HashMap::default()),
2029            conditional_include_projection_cells: Mutex::new(HashMap::default()),
2030            conditional_include_projection_index_build_count: AtomicUsize::new(0),
2031            conditional_include_projection_state_count: AtomicUsize::new(0),
2032            conditional_include_target_state_count: AtomicUsize::new(0),
2033            include_activation_build_count: AtomicUsize::new(0),
2034            using_donor_activation_count: AtomicUsize::new(0),
2035            using_namespace_lookup_count: AtomicUsize::new(0),
2036            using_name_candidate_inspection_count: AtomicUsize::new(0),
2037            callable_reference_spec_build_count: AtomicUsize::new(0),
2038            alias_source_parse_counts: Mutex::new(HashMap::default()),
2039            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2040            parser_alias_fallback_calls: AtomicUsize::new(0),
2041            parser_alias_fallback_files: AtomicUsize::new(0),
2042            parser_alias_source_parses: AtomicUsize::new(0),
2043            parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2044            field_type_facts: Mutex::new(HashMap::default()),
2045            structured_alias_targets: Mutex::new(HashMap::default()),
2046            callable_comparables: Mutex::new(HashMap::default()),
2047            callable_metadata: Mutex::new(HashMap::default()),
2048            comparable_name_declarations: Mutex::new(HashMap::default()),
2049            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2050            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2051            precise_parent_cache: Mutex::new(HashMap::default()),
2052            c_tag_kind_cache: Mutex::new(HashMap::default()),
2053            c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2054            macro_event_cells: Mutex::new(HashMap::default()),
2055            macro_event_name_sets: Mutex::new(HashMap::default()),
2056            macro_include_protection_cells: Mutex::new(HashMap::default()),
2057            macro_environment_checkpoints: Mutex::new(HashMap::default()),
2058            macro_replacements: Mutex::new(HashMap::default()),
2059            macro_local_binding_templates: Mutex::new(HashMap::default()),
2060            macro_lexical_templates: Mutex::new(HashMap::default()),
2061            macro_lexical_indexes: Mutex::new(HashMap::default()),
2062            macro_replacement_bodies: Mutex::new(HashMap::default()),
2063            macro_type_parameters: Mutex::new(HashMap::default()),
2064            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2065            macro_replacement_parse_count: AtomicUsize::new(0),
2066            macro_event_application_count: AtomicUsize::new(0),
2067            macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2068            macro_environment_copy_count: AtomicUsize::new(0),
2069            macro_environment_request_count: AtomicUsize::new(0),
2070            cpp_template_metadata: HashMap::default(),
2071            cpp_template_families: HashMap::default(),
2072            qualified_candidate_inspections: AtomicUsize::new(0),
2073            target_preserving_type_resolution_count: AtomicUsize::new(0),
2074            visibility_identifier_lookup_count: 0,
2075            visibility_identifier_batch_count: 0,
2076        }
2077    }
2078
2079    /// The index's own C++ source, in the dispatching-analyzer shape.
2080    ///
2081    /// Four resolution paths reach the workspace through the C++ analyzer they
2082    /// already hold rather than through the analyzer the query was issued
2083    /// against; before the move they passed `&CppAnalyzer` straight into a
2084    /// `&dyn IAnalyzer` parameter. See [`CppGraphSource::from_source`].
2085    fn cpp_source(&self) -> CppGraphSource<'a> {
2086        CppGraphSource::from_source(self.cpp, self.token)
2087    }
2088
2089    pub fn build(
2090        cpp: &'a dyn CppSource,
2091        token: QueryToken<'a>,
2092        analyzer: &CppGraphSource<'_>,
2093        roots: &HashSet<ProjectFile>,
2094    ) -> Self {
2095        Self::build_with_cancellation(cpp, token, analyzer, roots, None)
2096    }
2097
2098    pub fn build_with_cancellation(
2099        cpp: &'a dyn CppSource,
2100        token: QueryToken<'a>,
2101        analyzer: &CppGraphSource<'_>,
2102        roots: &HashSet<ProjectFile>,
2103        cancellation: Option<&CancellationToken>,
2104    ) -> Self {
2105        let visibility_started = Instant::now();
2106        let include_targets = cpp.include_target_index();
2107        let includes_started = Instant::now();
2108        let mut include_graph = IncludeGraph::default();
2109        for root in roots {
2110            include_graph.extend_with(root, cancellation, &mut |file| {
2111                cpp.canonical_include_paths(token, file)
2112                    .unwrap_or_default()
2113                    .into_iter()
2114                    .flat_map(|include| {
2115                        resolve_include_targets_with_index(file, &include, include_targets)
2116                    })
2117                    .collect()
2118            });
2119        }
2120        let include_elapsed = includes_started.elapsed();
2121        let include_file_count = include_graph.files().count();
2122        let visible_source_files_by_root = roots
2123            .iter()
2124            .map(|root| {
2125                (
2126                    root.clone(),
2127                    include_graph.reachable_files(root, cancellation),
2128                )
2129            })
2130            .collect::<HashMap<_, _>>();
2131        let mut visibility_stats = BoundedVisibilityStats::default();
2132        let mut visible_by_file = build_bounded_visible_declarations(
2133            cpp,
2134            token,
2135            analyzer,
2136            roots,
2137            &visible_source_files_by_root,
2138            cancellation,
2139            &mut visibility_stats,
2140        );
2141        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
2142            eprintln!(
2143                "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={}",
2144                visibility_started.elapsed().as_millis(),
2145                include_elapsed.as_millis(),
2146                include_file_count,
2147                visibility_stats.rounds,
2148                visibility_stats.root_names,
2149                visibility_stats.identifier_lookups,
2150                visibility_stats.identifier_batches,
2151                visibility_stats.candidate_units,
2152                visibility_stats.candidate_sources,
2153                visibility_stats.declaration_reads,
2154                visibility_stats.declaration_units,
2155                visibility_stats.selected_units,
2156                visibility_stats.dependency_source_names,
2157                visibility_stats.dependency_names,
2158                visibility_stats.lookup_elapsed.as_millis(),
2159                visibility_stats.declaration_elapsed.as_millis(),
2160                visibility_stats.dependency_source_elapsed.as_millis(),
2161            );
2162        }
2163        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
2164        let finalize_started = Instant::now();
2165        if report_stats {
2166            eprintln!(
2167                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=started roots={} visible_units={}",
2168                visible_by_file.len(),
2169                visible_by_file.values().map(HashSet::len).sum::<usize>(),
2170            );
2171        }
2172        let owner_started = Instant::now();
2173        if report_stats {
2174            eprintln!("BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=started");
2175        }
2176        let owner_stats = extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
2177        if report_stats {
2178            eprintln!(
2179                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=completed unseen_owners={} definition_lookups={} admitted={} elapsed_ms={}",
2180                owner_stats.unseen_owners,
2181                owner_stats.definition_lookups,
2182                owner_stats.admitted,
2183                owner_started.elapsed().as_millis(),
2184            );
2185        }
2186        let mut global_field_internal_linkage = HashMap::default();
2187        let identifier_started = Instant::now();
2188        if report_stats {
2189            eprintln!(
2190                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=started"
2191            );
2192        }
2193        let visible_by_identifier = build_visible_identifier_index(
2194            analyzer,
2195            &visible_by_file,
2196            &visible_source_files_by_root,
2197            &mut global_field_internal_linkage,
2198        );
2199        if report_stats {
2200            eprintln!(
2201                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=completed roots={} names={} candidates={} elapsed_ms={}",
2202                visible_by_identifier.len(),
2203                visible_by_identifier
2204                    .values()
2205                    .map(HashMap::len)
2206                    .sum::<usize>(),
2207                visible_by_identifier
2208                    .values()
2209                    .flat_map(HashMap::values)
2210                    .map(Vec::len)
2211                    .sum::<usize>(),
2212                identifier_started.elapsed().as_millis(),
2213            );
2214        }
2215        let mut cpp_template_metadata = HashMap::default();
2216        let metadata_started = Instant::now();
2217        let mut template_classes = 0usize;
2218        if report_stats {
2219            eprintln!(
2220                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=started"
2221            );
2222        }
2223        for unit in visible_by_file
2224            .values()
2225            .flatten()
2226            .filter(|unit| unit.is_class())
2227        {
2228            template_classes += 1;
2229            if cpp_template_metadata.contains_key(unit) {
2230                continue;
2231            }
2232            if let Some(metadata) = cpp.template_metadata(unit) {
2233                cpp_template_metadata.insert(unit.clone(), metadata);
2234            }
2235        }
2236        if report_stats {
2237            eprintln!(
2238                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=completed classes={} metadata={} elapsed_ms={}",
2239                template_classes,
2240                cpp_template_metadata.len(),
2241                metadata_started.elapsed().as_millis(),
2242            );
2243        }
2244        let families_started = Instant::now();
2245        if report_stats {
2246            eprintln!(
2247                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=started"
2248            );
2249        }
2250        let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
2251        for (unit, metadata) in &cpp_template_metadata {
2252            cpp_template_families
2253                .entry(metadata.primary_fq_name.clone())
2254                .or_default()
2255                .push(unit.clone());
2256        }
2257        // `cpp_template_metadata` is hash-keyed on `CodeUnit`, so the push
2258        // order above is a function of those hashes. Two mirrored headers can
2259        // declare one specialization; `select_template_specialization` treats
2260        // them as interchangeable and returns the family's first entry, so an
2261        // unsorted family made the reported declaration depend on the
2262        // workspace's absolute path and on unrelated files (#1836). Order the
2263        // family exactly as `build_visible_identifier_index` orders its
2264        // per-identifier candidate lists.
2265        for family in cpp_template_families.values_mut() {
2266            sort_lookup_units(family);
2267        }
2268        if report_stats {
2269            eprintln!(
2270                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=completed families={} members={} elapsed_ms={}",
2271                cpp_template_families.len(),
2272                cpp_template_families.values().map(Vec::len).sum::<usize>(),
2273                families_started.elapsed().as_millis(),
2274            );
2275            eprintln!(
2276                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=completed roots={} visible_units={} elapsed_ms={} total_ms={}",
2277                visible_by_file.len(),
2278                visible_by_file.values().map(HashSet::len).sum::<usize>(),
2279                finalize_started.elapsed().as_millis(),
2280                visibility_started.elapsed().as_millis(),
2281            );
2282        }
2283        Self {
2284            cpp,
2285            token,
2286            visible_by_file,
2287            visible_by_identifier,
2288            global_field_internal_linkage,
2289            visible_source_files_by_root,
2290            alias_cells: Mutex::new(HashMap::default()),
2291            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2292            parser_alias_target_matches: RwLock::new(HashMap::default()),
2293            ordinary_type_import_cells: Mutex::new(HashMap::default()),
2294            project_using_index: OnceLock::new(),
2295            callable_reference_specs: Mutex::new(HashMap::default()),
2296            structured_include_fact_cells: Mutex::new(HashMap::default()),
2297            include_activation_cells: Mutex::new(HashMap::default()),
2298            compile_proven_guard_cells: Mutex::new(HashMap::default()),
2299            include_path_admission_cells: Mutex::new(HashMap::default()),
2300            conditional_include_projection_cells: Mutex::new(HashMap::default()),
2301            #[cfg(any(test, feature = "test-support"))]
2302            conditional_include_projection_index_build_count: AtomicUsize::new(0),
2303            #[cfg(any(test, feature = "test-support"))]
2304            conditional_include_projection_state_count: AtomicUsize::new(0),
2305            #[cfg(any(test, feature = "test-support"))]
2306            conditional_include_target_state_count: AtomicUsize::new(0),
2307            #[cfg(any(test, feature = "test-support"))]
2308            include_activation_build_count: AtomicUsize::new(0),
2309            #[cfg(any(test, feature = "test-support"))]
2310            using_donor_activation_count: AtomicUsize::new(0),
2311            #[cfg(any(test, feature = "test-support"))]
2312            using_namespace_lookup_count: AtomicUsize::new(0),
2313            #[cfg(any(test, feature = "test-support"))]
2314            using_name_candidate_inspection_count: AtomicUsize::new(0),
2315            #[cfg(any(test, feature = "test-support"))]
2316            callable_reference_spec_build_count: AtomicUsize::new(0),
2317            #[cfg(any(test, feature = "test-support"))]
2318            alias_source_parse_counts: Mutex::new(HashMap::default()),
2319            #[cfg(any(test, feature = "test-support"))]
2320            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2321            parser_alias_fallback_calls: AtomicUsize::new(0),
2322            parser_alias_fallback_files: AtomicUsize::new(0),
2323            parser_alias_source_parses: AtomicUsize::new(0),
2324            parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2325            field_type_facts: Mutex::new(HashMap::default()),
2326            structured_alias_targets: Mutex::new(HashMap::default()),
2327            callable_comparables: Mutex::new(HashMap::default()),
2328            callable_metadata: Mutex::new(HashMap::default()),
2329            comparable_name_declarations: Mutex::new(HashMap::default()),
2330            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2331            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2332            precise_parent_cache: Mutex::new(HashMap::default()),
2333            c_tag_kind_cache: Mutex::new(HashMap::default()),
2334            c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2335            macro_event_cells: Mutex::new(HashMap::default()),
2336            macro_event_name_sets: Mutex::new(HashMap::default()),
2337            macro_include_protection_cells: Mutex::new(HashMap::default()),
2338            macro_environment_checkpoints: Mutex::new(HashMap::default()),
2339            macro_replacements: Mutex::new(HashMap::default()),
2340            macro_local_binding_templates: Mutex::new(HashMap::default()),
2341            macro_lexical_templates: Mutex::new(HashMap::default()),
2342            macro_lexical_indexes: Mutex::new(HashMap::default()),
2343            macro_replacement_bodies: Mutex::new(HashMap::default()),
2344            macro_type_parameters: Mutex::new(HashMap::default()),
2345            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2346            #[cfg(any(test, feature = "test-support"))]
2347            macro_replacement_parse_count: AtomicUsize::new(0),
2348            #[cfg(any(test, feature = "test-support"))]
2349            macro_event_application_count: AtomicUsize::new(0),
2350            #[cfg(any(test, feature = "test-support"))]
2351            macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2352            #[cfg(any(test, feature = "test-support"))]
2353            macro_environment_copy_count: AtomicUsize::new(0),
2354            #[cfg(any(test, feature = "test-support"))]
2355            macro_environment_request_count: AtomicUsize::new(0),
2356            cpp_template_metadata,
2357            cpp_template_families,
2358            #[cfg(any(test, feature = "test-support"))]
2359            qualified_candidate_inspections: AtomicUsize::new(0),
2360            #[cfg(any(test, feature = "test-support"))]
2361            target_preserving_type_resolution_count: AtomicUsize::new(0),
2362            #[cfg(any(test, feature = "test-support"))]
2363            visibility_identifier_lookup_count: visibility_stats.identifier_lookups,
2364            #[cfg(any(test, feature = "test-support"))]
2365            visibility_identifier_batch_count: visibility_stats.identifier_batches,
2366        }
2367    }
2368
2369    pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2370        if file == target.source() {
2371            return true;
2372        }
2373        if self.global_field_has_internal_linkage(target) {
2374            return self
2375                .visible_source_files_by_root
2376                .get(file)
2377                .is_some_and(|sources| sources.contains(target.source()));
2378        }
2379        self.visible_by_file
2380            .get(file)
2381            .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
2382    }
2383
2384    fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
2385        self.global_field_internal_linkage
2386            .get(unit)
2387            .copied()
2388            .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
2389    }
2390
2391    pub fn call_arity_evidence(
2392        &self,
2393        file: &ProjectFile,
2394        call: Node<'_>,
2395        source: &str,
2396    ) -> CallArityEvidence {
2397        self.call_arity_evidence_at(file, call, source, call.start_byte())
2398    }
2399
2400    /// Argument-count evidence for a call whose macro environment is not the
2401    /// one at its own byte offset.
2402    ///
2403    /// A call recovered from a macro replacement lives in a sentinel parse of
2404    /// its own, so its node offsets say nothing about which macros are active.
2405    /// `environment_byte` names the position in `file` whose macro environment
2406    /// governs the call: the macro definition site for a replacement body.
2407    pub fn call_arity_evidence_at(
2408        &self,
2409        file: &ProjectFile,
2410        call: Node<'_>,
2411        source: &str,
2412        environment_byte: usize,
2413    ) -> CallArityEvidence {
2414        let Some(arguments) = call
2415            .child_by_field_name("arguments")
2416            .or_else(|| call.child_by_field_name("parameters"))
2417            .or_else(|| call.child_by_field_name("value"))
2418            .or_else(|| first_named_child_of_kind(call, "argument_list"))
2419            .or_else(|| first_named_child_of_kind(call, "initializer_list"))
2420        else {
2421            return CallArityEvidence::Exact(0);
2422        };
2423        let recovered_c_keyword_arguments =
2424            recovered_c_keyword_argument_count(file, call, arguments, source);
2425        let c_semantics = reference_uses_c_semantics(self.cpp, file);
2426        let arguments = argument_children(arguments)
2427            .flat_map(|argument| {
2428                recovered_c_new_expression_arguments(argument, c_semantics)
2429                    .map(Vec::from)
2430                    .unwrap_or_else(|| vec![argument])
2431            })
2432            .collect::<Vec<_>>();
2433        if arguments
2434            .iter()
2435            .all(|argument| !argument_shape_may_change_arity(*argument))
2436        {
2437            return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
2438        }
2439        let environment = self.macro_environment(file, environment_byte);
2440        let mut stack = Vec::new();
2441        let mut total = recovered_c_keyword_arguments;
2442        for argument in arguments {
2443            if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
2444                return CallArityEvidence::Unknown;
2445            }
2446            let CallArityEvidence::Exact(spread) =
2447                self.argument_arity_evidence(argument, source, &environment, &mut stack)
2448            else {
2449                return CallArityEvidence::Unknown;
2450            };
2451            total += spread;
2452        }
2453        CallArityEvidence::Exact(total)
2454    }
2455
2456    fn argument_arity_evidence(
2457        &self,
2458        argument: Node<'_>,
2459        source: &str,
2460        environment: &MacroEnvironment,
2461        stack: &mut Vec<(ProjectFile, usize)>,
2462    ) -> CallArityEvidence {
2463        let (name, invocation_arguments, function_like) = match argument.kind() {
2464            "identifier" => (node_text(argument, source), None, false),
2465            "call_expression" => {
2466                let Some(function) = argument.child_by_field_name("function") else {
2467                    return CallArityEvidence::Exact(1);
2468                };
2469                if function.kind() != "identifier" {
2470                    return CallArityEvidence::Exact(1);
2471                }
2472                let Some(arguments) = argument.child_by_field_name("arguments") else {
2473                    return CallArityEvidence::Exact(1);
2474                };
2475                (node_text(function, source), Some(arguments), true)
2476            }
2477            _ => return CallArityEvidence::Exact(1),
2478        };
2479        let Some(binding) = environment.binding(name) else {
2480            return if environment.unknown_names {
2481                CallArityEvidence::Unknown
2482            } else {
2483                CallArityEvidence::Exact(1)
2484            };
2485        };
2486        if !binding.is_exact() {
2487            return CallArityEvidence::Unknown;
2488        }
2489        match (&binding.definition, invocation_arguments, function_like) {
2490            (MacroDefinition::Object { replacement }, None, false) => self
2491                .replacement_arity_evidence(
2492                    replacement,
2493                    &[],
2494                    &[],
2495                    source,
2496                    environment,
2497                    stack,
2498                    binding,
2499                ),
2500            (
2501                MacroDefinition::Function {
2502                    parameters,
2503                    replacement,
2504                },
2505                Some(arguments),
2506                true,
2507            ) => {
2508                let actuals = argument_children(arguments).collect::<Vec<_>>();
2509                if actuals.len() != parameters.len() {
2510                    CallArityEvidence::Unknown
2511                } else {
2512                    self.replacement_arity_evidence(
2513                        replacement,
2514                        parameters,
2515                        &actuals,
2516                        source,
2517                        environment,
2518                        stack,
2519                        binding,
2520                    )
2521                }
2522            }
2523            (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
2524            _ => CallArityEvidence::Unknown,
2525        }
2526    }
2527
2528    #[allow(clippy::too_many_arguments)]
2529    fn replacement_arity_evidence(
2530        &self,
2531        replacement: &str,
2532        parameters: &[String],
2533        actuals: &[Node<'_>],
2534        actual_source: &str,
2535        environment: &MacroEnvironment,
2536        stack: &mut Vec<(ProjectFile, usize)>,
2537        binding: &MacroBinding,
2538    ) -> CallArityEvidence {
2539        let identity = (binding.source.clone(), binding.declaration_byte);
2540        if stack.contains(&identity) || replacement.trim().is_empty() {
2541            return CallArityEvidence::Unknown;
2542        }
2543        stack.push(identity);
2544        let parsed = self.parsed_macro_replacement(binding, replacement);
2545        let evidence = (|| {
2546            let ParsedMacroReplacement::Parsed {
2547                source: sentinel,
2548                tree,
2549            } = parsed.as_ref()
2550            else {
2551                return None;
2552            };
2553            let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
2554            let arguments = call.child_by_field_name("arguments")?;
2555            let mut total = 0usize;
2556            for argument in argument_children(arguments) {
2557                if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
2558                    return None;
2559                }
2560                if argument.kind() == "identifier"
2561                    && let Some(parameter_index) = parameters
2562                        .iter()
2563                        .position(|parameter| parameter == node_text(argument, sentinel))
2564                {
2565                    if !macro_expansion_shape_is_safe(
2566                        actuals[parameter_index],
2567                        actual_source,
2568                        &[],
2569                        environment,
2570                    ) {
2571                        return None;
2572                    }
2573                    let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
2574                        actuals[parameter_index],
2575                        actual_source,
2576                        environment,
2577                        stack,
2578                    ) else {
2579                        return None;
2580                    };
2581                    total += spread;
2582                    continue;
2583                }
2584                let CallArityEvidence::Exact(spread) =
2585                    self.argument_arity_evidence(argument, sentinel, environment, stack)
2586                else {
2587                    return None;
2588                };
2589                total += spread;
2590            }
2591            Some(CallArityEvidence::Exact(total))
2592        })()
2593        .unwrap_or(CallArityEvidence::Unknown);
2594        stack.pop();
2595        evidence
2596    }
2597
2598    fn parsed_macro_replacement(
2599        &self,
2600        binding: &MacroBinding,
2601        replacement: &str,
2602    ) -> Arc<ParsedMacroReplacement> {
2603        let key = (binding.source.clone(), binding.declaration_byte);
2604        let mut cache = self
2605            .macro_replacements
2606            .lock()
2607            .expect("C++ macro replacement cache poisoned");
2608        if let Some(parsed) = cache.get(&key) {
2609            return Arc::clone(parsed);
2610        }
2611        #[cfg(any(test, feature = "test-support"))]
2612        self.macro_replacement_parse_count
2613            .fetch_add(1, Ordering::Relaxed);
2614        let source =
2615            format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
2616        let mut parser = Parser::new();
2617        let parsed = parser
2618            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2619            .ok()
2620            .and_then(|()| parser.parse(&source, None))
2621            .filter(|tree| !tree.root_node().has_error())
2622            .map_or(ParsedMacroReplacement::Unsupported, |tree| {
2623                ParsedMacroReplacement::Parsed { source, tree }
2624            });
2625        let parsed = Arc::new(parsed);
2626        cache.insert(key, Arc::clone(&parsed));
2627        parsed
2628    }
2629
2630    /// Recover a typed local declared by an active C function-like macro.
2631    ///
2632    /// This is intentionally narrower than macro expansion. The replacement
2633    /// must parse as one declaration, and the invocation must bind every
2634    /// formal parameter to one structured argument. That is sufficient for
2635    /// declaration macros such as `THIS(StorageAzure)`. An unavailable include
2636    /// can make the binding provisional without erasing its last known
2637    /// definition; an explicit conflicting definition still replaces it with
2638    /// Unsupported. Malformed and statement-producing macros also fail closed.
2639    pub fn function_macro_local_binding<'tree>(
2640        &self,
2641        file: &ProjectFile,
2642        statement: Node<'tree>,
2643        source: &str,
2644    ) -> Option<MacroLocalBinding<'tree>> {
2645        if !is_c_source_file(file) {
2646            return None;
2647        }
2648        if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
2649            return Some(binding);
2650        }
2651        let call = match statement.kind() {
2652            "call_expression" => statement,
2653            "expression_statement" if statement.named_child_count() == 1 => {
2654                statement.named_child(0)?
2655            }
2656            _ => return None,
2657        };
2658        if call.kind() != "call_expression" {
2659            return None;
2660        }
2661        let function = call.child_by_field_name("function")?;
2662        if function.kind() != "identifier" {
2663            return None;
2664        }
2665        let arguments = call.child_by_field_name("arguments")?;
2666        let actuals = argument_children(arguments).collect::<Vec<_>>();
2667        let environment = self.macro_environment(file, call.start_byte());
2668        let function_name = node_text(function, source);
2669        let binding = environment.binding(function_name)?;
2670        let MacroDefinition::Function {
2671            parameters,
2672            replacement,
2673        } = &binding.definition
2674        else {
2675            return None;
2676        };
2677        if actuals.len() != parameters.len() {
2678            return None;
2679        }
2680        let template = self.macro_local_binding_template(binding, parameters, replacement)?;
2681        let (type_name, type_node) = match &template.declared_type {
2682            MacroLocalBindingTypeTemplate::Parameter(index) => {
2683                let actual = *actuals.get(*index)?;
2684                if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
2685                    return None;
2686                }
2687                (node_text(actual, source).trim().to_string(), Some(actual))
2688            }
2689            MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
2690        };
2691        if type_name.is_empty() {
2692            return None;
2693        }
2694        Some(MacroLocalBinding {
2695            name: template.name.clone(),
2696            type_name,
2697            type_node,
2698            pointer_depth: template.pointer_depth,
2699            proven_unit: None,
2700        })
2701    }
2702
2703    /// Recover the typed receiver established by a C container macro
2704    /// assignment, such as `value = container_of(ptr, struct item, link)`.
2705    ///
2706    /// The macro definition's parsed replacement identifies the one formal
2707    /// parameter used in type position.  The invocation supplies the actual
2708    /// type node, which is then resolved through the ordinary visibility
2709    /// index.  Calls with no unique type-position parameter, an unresolved
2710    /// type, or an uncertain macro environment remain unproven.
2711    pub fn function_macro_container_binding<'tree>(
2712        &self,
2713        analyzer: &CppGraphSource<'_>,
2714        file: &ProjectFile,
2715        assignment: Node<'tree>,
2716        source: &str,
2717    ) -> Option<MacroLocalBinding<'tree>> {
2718        if !is_c_source_file(file) || assignment.kind() != "assignment_expression" {
2719            return None;
2720        }
2721        let name_node = assignment.child_by_field_name("left")?;
2722        if name_node.kind() != "identifier" {
2723            return None;
2724        }
2725        let call = assignment.child_by_field_name("right")?;
2726        if call.kind() != "call_expression" {
2727            return None;
2728        }
2729        let function = call.child_by_field_name("function")?;
2730        if function.kind() != "identifier" {
2731            return None;
2732        }
2733        let arguments = call.child_by_field_name("arguments")?;
2734        let actuals = argument_children(arguments).collect::<Vec<_>>();
2735        let function_name = node_text(function, source);
2736        let environment = self.macro_environment(file, call.start_byte());
2737        let binding = environment.binding(function_name)?;
2738        if !binding.is_exact() {
2739            return None;
2740        }
2741        let MacroDefinition::Function {
2742            parameters,
2743            replacement,
2744        } = &binding.definition
2745        else {
2746            return None;
2747        };
2748        if actuals.len() != parameters.len() {
2749            return None;
2750        }
2751        let body = self.parsed_macro_replacement_body(
2752            &(binding.source.clone(), binding.declaration_byte),
2753            parameters,
2754            replacement,
2755        )?;
2756        let type_parameter = macro_replacement_type_parameter(&body, parameters)?;
2757        let type_argument = *actuals.get(type_parameter)?;
2758        let type_node = macro_type_argument_node(type_argument, source)?;
2759        let type_name = node_text(type_node, source).trim().to_string();
2760        if type_name.is_empty() {
2761            return None;
2762        }
2763        let explicit_tag = match node_text(type_argument, source) {
2764            "struct" => Some(CppCTagKind::Struct),
2765            "union" => Some(CppCTagKind::Union),
2766            _ => None,
2767        };
2768        let resolved_type = if let Some(tag) = explicit_tag {
2769            let candidates = self
2770                .visible_identifier_candidates(file, &type_name)
2771                .filter(|candidate| self.cached_c_tag_kind(analyzer, candidate) == Some(tag))
2772                .collect::<Vec<_>>();
2773            self.resolve_type_candidates(
2774                analyzer,
2775                file,
2776                &candidates,
2777                TypeCandidateResolution::Canonical,
2778            )
2779            .ok()
2780        } else {
2781            self.resolve_type_node_result(file, type_node, source)
2782                .ok()
2783                .flatten()
2784        };
2785        let proven_unit = resolved_type?;
2786        Some(MacroLocalBinding {
2787            name: node_text(name_node, source).to_string(),
2788            type_name,
2789            type_node: Some(type_node),
2790            pointer_depth: 1,
2791            proven_unit: Some(proven_unit),
2792        })
2793    }
2794
2795    /// Recover the invocation-specific type of a replacement local visible
2796    /// at the selected source token.
2797    pub fn macro_local_binding_at<'tree>(
2798        &self,
2799        file: &ProjectFile,
2800        root: Node<'tree>,
2801        source: &str,
2802        start_byte: usize,
2803        end_byte: usize,
2804    ) -> Option<MacroLocalBinding<'tree>> {
2805        crate::graph::macro_lexical::typed_binding(self, file, root, source, start_byte, end_byte)
2806    }
2807
2808    /// Resolve the source-backed lexical macro declaration for one source
2809    /// range. The range may be a point range (`start..start + 1`) inside an
2810    /// identifier, which is the form used by goto-definition queries.
2811    pub fn macro_lexical_binding(
2812        &self,
2813        file: &ProjectFile,
2814        root: Node<'_>,
2815        source: &str,
2816        start_byte: usize,
2817        end_byte: usize,
2818    ) -> Option<MacroLexicalBinding> {
2819        crate::graph::macro_lexical::binding(self, file, root, source, start_byte, end_byte)
2820    }
2821
2822    /// Enumerate source ranges whose lexical meaning comes from a
2823    /// function-like macro formal or replacement local. Definition spellings
2824    /// themselves are omitted; callers needing goto-definition on a spelling
2825    /// use [`Self::macro_lexical_binding`].
2826    pub fn macro_lexical_references(
2827        &self,
2828        file: &ProjectFile,
2829        root: Node<'_>,
2830        source: &str,
2831        max_references: usize,
2832        cancelled: impl FnMut() -> bool,
2833    ) -> MacroLexicalReferences {
2834        crate::graph::macro_lexical::all_references(
2835            || self,
2836            file,
2837            root,
2838            source,
2839            max_references,
2840            cancelled,
2841        )
2842    }
2843
2844    /// Return the source identity of the exact active function-like macro
2845    /// binding. The definition AST is hydrated by the lexical helper from
2846    /// this `(ProjectFile, declaration byte)` pair, so no parser node escapes
2847    /// the prepared source borrow.
2848    pub(crate) fn function_macro_binding_at(
2849        &self,
2850        file: &ProjectFile,
2851        name: &str,
2852        before_byte: usize,
2853    ) -> Option<(ProjectFile, usize)> {
2854        let environment = self.macro_environment(file, before_byte);
2855        let binding = environment.binding(name)?;
2856        if binding.is_exact()
2857            && matches!(
2858                binding.definition,
2859                MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2860            )
2861        {
2862            return Some((binding.source.clone(), binding.declaration_byte));
2863        }
2864        // A definition under an unknown condition is still certain at a use
2865        // within that same branch. The checkpoint retains the event that made
2866        // the binding uncertain. Require that exact defining event, so an
2867        // intervening undef, include, or conditional redefinition cannot be
2868        // bypassed by a lexical search for an older macro.
2869        if binding.source != *file {
2870            return None;
2871        }
2872        let prepared = self.cpp.prepared_syntax(self.token, file)?;
2873        let root = prepared.tree().root_node();
2874        let source = prepared.source();
2875        let reference = root.descendant_for_byte_range(
2876            before_byte,
2877            before_byte.saturating_add(1).min(source.len()),
2878        )?;
2879        let reference_conditions = owning_preprocessor_conditionals(root, reference, source);
2880        let cell = self.macro_event_cell(file);
2881        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2882        let event = events
2883            .iter()
2884            .find(|event| event.byte() == binding.declaration_byte)?;
2885        let MacroEvent::Define {
2886            name: defined_name,
2887            binding: definition,
2888            conditionals,
2889            ..
2890        } = event
2891        else {
2892            return None;
2893        };
2894        if defined_name != name
2895            || conditionals.is_empty()
2896            || !conditionals
2897                .iter()
2898                .all(|condition| reference_conditions.contains(condition))
2899            || !matches!(
2900                definition.definition,
2901                MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2902            )
2903        {
2904            return None;
2905        }
2906        Some((definition.source.clone(), definition.declaration_byte))
2907    }
2908
2909    /// Return the type argument at a C function-like macro invocation.
2910    ///
2911    /// Exact local macro definitions identify type formals from their parsed
2912    /// replacement. An unavailable definition is deliberately not inferred:
2913    /// C has no AST distinction between an external macro call and an
2914    /// ordinary call to an unindexed function.
2915    pub fn function_macro_type_argument<'tree>(
2916        &self,
2917        file: &ProjectFile,
2918        node: Node<'tree>,
2919        source: &str,
2920    ) -> Option<Node<'tree>> {
2921        if !is_c_source_file(file) {
2922            return None;
2923        }
2924        let (call, argument_index, argument) = c_function_macro_argument(node)?;
2925        let function = call.child_by_field_name("function")?;
2926        let function_name = node_text(function, source);
2927        if function_name.is_empty() {
2928            return None;
2929        }
2930        if !self.file_defines_macro_name(file, function_name)
2931            && !self
2932                .visible_identifier_candidates(file, function_name)
2933                .any(|candidate| candidate.is_macro())
2934        {
2935            return None;
2936        }
2937        let environment = self.macro_environment(file, call.start_byte());
2938        let binding = environment.binding(function_name)?;
2939        if !binding.is_exact() {
2940            return None;
2941        }
2942        let (parameters, replacement, variadic) = match &binding.definition {
2943            MacroDefinition::Function {
2944                parameters,
2945                replacement,
2946            } => (parameters, replacement, false),
2947            MacroDefinition::VariadicFunction {
2948                parameters,
2949                replacement,
2950            } => (parameters, replacement, true),
2951            MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
2952        };
2953        let actuals = call
2954            .child_by_field_name("arguments")
2955            .map(argument_children)
2956            .into_iter()
2957            .flatten()
2958            .collect::<Vec<_>>();
2959        let arity_matches = if variadic {
2960            actuals.len() >= parameters.len()
2961        } else {
2962            actuals.len() == parameters.len()
2963        };
2964        let type_parameters = self.macro_type_parameter_indices(
2965            &(binding.source.clone(), binding.declaration_byte),
2966            parameters,
2967            replacement,
2968        )?;
2969        if !arity_matches || !type_parameters.contains(&argument_index) {
2970            return None;
2971        }
2972        let type_node = macro_type_argument_node(argument, source)?;
2973        if self.names_a_macro_at(file, node_text(type_node, source), type_node.start_byte()) {
2974            return None;
2975        }
2976        Some(type_node)
2977    }
2978
2979    fn macro_local_binding_template(
2980        &self,
2981        binding: &MacroBinding,
2982        parameters: &[String],
2983        replacement: &str,
2984    ) -> Option<Arc<MacroLocalBindingTemplate>> {
2985        let key = (binding.source.clone(), binding.declaration_byte);
2986        if let Some(template) = self
2987            .macro_local_binding_templates
2988            .lock()
2989            .expect("C++ macro local-binding cache poisoned")
2990            .get(&key)
2991        {
2992            return template.clone();
2993        }
2994        let template = (|| {
2995            let body = self.parsed_macro_replacement_body(&key, parameters, replacement)?;
2996            let sentinel = body.source.as_str();
2997            let statements = body.statements()?;
2998            if statements.named_child_count() != 1 {
2999                return None;
3000            }
3001            let declaration = statements.named_child(0)?;
3002            if declaration.kind() != "declaration" {
3003                return None;
3004            }
3005            let type_node = declaration
3006                .child_by_field_name("type")
3007                .or_else(|| first_type_child(declaration))?;
3008            let declarator = declaration.child_by_field_name("declarator").or_else(|| {
3009                let mut cursor = declaration.walk();
3010                declaration.named_children(&mut cursor).find_map(|child| {
3011                    if child.kind() == "init_declarator" {
3012                        child.child_by_field_name("declarator")
3013                    } else {
3014                        is_declarator_node(child).then_some(child)
3015                    }
3016                })
3017            })?;
3018            let name = extract_variable_name(declarator, sentinel)?;
3019            let pointer_depth =
3020                declared_name_binding(declaration, type_node, &name, sentinel)?.pointer_depth;
3021            let type_text = node_text(type_node, sentinel).trim();
3022            let declared_type = parameters
3023                .iter()
3024                .position(|parameter| parameter == type_text)
3025                .map(MacroLocalBindingTypeTemplate::Parameter)
3026                .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
3027            Some(Arc::new(MacroLocalBindingTemplate {
3028                name,
3029                declared_type,
3030                pointer_depth,
3031            }))
3032        })();
3033        self.macro_local_binding_templates
3034            .lock()
3035            .expect("C++ macro local-binding cache poisoned")
3036            .insert(key, template.clone());
3037        template
3038    }
3039
3040    /// The parsed replacement body of the function-like macro `definition`
3041    /// defines, or `None` when the replacement cannot be recovered exactly.
3042    ///
3043    /// `definition` is the defining `preproc_function_def` node in `file`, so
3044    /// the result describes that definition rather than whichever same-named
3045    /// macro a later reference resolves to.
3046    pub fn function_macro_replacement_body(
3047        &self,
3048        file: &ProjectFile,
3049        definition: Node<'_>,
3050        source: &str,
3051    ) -> Option<Arc<ParsedReplacementBody>> {
3052        debug_assert_eq!(definition.kind(), "preproc_function_def");
3053        let (parameters, replacement) = match Self::decode_macro_definition(definition, source) {
3054            MacroDefinition::Function {
3055                parameters,
3056                replacement,
3057            }
3058            | MacroDefinition::VariadicFunction {
3059                parameters,
3060                replacement,
3061            } => (parameters, replacement),
3062            MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
3063        };
3064        self.parsed_macro_replacement_body(
3065            &(file.clone(), definition.start_byte()),
3066            &parameters,
3067            &replacement,
3068        )
3069    }
3070
3071    /// Parse one function-like macro replacement inside the shared sentinel.
3072    ///
3073    /// The parse fails closed, and the failure is cached, whenever the
3074    /// sentinel tree carries an error or the replacement uses preprocessor
3075    /// syntax that has no C++ meaning. Token pasting and stringizing produce
3076    /// `ERROR` nodes; `__VA_ARGS__` parses as an ordinary identifier and is
3077    /// therefore rejected from the parsed tree instead of the source text.
3078    fn parsed_macro_replacement_body(
3079        &self,
3080        key: &(ProjectFile, usize),
3081        parameters: &[String],
3082        replacement: &str,
3083    ) -> Option<Arc<ParsedReplacementBody>> {
3084        if let Some(body) = self
3085            .macro_replacement_bodies
3086            .lock()
3087            .expect("C++ macro replacement body cache poisoned")
3088            .get(key)
3089        {
3090            return body.clone();
3091        }
3092        let body = (|| {
3093            if replacement.trim().is_empty() {
3094                return None;
3095            }
3096            let (source, tree, original_offsets) =
3097                Self::parse_macro_replacement_body(replacement, parameters)?;
3098            let body = ParsedReplacementBody {
3099                source,
3100                tree,
3101                body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3102                parameters: parameters.to_vec(),
3103                original_offsets,
3104            };
3105            body.statements()?;
3106            if body.expands_variadic_arguments() {
3107                return None;
3108            }
3109            Some(Arc::new(body))
3110        })();
3111        self.macro_replacement_bodies
3112            .lock()
3113            .expect("C++ macro replacement body cache poisoned")
3114            .insert(key.clone(), body.clone());
3115        body
3116    }
3117
3118    /// Parse a replacement while preserving a map from the parse buffer back
3119    /// to the original logical-line bytes. A block formal can occur where C
3120    /// requires a statement terminator (`if (condition) block`); the caller's
3121    /// actual block supplies that terminator only after macro substitution.
3122    /// Tree-sitter exposes the formal as an identifier inside recovery, so a
3123    /// semicolon is inserted at that AST-derived boundary for the fallback
3124    /// parse. No identifier text is scanned to find the insertion point.
3125    fn parse_macro_replacement_body(
3126        replacement: &str,
3127        parameters: &[String],
3128    ) -> Option<(String, Tree, Box<[usize]>)> {
3129        let normalized = normalize_macro_continuations(replacement);
3130        let parse = |replacement: &str| {
3131            let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
3132            let mut parser = Parser::new();
3133            parser
3134                .set_language(&tree_sitter_cpp::LANGUAGE.into())
3135                .ok()?;
3136            let tree = parser.parse(&source, None)?;
3137            Some((source, tree))
3138        };
3139        let (source, tree) = parse(&normalized)?;
3140        if !tree.root_node().has_error() {
3141            let mut original_offsets = (0..=normalized.len()).collect::<Vec<_>>();
3142            Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
3143            return Some((source, tree, original_offsets.into_boxed_slice()));
3144        }
3145
3146        let body_offset = MACRO_BODY_SENTINEL_PREFIX.len();
3147        let mut insertion_points = Vec::new();
3148        let mut stack = vec![tree.root_node()];
3149        while let Some(node) = stack.pop() {
3150            if matches!(
3151                node.kind(),
3152                "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
3153            ) && parameters
3154                .iter()
3155                .any(|parameter| parameter.as_str() == node_text(node, &source))
3156                && Self::macro_formal_needs_statement_separator(node)
3157            {
3158                let point = node.end_byte().saturating_sub(body_offset);
3159                if point <= normalized.len() && !insertion_points.contains(&point) {
3160                    insertion_points.push(point);
3161                }
3162            }
3163            push_named_children_reversed(node, &mut stack);
3164        }
3165        if insertion_points.is_empty() {
3166            return None;
3167        }
3168        insertion_points.sort_unstable();
3169        let mut recovered = Vec::with_capacity(normalized.len() + insertion_points.len());
3170        let mut original_offsets =
3171            Vec::with_capacity(normalized.len() + insertion_points.len() + 1);
3172        let mut next_insertion = 0;
3173        for (index, byte) in normalized.bytes().enumerate() {
3174            recovered.push(byte);
3175            original_offsets.push(index);
3176            while insertion_points.get(next_insertion).copied() == Some(index + 1) {
3177                recovered.push(b';');
3178                original_offsets.push(index + 1);
3179                next_insertion += 1;
3180            }
3181        }
3182        original_offsets.push(normalized.len());
3183        Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
3184        let recovered = String::from_utf8(recovered).expect("source text remains UTF-8");
3185        let (source, tree) = parse(&recovered)?;
3186        if tree.root_node().has_error() {
3187            return None;
3188        }
3189        Some((source, tree, original_offsets.into_boxed_slice()))
3190    }
3191
3192    /// Extend a replacement-origin map over the `; }` suffix appended by the
3193    /// sentinel parser. Nodes such as a declaration whose source omits its
3194    /// terminator include that synthetic semicolon; every suffix boundary
3195    /// still maps to the replacement's real end.
3196    fn append_sentinel_offsets(offsets: &mut Vec<usize>, replacement_end: usize) {
3197        offsets.extend([replacement_end; 3]);
3198    }
3199
3200    /// A formal is statement-shaped when the recovered tree places it directly
3201    /// at a statement boundary. This is the grammar recovery produced for
3202    /// replacement forms such as `if (condition) block`; an identifier inside
3203    /// an expression or call argument must not receive an artificial `;`.
3204    fn macro_formal_needs_statement_separator(node: Node<'_>) -> bool {
3205        let mut current = node;
3206        let mut crossed_recovery = false;
3207        while let Some(parent) = current.parent() {
3208            if parent.is_error() {
3209                crossed_recovery = true;
3210                current = parent;
3211                continue;
3212            }
3213            if matches!(
3214                parent.kind(),
3215                "call_expression"
3216                    | "argument_list"
3217                    | "field_expression"
3218                    | "binary_expression"
3219                    | "unary_expression"
3220                    | "assignment_expression"
3221                    | "conditional_expression"
3222                    | "parenthesized_expression"
3223                    | "subscript_expression"
3224            ) {
3225                return false;
3226            }
3227            if parent.kind() == "expression_statement" {
3228                return parent.named_child_count() == 1;
3229            }
3230            if parent.kind() == "compound_statement" && current == node {
3231                return true;
3232            }
3233            if matches!(
3234                parent.kind(),
3235                "compound_statement" | "if_statement" | "while_statement" | "do_statement"
3236            ) {
3237                return crossed_recovery
3238                    || parent.child_by_field_name("consequence") == Some(current);
3239            }
3240            if matches!(parent.kind(), "declaration" | "init_declarator") {
3241                return false;
3242            }
3243            current = parent;
3244        }
3245        false
3246    }
3247
3248    /// Identify fixed macro formals that retain an unambiguous type role even
3249    /// when an unrelated part of the replacement needs parser recovery.
3250    ///
3251    /// Variadic production macros commonly contain token pasting elsewhere in
3252    /// the body. The strict shared replacement parser must keep rejecting that
3253    /// body for expansion modeling, but a type declaration such as `TY col`
3254    /// remains usable when the node itself retains a structured type role.
3255    fn macro_type_parameter_indices(
3256        &self,
3257        key: &(ProjectFile, usize),
3258        parameters: &[String],
3259        replacement: &str,
3260    ) -> Option<Arc<[usize]>> {
3261        if let Some(indices) = self
3262            .macro_type_parameters
3263            .lock()
3264            .expect("C++ macro type-parameter cache poisoned")
3265            .get(key)
3266        {
3267            return indices.clone();
3268        }
3269        #[cfg(any(test, feature = "test-support"))]
3270        self.macro_replacement_parse_count
3271            .fetch_add(1, Ordering::Relaxed);
3272        let indices = (|| {
3273            if replacement.trim().is_empty() {
3274                return None;
3275            }
3276            // Keep this parser permissive. Type-role extraction predates the
3277            // replacement-body model and deliberately survives unrelated
3278            // token-pasting or recovery elsewhere in a production macro.
3279            let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
3280            let mut parser = Parser::new();
3281            parser
3282                .set_language(&tree_sitter_cpp::LANGUAGE.into())
3283                .ok()?;
3284            let tree = parser.parse(&source, None)?;
3285            let mut original_offsets = (0..=replacement.len()).collect::<Vec<_>>();
3286            Self::append_sentinel_offsets(&mut original_offsets, replacement.len());
3287            let body = ParsedReplacementBody {
3288                source,
3289                tree,
3290                body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3291                parameters: parameters.to_vec(),
3292                original_offsets: original_offsets.into_boxed_slice(),
3293            };
3294            macro_replacement_type_parameters(&body, parameters).map(Arc::from)
3295        })();
3296        self.macro_type_parameters
3297            .lock()
3298            .expect("C++ macro type-parameter cache poisoned")
3299            .insert(key.clone(), indices.clone());
3300        indices
3301    }
3302
3303    fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
3304        let replacement = if node.kind() == "preproc_function_def" {
3305            function_macro_replacement_span(node, source)
3306                .and_then(|span| source.get(span))
3307                .map(str::to_owned)
3308                .or_else(|| {
3309                    node.child_by_field_name("value")
3310                        .map(|value| node_text(value, source).to_string())
3311                })
3312                .unwrap_or_default()
3313        } else {
3314            node.child_by_field_name("value")
3315                .map(|value| node_text(value, source).to_string())
3316                .unwrap_or_default()
3317        };
3318        if node.kind() == "preproc_def" {
3319            return MacroDefinition::Object { replacement };
3320        }
3321        let Some(parameters) = node.child_by_field_name("parameters") else {
3322            return MacroDefinition::Unsupported;
3323        };
3324        let variadic = (0..parameters.child_count()).any(|index| {
3325            parameters
3326                .child(index)
3327                .is_some_and(|child| child.kind() == "...")
3328        });
3329        let parameters = (0..parameters.named_child_count())
3330            .filter_map(|index| parameters.named_child(index))
3331            .map(|parameter| node_text(parameter, source).to_string())
3332            .collect::<Vec<_>>();
3333        if variadic {
3334            MacroDefinition::VariadicFunction {
3335                parameters,
3336                replacement,
3337            }
3338        } else {
3339            MacroDefinition::Function {
3340                parameters,
3341                replacement,
3342            }
3343        }
3344    }
3345
3346    pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
3347        clone_or_default_file_cell(
3348            &self.macro_event_cells,
3349            file,
3350            "C++ macro event cache poisoned",
3351        )
3352    }
3353
3354    fn file_defines_macro_name(&self, file: &ProjectFile, name: &str) -> bool {
3355        if let Some(names) = self
3356            .macro_event_name_sets
3357            .lock()
3358            .expect("C++ macro event-name cache poisoned")
3359            .get(file)
3360            .cloned()
3361        {
3362            return names.contains(name);
3363        }
3364        let cell = self.macro_event_cell(file);
3365        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3366        let names = Arc::new(
3367            events
3368                .iter()
3369                .filter_map(|event| match event {
3370                    MacroEvent::Define { name, .. } => Some(name.clone()),
3371                    MacroEvent::Undef { .. }
3372                    | MacroEvent::Include { .. }
3373                    | MacroEvent::Invalidate { .. } => None,
3374                })
3375                .collect(),
3376        );
3377        self.macro_event_name_sets
3378            .lock()
3379            .expect("C++ macro event-name cache poisoned")
3380            .insert(file.clone(), Arc::clone(&names));
3381        names.contains(name)
3382    }
3383
3384    fn macro_environment_checkpoint_cell(
3385        &self,
3386        file: &ProjectFile,
3387    ) -> MacroEnvironmentCheckpointCell {
3388        clone_or_default_file_cell(
3389            &self.macro_environment_checkpoints,
3390            file,
3391            "C++ macro environment checkpoint cache poisoned",
3392        )
3393    }
3394
3395    /// The environment of `file`'s macro events applied up to `before_byte`.
3396    pub fn macro_environment(
3397        &self,
3398        file: &ProjectFile,
3399        before_byte: usize,
3400    ) -> Arc<MacroEnvironment> {
3401        #[cfg(any(test, feature = "test-support"))]
3402        self.macro_environment_request_count
3403            .fetch_add(1, Ordering::Relaxed);
3404        let cell = self.macro_event_cell(file);
3405        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3406        let frontier = events.partition_point(|event| event.byte() < before_byte);
3407        let checkpoint_cell = self.macro_environment_checkpoint_cell(file);
3408        let checkpoints =
3409            checkpoint_cell.get_or_init(|| self.build_macro_environment_checkpoints(file, events));
3410        let checkpoint = checkpoints.at_or_before(frontier);
3411        if checkpoint.frontier == frontier {
3412            return Arc::clone(&checkpoint.environment);
3413        }
3414        #[cfg(any(test, feature = "test-support"))]
3415        self.macro_environment_copy_count
3416            .fetch_add(1, Ordering::Relaxed);
3417        let mut environment = checkpoint.environment.as_ref().clone();
3418        let mut include_stack = HashSet::from_iter([file.clone()]);
3419        for event in &events[checkpoint.frontier..frontier] {
3420            self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3421        }
3422        Arc::new(environment)
3423    }
3424
3425    /// Apply `file`'s events once, keeping the environment at the prefixes
3426    /// [`MacroEnvironmentCheckpoints`] describes.
3427    fn build_macro_environment_checkpoints(
3428        &self,
3429        file: &ProjectFile,
3430        events: &[MacroEvent],
3431    ) -> MacroEnvironmentCheckpoints {
3432        #[cfg(any(test, feature = "test-support"))]
3433        self.macro_environment_checkpoint_build_count
3434            .fetch_add(1, Ordering::Relaxed);
3435        // The TU's build-proven defines hold from the first byte (#2011): they
3436        // are facts of the whole compile, so they seed the frontier-zero
3437        // checkpoint. A later explicit #undef event still overrides them
3438        // through `known_undefined_names`.
3439        let mut environment = MacroEnvironment {
3440            build_proven_defines: self
3441                .compile_proven_guards(file)
3442                .iter()
3443                .filter_map(|guard| match guard {
3444                    PreprocessorGuard::Defined(name) => Some(name.clone()),
3445                    _ => None,
3446                })
3447                .collect(),
3448            ..MacroEnvironment::default()
3449        };
3450        let mut checkpoints = vec![MacroEnvironmentCheckpoint {
3451            frontier: 0,
3452            environment: Arc::new(environment.clone()),
3453        }];
3454        // Keep roughly one fixed stride's worth of checkpoints, while taking
3455        // a checkpoint at every event for event sets no larger than the fixed
3456        // stride. Generated C tables commonly have thousands of uses between
3457        // one #define and a trailing #undef; with a fixed stride those
3458        // identical frontier requests would all copy and replay the same
3459        // prefix.
3460        let checkpoint_stride = events
3461            .len()
3462            .div_ceil(MACRO_ENVIRONMENT_CHECKPOINT_STRIDE)
3463            .clamp(1, MACRO_ENVIRONMENT_CHECKPOINT_STRIDE);
3464        let mut include_stack = HashSet::from_iter([file.clone()]);
3465        for (index, event) in events.iter().enumerate() {
3466            self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3467            let frontier = index + 1;
3468            if frontier % checkpoint_stride == 0 || matches!(event, MacroEvent::Include { .. }) {
3469                checkpoints.push(MacroEnvironmentCheckpoint {
3470                    frontier,
3471                    environment: Arc::new(environment.clone()),
3472                });
3473            }
3474        }
3475        MacroEnvironmentCheckpoints { checkpoints }
3476    }
3477
3478    /// Whether `name` is bound as a macro at `before_byte` in `file`,
3479    /// including a binding this environment cannot pin to one replacement
3480    /// (a conditional `#define`, or a function-like macro).
3481    ///
3482    /// [`Self::object_macro_replacement_at`] collapses every such binding to
3483    /// `None`, which is indistinguishable from "not a macro at all". A caller
3484    /// that must not read a macro token as an ordinary type name needs the two
3485    /// apart: an unexpandable macro is an unknown, a plain identifier is not.
3486    pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
3487        self.macro_environment(file, before_byte)
3488            .binding(name)
3489            .is_some()
3490    }
3491
3492    pub fn macro_name_may_be_bound_at(
3493        &self,
3494        file: &ProjectFile,
3495        name: &str,
3496        before_byte: usize,
3497    ) -> bool {
3498        self.macro_environment(file, before_byte).may_bind(name)
3499    }
3500
3501    /// Whether the active macro binding at this reference is the requested
3502    /// indexed definition. Name equality alone is not enough because two
3503    /// headers can define the same macro for different translation units.
3504    pub fn macro_binding_matches_target_at(
3505        &self,
3506        analyzer: &CppGraphSource<'_>,
3507        file: &ProjectFile,
3508        name: &str,
3509        before_byte: usize,
3510        target: &CodeUnit,
3511    ) -> bool {
3512        let ranges = analyzer.ranges(target);
3513        let declaration_bytes = self.macro_declaration_bytes(target, &ranges);
3514        self.macro_binding_matches_target_declaration_at(
3515            file,
3516            name,
3517            before_byte,
3518            target.source(),
3519            &declaration_bytes,
3520        )
3521    }
3522
3523    /// Resolve declaration ranges once for inverse scans that test many call
3524    /// sites against one macro target. Walking a large generated syntax tree
3525    /// back to the same `#define` for every call site is otherwise quadratic
3526    /// in the number of top-level declarations.
3527    pub(crate) fn macro_declaration_bytes(
3528        &self,
3529        target: &CodeUnit,
3530        ranges: &[Range],
3531    ) -> Vec<usize> {
3532        let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
3533            return Vec::new();
3534        };
3535        ranges
3536            .iter()
3537            .filter_map(|range| {
3538                let mut node = node_for_exact_range(prepared.tree().root_node(), range)?;
3539                while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
3540                    node = node.parent()?;
3541                }
3542                Some(node.start_byte())
3543            })
3544            .collect()
3545    }
3546
3547    pub(crate) fn macro_binding_matches_target_declaration_at(
3548        &self,
3549        file: &ProjectFile,
3550        name: &str,
3551        before_byte: usize,
3552        target_source: &ProjectFile,
3553        target_declaration_bytes: &[usize],
3554    ) -> bool {
3555        let environment = self.macro_environment(file, before_byte);
3556        let Some(binding) = environment.binding(name) else {
3557            return false;
3558        };
3559        if binding.definition == MacroDefinition::Unsupported {
3560            return false;
3561        }
3562        // A normal header guard makes the replacement text conditional, but
3563        // it does not erase the definition site's source and byte identity.
3564        // Keep that identity even when expansion details are not exact.
3565        if binding.source != *target_source {
3566            return false;
3567        }
3568        target_declaration_bytes.contains(&binding.declaration_byte)
3569    }
3570
3571    /// Resolve an ordinary expression-position macro token at its exact byte.
3572    ///
3573    /// Calls and preprocessor-condition tokens have separate resolution
3574    /// surfaces. Declaration names, macro parameters, and labels are not
3575    /// references. Keeping that role policy here makes forward and both
3576    /// inverse graph builders consume the same activation verdict (#2093).
3577    pub fn resolve_ordinary_macro_reference(
3578        &self,
3579        analyzer: &CppGraphSource<'_>,
3580        file: &ProjectFile,
3581        node: Node<'_>,
3582        source: &str,
3583    ) -> OrdinaryMacroReferenceResolution {
3584        if !is_ordinary_macro_reference_node(node) {
3585            return OrdinaryMacroReferenceResolution::Missing;
3586        }
3587        let name = node_text(node, source);
3588        if name.is_empty() {
3589            return OrdinaryMacroReferenceResolution::Missing;
3590        }
3591        let visible = self
3592            .visible_identifier_candidates(file, name)
3593            .filter(|candidate| candidate.is_macro())
3594            .cloned()
3595            .collect::<Vec<_>>();
3596        let mut exact = Vec::new();
3597        for candidate in &visible {
3598            if self.macro_binding_matches_target_at(
3599                analyzer,
3600                file,
3601                name,
3602                node.start_byte(),
3603                candidate,
3604            ) && !exact
3605                .iter()
3606                .any(|existing| same_visible_symbol(existing, candidate))
3607            {
3608                exact.push(candidate.clone());
3609            }
3610        }
3611        match exact.len() {
3612            1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
3613            2.. => OrdinaryMacroReferenceResolution::Ambiguous,
3614            0 if !visible.is_empty()
3615                && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
3616            {
3617                OrdinaryMacroReferenceResolution::Ambiguous
3618            }
3619            0 => OrdinaryMacroReferenceResolution::Missing,
3620        }
3621    }
3622
3623    /// Collect reference-capable C tokens beneath tree-sitter recovery nodes.
3624    ///
3625    /// The ordinary census deliberately skips every `ERROR` subtree. This
3626    /// separate, precision-only frontier admits only roles that retain enough
3627    /// structure for the C usage graph to interpret independently (#2089).
3628    /// Macro evidence comes from this visibility index at the exact byte; no
3629    /// source-text parsing or terminal-name fallback is used.
3630    pub fn recovered_c_reference_ranges(
3631        &self,
3632        file: &ProjectFile,
3633        root: Node<'_>,
3634        source: &str,
3635        limit: usize,
3636    ) -> RecoveredCReferenceRanges {
3637        if !is_c_source_file(file) {
3638            return RecoveredCReferenceRanges::Complete(Vec::new());
3639        }
3640        let mut ranges = Vec::new();
3641        let mut seen = HashSet::default();
3642        let mut stack = vec![(root, root.is_error())];
3643        while let Some((node, inside_error)) = stack.pop() {
3644            let inside_error = inside_error || node.is_error();
3645            if node.kind() == "preproc_arg" {
3646                // Tree-sitter keeps an object-like replacement opaque. The
3647                // inverse extractor uses the same parsed replacement helper;
3648                // retain its exact macro/type leaves for precision membership
3649                // even when the preprocessor node is outside ERROR recovery.
3650                let macro_value_kind = node.parent().and_then(|parent| {
3651                    (parent.child_by_field_name("value") == Some(node)).then_some(parent.kind())
3652                });
3653                if matches!(
3654                    macro_value_kind,
3655                    Some("preproc_def" | "preproc_function_def")
3656                ) {
3657                    let name = node_text(node, source);
3658                    if !name.is_empty()
3659                        && self.macro_name_may_be_bound_at(file, name, node.start_byte())
3660                        && !push_recovered_c_range(
3661                            &mut ranges,
3662                            &mut seen,
3663                            node.start_byte(),
3664                            node.end_byte(),
3665                            node,
3666                            limit,
3667                        )
3668                    {
3669                        return RecoveredCReferenceRanges::LimitExceeded;
3670                    }
3671                }
3672                if macro_value_kind == Some("preproc_def") {
3673                    for reference in object_macro_replacement_type_references(node, source) {
3674                        for range in reference.component_ranges {
3675                            let visible = self
3676                                .visible_identifier_candidates(file, &source[range.clone()])
3677                                .any(|candidate| candidate.is_class() || candidate.is_module());
3678                            if visible
3679                                && !push_recovered_c_range(
3680                                    &mut ranges,
3681                                    &mut seen,
3682                                    range.start,
3683                                    range.end,
3684                                    node,
3685                                    limit,
3686                                )
3687                            {
3688                                return RecoveredCReferenceRanges::LimitExceeded;
3689                            }
3690                        }
3691                    }
3692                }
3693            }
3694            if inside_error
3695                && recovered_c_reference_node(self, file, node, source)
3696                && !push_recovered_c_range(
3697                    &mut ranges,
3698                    &mut seen,
3699                    node.start_byte(),
3700                    node.end_byte(),
3701                    node,
3702                    limit,
3703                )
3704            {
3705                return RecoveredCReferenceRanges::LimitExceeded;
3706            }
3707            let mut cursor = node.walk();
3708            for child in node.named_children(&mut cursor) {
3709                stack.push((child, inside_error));
3710            }
3711        }
3712        ranges.sort_unstable();
3713        RecoveredCReferenceRanges::Complete(ranges)
3714    }
3715
3716    /// Whether this target is an indexed macro visible from this file.
3717    ///
3718    /// An unresolved conditional can make more than one same-name macro a
3719    /// possible active binding. Each possible target can keep the site as an
3720    /// unproven hit. A macro in an unrelated translation unit stays excluded.
3721    pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
3722        self.visible_identifier_candidates(file, target.identifier())
3723            .filter(|candidate| candidate.is_macro())
3724            .any(|candidate| {
3725                candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
3726            })
3727    }
3728
3729    pub fn object_macro_replacement_at(
3730        &self,
3731        file: &ProjectFile,
3732        name: &str,
3733        before_byte: usize,
3734    ) -> Option<String> {
3735        let environment = self.macro_environment(file, before_byte);
3736        let binding = environment.binding(name)?;
3737        if !binding.exact {
3738            return None;
3739        }
3740        match &binding.definition {
3741            MacroDefinition::Object { replacement } => Some(replacement.clone()),
3742            MacroDefinition::Function { .. }
3743            | MacroDefinition::VariadicFunction { .. }
3744            | MacroDefinition::Unsupported => None,
3745        }
3746    }
3747
3748    fn apply_macro_events(
3749        &self,
3750        file: &ProjectFile,
3751        before_byte: Option<usize>,
3752        environment: &mut MacroEnvironment,
3753        include_stack: &mut HashSet<ProjectFile>,
3754    ) {
3755        if !include_stack.insert(file.clone()) {
3756            return;
3757        }
3758        if self.cpp.prepared_syntax(self.token, file).is_none() {
3759            environment.mark_unknown_names(file, before_byte.unwrap_or_default());
3760            include_stack.remove(file);
3761            return;
3762        }
3763        match self.macro_include_protection(file) {
3764            MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
3765                Some(binding) if binding.is_exact() => {
3766                    include_stack.remove(file);
3767                    return;
3768                }
3769                Some(_) | None if environment.unknown_names => {
3770                    let mut ambiguous_seen = HashSet::default();
3771                    self.mark_macro_events_ambiguous(
3772                        file,
3773                        environment,
3774                        &mut ambiguous_seen,
3775                        file,
3776                        before_byte.unwrap_or_default(),
3777                    );
3778                    include_stack.remove(file);
3779                    return;
3780                }
3781                Some(_) => {
3782                    let mut ambiguous_seen = HashSet::default();
3783                    self.mark_macro_events_ambiguous(
3784                        file,
3785                        environment,
3786                        &mut ambiguous_seen,
3787                        file,
3788                        before_byte.unwrap_or_default(),
3789                    );
3790                    include_stack.remove(file);
3791                    return;
3792                }
3793                None => {}
3794            },
3795            MacroIncludeProtection::PragmaOnce => {
3796                if !environment.applied_pragma_once_files.insert(file.clone()) {
3797                    include_stack.remove(file);
3798                    return;
3799                }
3800                if environment.maybe_applied_pragma_once_files.remove(file) {
3801                    // A prior conditional include may already have consumed the pragma-once
3802                    // header. This unconditional include guarantees it is consumed now, but
3803                    // cannot prove whether its events occur before or after intervening local
3804                    // macro changes, so preserve the union as ambiguous.
3805                    let mut ambiguous_seen = HashSet::default();
3806                    environment.applied_pragma_once_files.remove(file);
3807                    self.mark_macro_events_ambiguous(
3808                        file,
3809                        environment,
3810                        &mut ambiguous_seen,
3811                        file,
3812                        before_byte.unwrap_or_default(),
3813                    );
3814                    environment.maybe_applied_pragma_once_files.remove(file);
3815                    environment.applied_pragma_once_files.insert(file.clone());
3816                    include_stack.remove(file);
3817                    return;
3818                }
3819            }
3820            MacroIncludeProtection::None => {}
3821        }
3822        let cell = self.macro_event_cell(file);
3823        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3824        for event in events {
3825            if before_byte.is_some_and(|limit| event.byte() >= limit) {
3826                break;
3827            }
3828            self.apply_macro_event(file, event, environment, include_stack);
3829        }
3830        include_stack.remove(file);
3831    }
3832
3833    fn apply_macro_event(
3834        &self,
3835        file: &ProjectFile,
3836        event: &MacroEvent,
3837        environment: &mut MacroEnvironment,
3838        include_stack: &mut HashSet<ProjectFile>,
3839    ) {
3840        #[cfg(any(test, feature = "test-support"))]
3841        self.macro_event_application_count
3842            .fetch_add(1, Ordering::Relaxed);
3843        match event {
3844            MacroEvent::Define {
3845                name,
3846                binding,
3847                conditionals,
3848                byte,
3849            } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3850                Some(true) => environment.insert(name.clone(), binding.clone()),
3851                Some(false) => {}
3852                None => Self::merge_conditional_macro_definition(
3853                    environment,
3854                    name,
3855                    binding,
3856                    file,
3857                    *byte,
3858                ),
3859            },
3860            MacroEvent::Undef {
3861                name,
3862                conditionals,
3863                byte,
3864            } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3865                Some(true) => environment.remove(name),
3866                Some(false) => {}
3867                None => {
3868                    if environment.binding(name).is_some() {
3869                        environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
3870                    }
3871                }
3872            },
3873            MacroEvent::Include {
3874                targets,
3875                conditionals,
3876                byte,
3877            } => {
3878                let condition =
3879                    self.macro_event_condition_value(file, *byte, environment, conditionals);
3880                if condition == Some(false) {
3881                    return;
3882                }
3883                if targets.is_empty() {
3884                    environment.mark_unknown_names(file, *byte);
3885                    return;
3886                }
3887                if condition.is_none() || targets.len() > 1 {
3888                    let mut ambiguous_seen = HashSet::default();
3889                    for target in targets {
3890                        self.mark_macro_events_ambiguous(
3891                            target,
3892                            environment,
3893                            &mut ambiguous_seen,
3894                            file,
3895                            *byte,
3896                        );
3897                    }
3898                } else if let Some(target) = targets.first() {
3899                    self.apply_macro_events(target, None, environment, include_stack);
3900                }
3901            }
3902            MacroEvent::Invalidate { byte } => {
3903                for binding in environment.bindings.values_mut() {
3904                    *binding = MacroBinding::uncertain_from(binding, file, *byte);
3905                }
3906            }
3907        }
3908    }
3909
3910    /// Evaluate the structured conditional path that owns one macro event.
3911    ///
3912    /// `Some(true)` and `Some(false)` are proofs from exact macro bindings at
3913    /// this source byte. `None` preserves the old conditional merge when a
3914    /// build/configuration input or an unsupported expression is involved.
3915    ///
3916    /// `conditionals` is the event's own [`OwningPreprocessorConditionals`]:
3917    /// which ancestors structurally own the event was decided when the events
3918    /// were collected, so all this walk does is find those nodes again and ask
3919    /// the environment what their conditions are worth here.
3920    fn macro_event_condition_value(
3921        &self,
3922        file: &ProjectFile,
3923        event_byte: usize,
3924        environment: &MacroEnvironment,
3925        conditionals: &OwningPreprocessorConditionals,
3926    ) -> Option<bool> {
3927        if conditionals.is_empty() {
3928            return Some(true);
3929        }
3930        let prepared = self.cpp.prepared_syntax(self.token, file)?;
3931        let source = prepared.source();
3932        let root = prepared.tree().root_node();
3933        let descendant = root.descendant_for_byte_range(
3934            event_byte,
3935            event_byte.saturating_add(1).min(source.len()),
3936        )?;
3937        let mut unknown = false;
3938        let mut current = descendant.parent();
3939        while let Some(conditional) = current {
3940            if matches!(
3941                conditional.kind(),
3942                "preproc_if" | "preproc_ifdef" | "preproc_elif"
3943            ) && conditionals.contains(&conditional.start_byte())
3944            {
3945                let mut value = match conditional.kind() {
3946                    "preproc_ifdef" => {
3947                        let name = conditional.child_by_field_name("name")?;
3948                        let defined =
3949                            self.macro_name_defined_value(environment, node_text(name, source));
3950                        match conditional.child(0)?.kind() {
3951                            "#ifdef" => defined,
3952                            "#ifndef" => defined.map(|defined| !defined),
3953                            _ => None,
3954                        }
3955                    }
3956                    "preproc_if" | "preproc_elif" => conditional
3957                        .child_by_field_name("condition")
3958                        .and_then(|condition| {
3959                            self.preprocessor_integer_value(
3960                                condition,
3961                                source,
3962                                environment,
3963                                &mut Vec::new(),
3964                                0,
3965                            )
3966                        })
3967                        .map(|value| value != 0),
3968                    _ => unreachable!(),
3969                };
3970                if conditional
3971                    .child_by_field_name("alternative")
3972                    .is_some_and(|alternative| {
3973                        alternative.start_byte() <= descendant.start_byte()
3974                            && descendant.end_byte() <= alternative.end_byte()
3975                    })
3976                {
3977                    value = value.map(|value| !value);
3978                }
3979                match value {
3980                    Some(true) => {}
3981                    Some(false) => return Some(false),
3982                    None => unknown = true,
3983                }
3984            }
3985            current = conditional.parent();
3986        }
3987        (!unknown).then_some(true)
3988    }
3989
3990    fn macro_name_defined_value(&self, environment: &MacroEnvironment, name: &str) -> Option<bool> {
3991        if environment.known_undefined_names.contains(name) {
3992            return Some(false);
3993        }
3994        if let Some(binding) = environment.binding(name) {
3995            return binding.is_exact().then_some(true);
3996        }
3997        environment
3998            .build_proven_defines
3999            .contains(name)
4000            .then_some(true)
4001    }
4002
4003    fn preprocessor_integer_value(
4004        &self,
4005        expression: Node<'_>,
4006        source: &str,
4007        environment: &MacroEnvironment,
4008        expansion_stack: &mut Vec<(ProjectFile, usize)>,
4009        depth: usize,
4010    ) -> Option<i128> {
4011        // Macro replacement graphs can cycle. This explicit bound makes the
4012        // otherwise recursive AST evaluation stack-safe for hostile input.
4013        if depth >= 64 {
4014            return None;
4015        }
4016        match expression.kind() {
4017            "number_literal" => parse_cpp_integer_literal(node_text(expression, source)),
4018            "identifier" | "type_identifier" => {
4019                let binding = environment.binding(node_text(expression, source))?;
4020                if !binding.is_exact() {
4021                    return None;
4022                }
4023                let MacroDefinition::Object { replacement } = &binding.definition else {
4024                    return None;
4025                };
4026                let identity = (binding.source.clone(), binding.declaration_byte);
4027                if expansion_stack.contains(&identity) {
4028                    return None;
4029                }
4030                expansion_stack.push(identity);
4031                let parsed = self.parsed_macro_replacement(binding, replacement);
4032                let value = match parsed.as_ref() {
4033                    ParsedMacroReplacement::Parsed {
4034                        source: replacement_source,
4035                        tree,
4036                    } => first_descendant_of_kind(tree.root_node(), "call_expression")
4037                        .and_then(|call| call.child_by_field_name("arguments"))
4038                        .and_then(|arguments| argument_children(arguments).next())
4039                        .and_then(|argument| {
4040                            self.preprocessor_integer_value(
4041                                argument,
4042                                replacement_source,
4043                                environment,
4044                                expansion_stack,
4045                                depth + 1,
4046                            )
4047                        }),
4048                    ParsedMacroReplacement::Unsupported => None,
4049                };
4050                expansion_stack.pop();
4051                value
4052            }
4053            "preproc_defined" => {
4054                let mut cursor = expression.walk();
4055                let name = expression
4056                    .named_children(&mut cursor)
4057                    .find(|child| child.kind() == "identifier")?;
4058                self.macro_name_defined_value(environment, node_text(name, source))
4059                    .map(i128::from)
4060            }
4061            "parenthesized_expression" => expression.named_child(0).and_then(|child| {
4062                self.preprocessor_integer_value(
4063                    child,
4064                    source,
4065                    environment,
4066                    expansion_stack,
4067                    depth + 1,
4068                )
4069            }),
4070            "unary_expression" => {
4071                let operator = expression.child_by_field_name("operator")?.kind();
4072                let argument = expression.child_by_field_name("argument")?;
4073                let value = self.preprocessor_integer_value(
4074                    argument,
4075                    source,
4076                    environment,
4077                    expansion_stack,
4078                    depth + 1,
4079                )?;
4080                match operator {
4081                    "+" => Some(value),
4082                    "-" => value.checked_neg(),
4083                    "!" => Some(i128::from(value == 0)),
4084                    "~" => Some(!value),
4085                    _ => None,
4086                }
4087            }
4088            "binary_expression" => {
4089                let left = self.preprocessor_integer_value(
4090                    expression.child_by_field_name("left")?,
4091                    source,
4092                    environment,
4093                    expansion_stack,
4094                    depth + 1,
4095                )?;
4096                let right = self.preprocessor_integer_value(
4097                    expression.child_by_field_name("right")?,
4098                    source,
4099                    environment,
4100                    expansion_stack,
4101                    depth + 1,
4102                )?;
4103                match expression.child_by_field_name("operator")?.kind() {
4104                    "+" => left.checked_add(right),
4105                    "-" => left.checked_sub(right),
4106                    "*" => left.checked_mul(right),
4107                    "/" => left.checked_div(right),
4108                    "%" => left.checked_rem(right),
4109                    "<<" => u32::try_from(right)
4110                        .ok()
4111                        .and_then(|shift| left.checked_shl(shift)),
4112                    ">>" => u32::try_from(right)
4113                        .ok()
4114                        .and_then(|shift| left.checked_shr(shift)),
4115                    "<" => Some(i128::from(left < right)),
4116                    "<=" => Some(i128::from(left <= right)),
4117                    ">" => Some(i128::from(left > right)),
4118                    ">=" => Some(i128::from(left >= right)),
4119                    "==" => Some(i128::from(left == right)),
4120                    "!=" => Some(i128::from(left != right)),
4121                    "&" => Some(left & right),
4122                    "|" => Some(left | right),
4123                    "^" => Some(left ^ right),
4124                    "&&" => Some(i128::from(left != 0 && right != 0)),
4125                    "||" => Some(i128::from(left != 0 || right != 0)),
4126                    _ => None,
4127                }
4128            }
4129            _ => None,
4130        }
4131    }
4132
4133    fn mark_macro_events_ambiguous(
4134        &self,
4135        file: &ProjectFile,
4136        environment: &mut MacroEnvironment,
4137        include_stack: &mut HashSet<ProjectFile>,
4138        conditional_file: &ProjectFile,
4139        conditional_byte: usize,
4140    ) {
4141        if !include_stack.insert(file.clone()) {
4142            return;
4143        }
4144        if self.cpp.prepared_syntax(self.token, file).is_none() {
4145            environment.mark_unknown_names(conditional_file, conditional_byte);
4146            return;
4147        }
4148        match self.macro_include_protection(file) {
4149            MacroIncludeProtection::MacroGuard(guard) => {
4150                if environment
4151                    .binding(&guard)
4152                    .is_some_and(MacroBinding::is_exact)
4153                {
4154                    return;
4155                }
4156            }
4157            MacroIncludeProtection::PragmaOnce => {
4158                if environment.applied_pragma_once_files.contains(file) {
4159                    return;
4160                }
4161                environment
4162                    .maybe_applied_pragma_once_files
4163                    .insert(file.clone());
4164            }
4165            MacroIncludeProtection::None => {}
4166        }
4167        let cell = self.macro_event_cell(file);
4168        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
4169        for event in events {
4170            #[cfg(any(test, feature = "test-support"))]
4171            self.macro_event_application_count
4172                .fetch_add(1, Ordering::Relaxed);
4173            match event {
4174                MacroEvent::Define { name, binding, .. } => {
4175                    Self::merge_conditional_macro_definition(
4176                        environment,
4177                        name,
4178                        binding,
4179                        conditional_file,
4180                        conditional_byte,
4181                    );
4182                }
4183                MacroEvent::Undef { name, .. } => {
4184                    if environment.binding(name).is_some() {
4185                        environment.insert(
4186                            name.clone(),
4187                            MacroBinding::ambiguous(conditional_file, conditional_byte),
4188                        );
4189                    } else {
4190                        environment.remove_known_undefined(name);
4191                    }
4192                }
4193                MacroEvent::Include { targets, .. } => {
4194                    if targets.is_empty() {
4195                        environment.mark_unknown_names(conditional_file, conditional_byte);
4196                        continue;
4197                    }
4198                    for target in targets {
4199                        self.mark_macro_events_ambiguous(
4200                            target,
4201                            environment,
4202                            include_stack,
4203                            conditional_file,
4204                            conditional_byte,
4205                        );
4206                    }
4207                }
4208                MacroEvent::Invalidate { .. } => {
4209                    for binding in environment.bindings.values_mut() {
4210                        *binding = MacroBinding::uncertain_from(
4211                            binding,
4212                            conditional_file,
4213                            conditional_byte,
4214                        );
4215                    }
4216                }
4217            }
4218        }
4219    }
4220
4221    fn merge_conditional_macro_definition(
4222        environment: &mut MacroEnvironment,
4223        name: &str,
4224        possible_binding: &MacroBinding,
4225        conditional_file: &ProjectFile,
4226        conditional_byte: usize,
4227    ) {
4228        // A conditional include can revisit an already-active guarded header.
4229        // If the possible branch defines the exact same macro, both outcomes
4230        // leave the binding unchanged; degrading it to Unknown would discard
4231        // proof because of an unrelated unresolved macro name (#2092).
4232        if environment.binding(name).is_some_and(|current| {
4233            current.definition != MacroDefinition::Unsupported
4234                && current.definition == possible_binding.definition
4235        }) {
4236            return;
4237        }
4238        environment.insert(
4239            name.to_string(),
4240            MacroBinding::ambiguous(conditional_file, conditional_byte),
4241        );
4242    }
4243
4244    pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
4245        let cell = self
4246            .macro_include_protection_cells
4247            .lock()
4248            .expect("C++ include protection cache poisoned")
4249            .entry(file.clone())
4250            .or_default()
4251            .clone();
4252        cell.get_or_init(|| {
4253            self.cpp.prepared_syntax(self.token, file).map_or(
4254                MacroIncludeProtection::None,
4255                |prepared| {
4256                    top_level_macro_include_protection(
4257                        prepared.tree().root_node(),
4258                        prepared.source(),
4259                    )
4260                },
4261            )
4262        })
4263        .clone()
4264    }
4265
4266    fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
4267        collect_macro_events(self.cpp, self.token, file)
4268    }
4269
4270    pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
4271        self.ordinary_type_import_cells
4272            .lock()
4273            .expect("C++ ordinary type import cache poisoned")
4274            .entry(file.clone())
4275            .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
4276            .clone()
4277    }
4278
4279    pub fn project_using_index(
4280        &self,
4281        build: impl FnOnce() -> ProjectUsingIndex,
4282    ) -> &ProjectUsingIndex {
4283        self.project_using_index.get_or_init(build)
4284    }
4285
4286    pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
4287        let mut files = self
4288            .visible_source_files_by_root
4289            .values()
4290            .flatten()
4291            .cloned()
4292            .collect::<HashSet<_>>()
4293            .into_iter()
4294            .collect::<Vec<_>>();
4295        files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
4296        files
4297    }
4298
4299    pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
4300        self.visible_source_files_by_root
4301            .get(root)
4302            .is_some_and(|files| files.contains(source))
4303    }
4304
4305    fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
4306        let cached = self
4307            .visible_parser_alias_name_sets
4308            .read()
4309            .expect("visible parser alias-name cache poisoned")
4310            .get(file)
4311            .cloned();
4312        let cell = if let Some(cached) = cached {
4313            cached
4314        } else {
4315            let mut cells = self
4316                .visible_parser_alias_name_sets
4317                .write()
4318                .expect("visible parser alias-name cache poisoned");
4319            Arc::clone(
4320                cells
4321                    .entry(file.clone())
4322                    .or_insert_with(|| Arc::new(OnceLock::new())),
4323            )
4324        };
4325        if let Some(names) = cell.get() {
4326            return names.contains(name);
4327        }
4328        let mut names = HashSet::default();
4329        let visible_files = self
4330            .visible_source_files_by_root
4331            .get(file)
4332            .cloned()
4333            .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
4334        for visible_file in visible_files {
4335            let Some(aliases) = self.canonical_alias_cell(self.cpp, &visible_file) else {
4336                return false;
4337            };
4338            for alias in aliases.get().expect("published canonical aliases").iter() {
4339                names.insert(alias.name.clone());
4340            }
4341        }
4342        #[cfg(any(test, feature = "test-support"))]
4343        self.visible_parser_alias_name_set_build_count
4344            .fetch_add(1, Ordering::Relaxed);
4345        cell.get_or_init(|| names).contains(name)
4346    }
4347
4348    pub fn parser_alias_name_may_resolve_to_target(
4349        &self,
4350        file: &ProjectFile,
4351        alias_name: &str,
4352        target: &CodeUnit,
4353    ) -> bool {
4354        let started = std::time::Instant::now();
4355        self.parser_alias_fallback_calls
4356            .fetch_add(1, Ordering::Relaxed);
4357        let key = (
4358            file.clone(),
4359            alias_name.to_string(),
4360            logical_symbol_key(target),
4361        );
4362        let cached = self
4363            .parser_alias_target_matches
4364            .read()
4365            .expect("parser alias target-match cache poisoned")
4366            .get(&key)
4367            .cloned();
4368        let cell = if let Some(cached) = cached {
4369            cached
4370        } else {
4371            let mut cells = self
4372                .parser_alias_target_matches
4373                .write()
4374                .expect("parser alias target-match cache poisoned");
4375            Arc::clone(
4376                cells
4377                    .entry(key)
4378                    .or_insert_with(|| Arc::new(OnceLock::new())),
4379            )
4380        };
4381        let matched = *cell.get_or_init(|| {
4382            any_visible_source_file_matches(
4383                &self.visible_source_files_by_root,
4384                file,
4385                |visible_file| {
4386                    self.parser_alias_fallback_files
4387                        .fetch_add(1, Ordering::Relaxed);
4388                    self.file_alias_matches(self.cpp, visible_file, alias_name, target)
4389                },
4390            )
4391        });
4392        self.parser_alias_fallback_elapsed_micros.fetch_add(
4393            started.elapsed().as_micros().min(usize::MAX as u128) as usize,
4394            Ordering::Relaxed,
4395        );
4396        matched
4397    }
4398
4399    fn callable_arities_for_target(
4400        &self,
4401        analyzer: &CppGraphSource<'_>,
4402        cpp: &dyn CppSource,
4403        file: &ProjectFile,
4404        prepared: &PreparedSyntaxTree,
4405        spec: &TargetSpec,
4406    ) -> Vec<ActivatedCallableArity> {
4407        let Some(signature) = spec.target.signature() else {
4408            return Vec::new();
4409        };
4410        let Some(candidates) = self
4411            .visible_by_identifier
4412            .get(file)
4413            .and_then(|by_name| by_name.get(&spec.member_name))
4414        else {
4415            return Vec::new();
4416        };
4417        let differing_candidates = candidates
4418            .iter()
4419            .filter(|candidate| {
4420                candidate.is_function()
4421                    && candidate.fq_name() == spec.target.fq_name()
4422                    && candidate.signature() == Some(signature)
4423            })
4424            .filter_map(|candidate| {
4425                analyzer
4426                    .signature_metadata(candidate)
4427                    .into_iter()
4428                    .find_map(|metadata| metadata.callable_arity())
4429                    .filter(|arity| Some(*arity) != spec.callable_arity)
4430                    .map(|arity| (candidate, arity))
4431            })
4432            .collect::<Vec<_>>();
4433        if differing_candidates.is_empty() {
4434            return Vec::new();
4435        }
4436        let mut arities = Vec::with_capacity(differing_candidates.len());
4437        // The activation ranges here describe the whole file rather than one
4438        // reference, so there is no reference guard environment to consult.
4439        let reference = CallableReferenceContext {
4440            file,
4441            position: None,
4442        };
4443        for (candidate, candidate_arity) in differing_candidates {
4444            let declaration_activation =
4445                callable_declaration_activation_in_file(analyzer, candidate, &reference);
4446            let Some(declaration_activation) = declaration_activation else {
4447                continue;
4448            };
4449            let activation_byte = if candidate.source() == file {
4450                Some(declaration_activation)
4451            } else {
4452                self.include_activation_for_source(cpp, file, prepared, candidate.source())
4453            };
4454            if let Some(activation_byte) = activation_byte {
4455                arities.push(ActivatedCallableArity {
4456                    activation_byte,
4457                    arity: candidate_arity,
4458                });
4459            }
4460        }
4461        arities
4462    }
4463
4464    fn callable_parameter_macro_arity(
4465        &self,
4466        target: &CodeUnit,
4467        parameter_types: Option<&[String]>,
4468    ) -> Option<CallableArity> {
4469        let parameter_types = parameter_types?;
4470        let [macro_name] = parameter_types else {
4471            return None;
4472        };
4473        if macro_name.is_empty()
4474            || !macro_name
4475                .chars()
4476                .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
4477        {
4478            return None;
4479        }
4480        let cache_key = (target.source().clone(), macro_name.clone());
4481        if let Some(cached) = self
4482            .callable_parameter_macro_arities
4483            .lock()
4484            .expect("C++ callable parameter-macro arity cache poisoned")
4485            .get(&cache_key)
4486            .copied()
4487        {
4488            return cached;
4489        }
4490        let mut visible_files = HashSet::default();
4491        collect_include_closure(
4492            &self.cpp_source(),
4493            self.cpp.include_target_index(),
4494            target.source(),
4495            &mut visible_files,
4496            None,
4497        );
4498        let mut arities = Vec::new();
4499        for visible_file in visible_files {
4500            let cell = self.macro_event_cell(&visible_file);
4501            for event in
4502                cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
4503            {
4504                let MacroEvent::Define { name, binding, .. } = event else {
4505                    continue;
4506                };
4507                if name != macro_name {
4508                    continue;
4509                }
4510                let MacroDefinition::Object { replacement } = &binding.definition else {
4511                    continue;
4512                };
4513                let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
4514                    continue;
4515                };
4516                if !arities.contains(&arity) {
4517                    arities.push(arity);
4518                }
4519            }
4520        }
4521        let resolved = (|| {
4522            let required = arities
4523                .iter()
4524                .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
4525                .min()?;
4526            let total = arities.iter().map(|arity| arity.total()).max()?;
4527            let repeated = arities
4528                .iter()
4529                .any(|arity| arity.accepts(arity.total().saturating_add(1)));
4530            // Preprocessor conditions can leave more than one object-like parameter
4531            // bundle active in the target header's include closure. Preserve their
4532            // conservative callable envelope instead of choosing whichever definition
4533            // happened to be visited first.
4534            Some(CallableArity::new(required, total, repeated))
4535        })();
4536        self.callable_parameter_macro_arities
4537            .lock()
4538            .expect("C++ callable parameter-macro arity cache poisoned")
4539            .insert(cache_key, resolved);
4540        resolved
4541    }
4542
4543    pub fn include_activation_for_source(
4544        &self,
4545        cpp: &dyn CppSource,
4546        file: &ProjectFile,
4547        prepared: &PreparedSyntaxTree,
4548        donor_source: &ProjectFile,
4549    ) -> Option<usize> {
4550        let key = (file.clone(), donor_source.clone());
4551        if let Some(cached) = self
4552            .include_activation_cells
4553            .lock()
4554            .expect("C++ include activation cache poisoned")
4555            .get(&key)
4556            .copied()
4557        {
4558            return cached;
4559        }
4560        #[cfg(any(test, feature = "test-support"))]
4561        self.include_activation_build_count
4562            .fetch_add(1, Ordering::Relaxed);
4563        let language_guard_stable = OnceCell::new();
4564        let activation =
4565            find_include_activation(cpp, self.token, file, prepared, donor_source, &|| {
4566                *language_guard_stable.get_or_init(|| self.include_language_guard_is_stable(file))
4567            });
4568        let mut cells = self
4569            .include_activation_cells
4570            .lock()
4571            .expect("C++ include activation cache poisoned");
4572        *cells.entry(key).or_insert(activation)
4573    }
4574
4575    pub fn conditional_include_projections_for_source(
4576        &self,
4577        file: &ProjectFile,
4578        prepared: &PreparedSyntaxTree,
4579        donor_source: &ProjectFile,
4580    ) -> Arc<[ConditionalIncludeProjection]> {
4581        static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
4582        let cell = self
4583            .conditional_include_projection_cells
4584            .lock()
4585            .expect("C++ conditional include projection cache poisoned")
4586            .entry(file.clone())
4587            .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
4588            .clone();
4589        let index = cell.get_or_build_pool_independent(|| {
4590            #[cfg(any(test, feature = "test-support"))]
4591            self.conditional_include_projection_index_build_count
4592                .fetch_add(1, Ordering::Relaxed);
4593            let language_guard_stable = OnceCell::new();
4594            find_conditional_include_projection_index(
4595                self.cpp,
4596                self.token,
4597                file,
4598                prepared,
4599                &|| {
4600                    *language_guard_stable
4601                        .get_or_init(|| self.include_language_guard_is_stable(file))
4602                },
4603                &|| {
4604                    #[cfg(any(test, feature = "test-support"))]
4605                    self.conditional_include_projection_state_count
4606                        .fetch_add(1, Ordering::Relaxed);
4607                },
4608            )
4609        });
4610        index
4611            .get(donor_source)
4612            .cloned()
4613            .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
4614    }
4615
4616    fn include_language_guard_is_stable(&self, file: &ProjectFile) -> bool {
4617        let mut macro_events = |file: &ProjectFile| {
4618            let cell = self.macro_event_cell(file);
4619            cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice())
4620                .clone()
4621        };
4622        include_language_guard_is_stable_with(
4623            self.cpp,
4624            self.token,
4625            file,
4626            &mut macro_events,
4627            &mut |file: &ProjectFile, prepared: &PreparedSyntaxTree| {
4628                self.structured_include_facts(file, prepared)
4629            },
4630        )
4631    }
4632
4633    #[cfg(any(test, feature = "test-support"))]
4634    pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
4635        (
4636            self.conditional_include_projection_index_build_count
4637                .load(Ordering::Relaxed),
4638            self.conditional_include_projection_state_count
4639                .load(Ordering::Relaxed),
4640        )
4641    }
4642
4643    #[cfg(any(test, feature = "test-support"))]
4644    pub fn conditional_include_target_state_count_for_test(&self) -> usize {
4645        self.conditional_include_target_state_count
4646            .load(Ordering::Relaxed)
4647    }
4648
4649    #[cfg(any(test, feature = "test-support"))]
4650    pub fn include_activation_build_count_for_test(&self) -> usize {
4651        self.include_activation_build_count.load(Ordering::Relaxed)
4652    }
4653
4654    #[cfg(any(test, feature = "test-support"))]
4655    pub fn note_using_donor_activation_for_test(&self) {
4656        self.using_donor_activation_count
4657            .fetch_add(1, Ordering::Relaxed);
4658    }
4659
4660    #[cfg(not(any(test, feature = "test-support")))]
4661    pub fn note_using_donor_activation_for_test(&self) {}
4662
4663    #[cfg(any(test, feature = "test-support"))]
4664    pub fn note_using_namespace_lookup_for_test(&self) {
4665        self.using_namespace_lookup_count
4666            .fetch_add(1, Ordering::Relaxed);
4667    }
4668
4669    #[cfg(not(any(test, feature = "test-support")))]
4670    pub fn note_using_namespace_lookup_for_test(&self) {}
4671
4672    #[cfg(any(test, feature = "test-support"))]
4673    pub fn note_using_name_candidate_inspection_for_test(&self) {
4674        self.using_name_candidate_inspection_count
4675            .fetch_add(1, Ordering::Relaxed);
4676    }
4677
4678    #[cfg(not(any(test, feature = "test-support")))]
4679    pub fn note_using_name_candidate_inspection_for_test(&self) {}
4680
4681    #[cfg(any(test, feature = "test-support"))]
4682    pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
4683        (
4684            self.using_donor_activation_count.load(Ordering::Relaxed),
4685            self.using_namespace_lookup_count.load(Ordering::Relaxed),
4686            self.callable_reference_spec_build_count
4687                .load(Ordering::Relaxed),
4688            self.using_name_candidate_inspection_count
4689                .load(Ordering::Relaxed),
4690        )
4691    }
4692
4693    pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
4694        file == target.source()
4695            || self
4696                .visible_by_file
4697                .get(file)
4698                .is_some_and(|visible| visible.contains(target))
4699    }
4700
4701    /// Whether some declaration of `declaration`'s logical symbol is visible at
4702    /// `reference_byte` in `file`.
4703    ///
4704    /// The question is asked of the *logical* symbol, not of the physical unit:
4705    /// an out-of-line body in a `.cpp` nobody includes is never itself visible,
4706    /// and it does not have to be - what makes the call legal is the header
4707    /// declaration that the reference file does include. Reading that relation
4708    /// through `same_logical_callable` rather than through signature strings is
4709    /// the same #2010 correction the gates make, and it matters here because
4710    /// the body and the declaration are exactly the pair that spells one
4711    /// parameter type two ways.
4712    pub fn declaration_visible_at(
4713        &self,
4714        analyzer: &CppGraphSource<'_>,
4715        file: &ProjectFile,
4716        declaration: &CodeUnit,
4717        reference_byte: usize,
4718    ) -> bool {
4719        let reference_guards = OnceCell::new();
4720        self.visible_identifier_candidates(file, declaration.identifier())
4721            .filter(|candidate| {
4722                self.same_logical_callable(analyzer, candidate, declaration)
4723                    || flattened_macro_namespace_declaration_matches(
4724                        analyzer,
4725                        self.cpp,
4726                        file,
4727                        candidate,
4728                        declaration,
4729                        reference_byte,
4730                    )
4731            })
4732            .any(|candidate| {
4733                self.physical_declaration_visible_at(
4734                    analyzer,
4735                    file,
4736                    candidate,
4737                    reference_byte,
4738                    &reference_guards,
4739                )
4740            })
4741    }
4742
4743    /// Whether a physical declaration is visible at an exact structured
4744    /// reference node. Inverse C field references need the reference's own
4745    /// preprocessor environment before using the callable activation path:
4746    /// callable activation can establish that a field declaration is
4747    /// nameable, but it must not admit the opposite branch of that field's
4748    /// conditional family.
4749    pub fn declaration_visible_at_reference(
4750        &self,
4751        analyzer: &CppGraphSource<'_>,
4752        file: &ProjectFile,
4753        declaration: &CodeUnit,
4754        reference: Node<'_>,
4755    ) -> bool {
4756        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4757            return false;
4758        };
4759        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4760        let declaration_guards = declaration_guard_requirements(analyzer, self.cpp, declaration);
4761        if !declaration_guards.iter().any(|(_, required)| {
4762            guards_compatible_at_reference(required, reference_guards.as_ref())
4763        }) {
4764            return false;
4765        }
4766        if declaration.source() == file
4767            && !analyzer.reference_uses_c_semantics(file)
4768            && (declaration.is_field() || declaration.is_callable())
4769            && type_owner_of(analyzer, declaration).is_some_and(|owner| {
4770                self.indexed_enclosing_owner_scope(analyzer, file, reference)
4771                    .is_some_and(|scope| scope == canonical_cpp_scope_components(&owner))
4772            })
4773        {
4774            // C++ complete-class context makes members visible throughout the
4775            // class. The guard check above still excludes a declaration from
4776            // an incompatible preprocessor branch.
4777            return true;
4778        }
4779        if declaration.is_field() && declaration.source() == file {
4780            let reference_byte = reference.start_byte();
4781            let reference_function =
4782                real_function_definition_ancestor(reference, prepared.source());
4783            let guards = OnceCell::new();
4784            let field_reference = CallableReferenceContext {
4785                file,
4786                position: Some(CallableReferencePosition {
4787                    prepared: prepared.as_ref(),
4788                    byte: reference_byte,
4789                    guards: &guards,
4790                }),
4791            };
4792            let mut has_local_declaration = false;
4793            let mut local_declaration_visible = false;
4794            for declaration in callable_declaration_nodes(analyzer, prepared.as_ref(), declaration)
4795            {
4796                let Some(declaration_function) =
4797                    real_function_definition_ancestor(declaration, prepared.source())
4798                else {
4799                    continue;
4800                };
4801                has_local_declaration = true;
4802                if reference_function.is_some_and(|reference_function| {
4803                    reference_function.start_byte() == declaration_function.start_byte()
4804                        && reference_function.end_byte() == declaration_function.end_byte()
4805                }) && callable_preprocessor_context_is_visible_for_reference(
4806                    declaration,
4807                    prepared.source(),
4808                    &field_reference,
4809                ) && declaration.end_byte() < reference_byte
4810                {
4811                    local_declaration_visible = true;
4812                    break;
4813                }
4814            }
4815            if has_local_declaration {
4816                return local_declaration_visible;
4817            }
4818        }
4819        let guards = OnceCell::new();
4820        self.physical_declaration_visible_at(
4821            analyzer,
4822            file,
4823            declaration,
4824            reference.start_byte(),
4825            &guards,
4826        )
4827    }
4828
4829    /// C forward navigation may bind a call to a later same-file definition.
4830    /// There is no earlier source declaration to activate in that legacy C
4831    /// shape, but the call's preprocessor environment must still imply the
4832    /// definition's requirements. Ordinary C++ and inverse visibility retain
4833    /// the declaration-order rule in [`Self::declaration_visible_at`].
4834    pub fn declaration_visible_for_c_forward_call(
4835        &self,
4836        analyzer: &CppGraphSource<'_>,
4837        file: &ProjectFile,
4838        declaration: &CodeUnit,
4839        reference_byte: usize,
4840    ) -> bool {
4841        if self.declaration_visible_at(analyzer, file, declaration, reference_byte) {
4842            return true;
4843        }
4844        if declaration.source() != file {
4845            return false;
4846        }
4847        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4848            return false;
4849        };
4850        let reference_guards = prepared
4851            .tree()
4852            .root_node()
4853            .descendant_for_byte_range(reference_byte, reference_byte)
4854            .and_then(|node| preprocessor_guard_environment(node, prepared.source()));
4855        declaration_guard_requirements(analyzer, self.cpp, declaration)
4856            .into_iter()
4857            .any(|(_, required)| {
4858                guard_requirements_hold_at_reference(&required, reference_guards.as_ref())
4859            })
4860    }
4861
4862    pub fn callable_arity_at_reference(
4863        &self,
4864        analyzer: &CppGraphSource<'_>,
4865        file: &ProjectFile,
4866        candidate: &CodeUnit,
4867        reference_byte: usize,
4868    ) -> Option<CallableArity> {
4869        let key = (file.clone(), logical_symbol_key(candidate));
4870        let cell = self
4871            .callable_reference_specs
4872            .lock()
4873            .expect("C++ callable reference-spec cache poisoned")
4874            .entry(key)
4875            .or_default()
4876            .clone();
4877        let spec = cell.get_or_init(|| {
4878            let prepared = self.cpp.prepared_syntax(self.token, file)?;
4879            let spec = TargetSpec::from_target(analyzer, candidate)?;
4880            let spec = spec
4881                .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
4882                .into_owned();
4883            #[cfg(any(test, feature = "test-support"))]
4884            self.callable_reference_spec_build_count
4885                .fetch_add(1, Ordering::Relaxed);
4886            Some(spec)
4887        });
4888        spec.as_ref()?.callable_arity_at(reference_byte)
4889    }
4890
4891    fn physical_declaration_visible_at(
4892        &self,
4893        analyzer: &CppGraphSource<'_>,
4894        file: &ProjectFile,
4895        declaration: &CodeUnit,
4896        reference_byte: usize,
4897        reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
4898    ) -> bool {
4899        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4900            return false;
4901        };
4902        let reference = CallableReferenceContext {
4903            file,
4904            position: Some(CallableReferencePosition {
4905                prepared: prepared.as_ref(),
4906                byte: reference_byte,
4907                guards: reference_guards,
4908            }),
4909        };
4910        if declaration.source() == file {
4911            return callable_declaration_activation_in_file(analyzer, declaration, &reference)
4912                .or_else(|| {
4913                    self.exhaustive_guard_family_activation(analyzer, declaration, &reference)
4914                })
4915                .is_some_and(|activation| activation < reference_byte);
4916        }
4917        if self
4918            .foreign_callable_declaration_activation(analyzer, declaration, &reference)
4919            .or_else(|| self.exhaustive_guard_family_activation(analyzer, declaration, &reference))
4920            .is_none()
4921        {
4922            return false;
4923        }
4924        declaration_guard_requirements(analyzer, self.cpp, declaration)
4925            .into_iter()
4926            .any(|(_, declaration_guards)| {
4927                self.foreign_declaration_reachable_at_reference(
4928                    file,
4929                    prepared.as_ref(),
4930                    declaration.source(),
4931                    &declaration_guards,
4932                    reference.guards(),
4933                    reference_byte,
4934                )
4935            })
4936    }
4937
4938    /// The byte at which `declaration` activates inside the foreign file that
4939    /// its canonical source occurrence describes.
4940    ///
4941    /// Which guard rule applies depends on what decides the reference's
4942    /// configuration. When `compile_commands.json` covers the reference's
4943    /// translation unit, the build is the proof source and the declaration's
4944    /// guards must follow from the reference's active guards plus the proven
4945    /// defines; a platform macro the build does not prove leaves the
4946    /// declaration invisible (#2011). With no compile coverage nothing decides
4947    /// a platform macro, so the cross-file rule stands: the donor resolves its
4948    /// own conditionals and only has to stay free of contradiction with the
4949    /// reference. Otherwise a libuv-shaped `#if defined(__linux__)` prototype
4950    /// in a shared header is hidden from every unguarded caller in another
4951    /// translation unit (#2988).
4952    fn foreign_callable_declaration_activation(
4953        &self,
4954        analyzer: &CppGraphSource<'_>,
4955        declaration: &CodeUnit,
4956        reference: &CallableReferenceContext<'_>,
4957    ) -> Option<usize> {
4958        let build_decides = !self.compile_context_is_absent(reference.file);
4959        let proven = self.compile_proven_guards(reference.file);
4960        let augmented;
4961        let active = match reference.guards() {
4962            Some(active) if !proven.is_empty() => {
4963                augmented = active.union(&proven).cloned().collect();
4964                Some(&augmented)
4965            }
4966            other => other,
4967        };
4968        analyzer
4969            .declaration_source_occurrences(declaration)?
4970            .into_iter()
4971            .filter_map(|occurrence| {
4972                let activation = occurrence.fact.callable_activation?;
4973                let mut required =
4974                    cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
4975                if required.iter().any(|guard| match guard {
4976                    PreprocessorGuard::Defined(name) if name == "__cplusplus" => reference.is_c(),
4977                    PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
4978                        !reference.is_c()
4979                    }
4980                    _ => false,
4981                }) {
4982                    return None;
4983                }
4984                required.retain(|guard| {
4985                    !matches!(guard,
4986                    PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
4987                        if name == "__cplusplus")
4988                });
4989                let visible = required.is_empty()
4990                    || if build_decides {
4991                        guard_requirements_hold_at_reference(&required, active)
4992                    } else {
4993                        guards_compatible_at_reference(&required, reference.guards())
4994                    };
4995                visible.then_some(activation)
4996            })
4997            .min()
4998    }
4999
5000    pub fn external_type_candidate_visible_at(
5001        &self,
5002        file: &ProjectFile,
5003        candidate: &CodeUnit,
5004        reference_byte: usize,
5005    ) -> bool {
5006        if candidate.source() == file {
5007            return true;
5008        }
5009        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5010            return false;
5011        };
5012        self.visible_identifier_candidates(file, candidate.identifier())
5013            .filter(|peer| same_logical_symbol(candidate, peer))
5014            .any(|peer| {
5015                peer.source() == file
5016                    || self
5017                        .include_activation_for_source(
5018                            self.cpp,
5019                            file,
5020                            prepared.as_ref(),
5021                            peer.source(),
5022                        )
5023                        .is_some_and(|activation| activation <= reference_byte)
5024            })
5025    }
5026
5027    pub fn external_type_declaration_visible_at(
5028        &self,
5029        file: &ProjectFile,
5030        candidate: &CodeUnit,
5031        reference_byte: usize,
5032    ) -> bool {
5033        if candidate.source() == file {
5034            return true;
5035        }
5036        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5037            return false;
5038        };
5039        self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
5040            .is_some_and(|activation| activation <= reference_byte)
5041    }
5042
5043    /// The preprocessor facts the build proves for a reference sited in
5044    /// `file` (#2011).
5045    ///
5046    /// Every `-D` that survives its command's `-D`/`-U` ordering is a positive
5047    /// `Defined` fact, and a fact holds only when every compile configuration
5048    /// that governs the file agrees on it (intersection). The facts are
5049    /// strictly additive to the reference's active guard set: they can prove a
5050    /// required guard, but the guard check itself is never weakened and no
5051    /// implication is ever inferred from source text.
5052    ///
5053    /// A file with its own database entry answers from that entry alone
5054    /// (phase 1). A header takes its context from the translation units whose
5055    /// include closure reaches it, intersected across all of them (phase 2):
5056    /// the header is compiled once per including TU, so a fact holds for a
5057    /// header-sited reference only when every one of those compilations
5058    /// proves it. A reaching TU the database does not cover proves nothing,
5059    /// which empties the intersection. A file nothing covers or reaches has
5060    /// no facts and every check runs on source structure alone.
5061    pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
5062        if let Some(cached) = self
5063            .compile_proven_guard_cells
5064            .lock()
5065            .expect("C++ compile-proven guard cache poisoned")
5066            .get(file)
5067        {
5068            return Arc::clone(cached);
5069        }
5070        let names = match context_fact_names(self.cpp.compile_contexts_for(file)) {
5071            Some(names) => names,
5072            None => {
5073                let mut translation_units = self.cpp.reaching_translation_units(file).into_iter();
5074                let seed = translation_units.next().and_then(|translation_unit| {
5075                    context_fact_names(self.cpp.compile_contexts_for(&translation_unit))
5076                });
5077                match seed {
5078                    None => HashSet::default(),
5079                    Some(mut names) => {
5080                        for translation_unit in translation_units {
5081                            let Some(reached) = context_fact_names(
5082                                self.cpp.compile_contexts_for(&translation_unit),
5083                            ) else {
5084                                names.clear();
5085                                break;
5086                            };
5087                            names.retain(|name| reached.contains(name));
5088                            if names.is_empty() {
5089                                break;
5090                            }
5091                        }
5092                        names
5093                    }
5094                }
5095            }
5096        };
5097        let proven = Arc::new(
5098            names
5099                .into_iter()
5100                .map(PreprocessorGuard::Defined)
5101                .collect::<HashSet<_>>(),
5102        );
5103        self.compile_proven_guard_cells
5104            .lock()
5105            .expect("C++ compile-proven guard cache poisoned")
5106            .insert(file.clone(), Arc::clone(&proven));
5107        proven
5108    }
5109
5110    /// The rule a conditional `#include` path's guards face on the way to a
5111    /// reference in `file`.
5112    ///
5113    /// This is the include-edge analogue of the choice
5114    /// [`Self::foreign_callable_declaration_activation`] makes for a foreign
5115    /// declaration's own guards (#2988), and it is decided by the same thing:
5116    /// what settles the reference's configuration. Memoized per file because
5117    /// the answer depends on nothing else, and the callers ask it once per
5118    /// candidate.
5119    fn include_path_admission(&self, file: &ProjectFile) -> IncludePathAdmission {
5120        if let Some(cached) = self
5121            .include_path_admission_cells
5122            .lock()
5123            .expect("C++ include-path admission cache poisoned")
5124            .get(file)
5125            .copied()
5126        {
5127            return cached;
5128        }
5129        let admission = if self.compile_context_is_absent(file) {
5130            IncludePathAdmission::Compatible
5131        } else {
5132            IncludePathAdmission::Proven
5133        };
5134        self.include_path_admission_cells
5135            .lock()
5136            .expect("C++ include-path admission cache poisoned")
5137            .insert(file.clone(), admission);
5138        admission
5139    }
5140
5141    /// Whether no compile data covers the compilations of `file`: it has no
5142    /// database entry of its own, and either nothing reaches it or some
5143    /// translation unit that reaches it has no entry. This is the state a
5144    /// regenerated `compile_commands.json` could decide; data that is present
5145    /// for every governing compilation but does not prove a guard is a
5146    /// decided conservative miss, not this state.
5147    fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
5148        if !self.cpp.compile_contexts_for(file).is_empty() {
5149            return false;
5150        }
5151        let translation_units = self.cpp.reaching_translation_units(file);
5152        translation_units.is_empty()
5153            || translation_units
5154                .iter()
5155                .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
5156    }
5157
5158    /// Whether a lookup miss for `identifier` in `file` is explainable by
5159    /// missing compile context (#2011): some same-name declaration is
5160    /// reachable through a conditional include whose required guards neither
5161    /// contradict the reference's active guards nor follow from them, and the
5162    /// translation unit has no compile-commands entry that could decide the
5163    /// question. Callers surface this as an explicit "requires compile
5164    /// context" incompleteness instead of an indistinguishable miss.
5165    ///
5166    /// A structurally disproven declaration (contradicting guards) and a TU
5167    /// whose compile context exists but does not prove the guard both answer
5168    /// `false`: those misses are decided, not incomplete.
5169    pub fn miss_requires_compile_context(
5170        &self,
5171        file: &ProjectFile,
5172        identifier: &str,
5173        reference: Node<'_>,
5174    ) -> bool {
5175        if !self.compile_context_is_absent(file) {
5176            return false;
5177        }
5178        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5179            return false;
5180        };
5181        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5182        let reference_byte = reference.start_byte();
5183        let mut sources = self
5184            .visible_identifier_candidates(file, identifier)
5185            .map(CodeUnit::source)
5186            .filter(|source| *source != file)
5187            .collect::<Vec<_>>();
5188        sources.sort();
5189        sources.dedup();
5190        sources.into_iter().any(|declaration_source| {
5191            self.conditional_include_projections_for_source(
5192                file,
5193                prepared.as_ref(),
5194                declaration_source,
5195            )
5196            .iter()
5197            .any(|projection| {
5198                projection.activation_byte <= reference_byte
5199                    && !guard_requirements_hold_at_reference(
5200                        &projection.required_guards,
5201                        reference_guards.as_ref(),
5202                    )
5203                    && guards_compatible_at_reference(
5204                        &projection.required_guards,
5205                        reference_guards.as_ref(),
5206                    )
5207            })
5208        })
5209    }
5210
5211    /// Decide whether a declaration that lives in another file reaches a
5212    /// reference in `file`.
5213    ///
5214    /// An external header selects its declaration branch before the reference
5215    /// file is parsed. Require compatible reference guards, but do not test
5216    /// the header's guard expression for stability in the reference file: a
5217    /// `.c` translation unit can never satisfy the `#ifdef __cplusplus` that
5218    /// wraps every declaration of a portable C header, and demanding it would
5219    /// hide the whole header. Guards that the reference file imposes on its
5220    /// own `#include` still have to be stable, and must satisfy the rule
5221    /// [`IncludePathAdmission`] names for this reference's translation unit.
5222    fn foreign_declaration_reachable_at_reference(
5223        &self,
5224        file: &ProjectFile,
5225        prepared: &PreparedSyntaxTree,
5226        declaration_source: &ProjectFile,
5227        declaration_guards: &HashSet<PreprocessorGuard>,
5228        reference_guards: Option<&HashSet<PreprocessorGuard>>,
5229        reference_byte: usize,
5230    ) -> bool {
5231        // The translation unit's build-proven defines join the reference's
5232        // active guard set (#2011): a conditional include like the nng
5233        // `NNG_PLATFORM_POSIX` chain is provable only by the compile command.
5234        // A reference whose own environment is unknown stays unknown -- the
5235        // facts extend an environment, they never invent one.
5236        let proven = self.compile_proven_guards(file);
5237        let augmented;
5238        let reference_guards = match reference_guards {
5239            Some(active) if !proven.is_empty() => {
5240                augmented = active.union(&proven).cloned().collect();
5241                Some(&augmented)
5242            }
5243            other => other,
5244        };
5245        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5246            eprintln!(
5247                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=foreign_guard_compatibility declaration_source={} declaration_guards={declaration_guards:?} reference_guards={reference_guards:?}",
5248                declaration_source.rel_path().display(),
5249            );
5250        }
5251        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5252            return false;
5253        }
5254        if self
5255            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5256            .is_some_and(|activation| activation <= reference_byte)
5257        {
5258            return true;
5259        }
5260        let projections =
5261            self.conditional_include_projections_for_source(file, prepared, declaration_source);
5262        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5263            eprintln!(
5264                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=filtered_projection source={} declaration_guards={} proven_guards={} projections={}",
5265                declaration_source.rel_path().display(),
5266                declaration_guards.len(),
5267                proven.len(),
5268                projections.len(),
5269            );
5270        }
5271        let admission = self.include_path_admission(file);
5272        projections.iter().any(|projection| {
5273            projection.activation_byte <= reference_byte
5274                && admission.admits(
5275                    &projection.required_guards,
5276                    &projection.partial_guards,
5277                    reference_guards,
5278                )
5279                && self.preprocessor_guards_stable_between(
5280                    file,
5281                    projection.activation_byte,
5282                    reference_byte,
5283                    &projection.required_guards,
5284                )
5285        })
5286    }
5287
5288    fn foreign_declaration_may_be_reachable_from_raw_guards(
5289        &self,
5290        file: &ProjectFile,
5291        prepared: &PreparedSyntaxTree,
5292        declaration_source: &ProjectFile,
5293        declaration_guards: &HashSet<PreprocessorGuard>,
5294        reference_guards: Option<&HashSet<PreprocessorGuard>>,
5295        reference_byte: usize,
5296    ) -> bool {
5297        let proven = self.compile_proven_guards(file);
5298        let augmented;
5299        let reference_guards = match reference_guards {
5300            Some(active) if !proven.is_empty() => {
5301                augmented = active.union(&proven).cloned().collect();
5302                Some(&augmented)
5303            }
5304            other => other,
5305        };
5306        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5307            return false;
5308        }
5309        if self
5310            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5311            .is_some_and(|activation| activation <= reference_byte)
5312        {
5313            return true;
5314        }
5315        let language_guard_stable = OnceCell::new();
5316        let reachable = find_conditional_include_projection_for_source(
5317            self.cpp,
5318            self.token,
5319            file,
5320            prepared,
5321            declaration_source,
5322            self.include_path_admission(file),
5323            reference_guards,
5324            reference_byte,
5325            &|| *language_guard_stable.get_or_init(|| self.include_language_guard_is_stable(file)),
5326            &|| {
5327                #[cfg(any(test, feature = "test-support"))]
5328                self.conditional_include_target_state_count
5329                    .fetch_add(1, Ordering::Relaxed);
5330            },
5331        );
5332        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5333            eprintln!(
5334                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_projection source={} declaration_guards={} proven_guards={} raw_guards={} reachable={reachable}",
5335                declaration_source.rel_path().display(),
5336                declaration_guards.len(),
5337                proven.len(),
5338                reference_guards.map_or(0, HashSet::len),
5339            );
5340        }
5341        reachable
5342    }
5343
5344    fn foreign_declaration_reachable_from_compile_proven_guards(
5345        &self,
5346        file: &ProjectFile,
5347        prepared: &PreparedSyntaxTree,
5348        declaration_source: &ProjectFile,
5349        declaration_guards: &HashSet<PreprocessorGuard>,
5350        reference_byte: usize,
5351    ) -> bool {
5352        let proven = self.compile_proven_guards(file);
5353        if proven.is_empty()
5354            || !guards_compatible_at_reference(declaration_guards, Some(proven.as_ref()))
5355        {
5356            return false;
5357        }
5358        let projections =
5359            self.conditional_include_projections_for_source(file, prepared, declaration_source);
5360        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5361            eprintln!(
5362                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=compile_proven_projection source={} declaration_guards={} proven_guards={} projections={}",
5363                declaration_source.rel_path().display(),
5364                declaration_guards.len(),
5365                proven.len(),
5366                projections.len(),
5367            );
5368        }
5369        projections.iter().any(|projection| {
5370            projection.activation_byte <= reference_byte
5371                    && guard_requirements_hold_at_reference(
5372                        &projection.required_guards,
5373                        Some(proven.as_ref()),
5374                    )
5375                    // Build facts hold at translation-unit entry. A source
5376                    // `#undef` or an earlier include may invalidate one before
5377                    // this conditional include is reached; mutations after the
5378                    // include cannot revoke declarations it already supplied.
5379                    && self.preprocessor_guards_stable_between(
5380                        file,
5381                        0,
5382                        projection.activation_byte,
5383                        &projection.required_guards,
5384                    )
5385        })
5386    }
5387
5388    pub fn external_type_candidate_visible_in_context(
5389        &self,
5390        analyzer: &CppGraphSource<'_>,
5391        file: &ProjectFile,
5392        candidate: &CodeUnit,
5393        reference: Node<'_>,
5394    ) -> bool {
5395        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
5396        if report_stats {
5397            eprintln!(
5398                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=started fqn={} candidate_source={} reference_file={} reference_byte={}",
5399                candidate.fq_name(),
5400                candidate.source().rel_path().display(),
5401                file.rel_path().display(),
5402                reference.start_byte(),
5403            );
5404        }
5405        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5406            return false;
5407        };
5408        let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
5409        let reference_guards = OnceCell::new();
5410        let reference_guards_at_site = || {
5411            reference_guards.get_or_init(|| {
5412                let started = Instant::now();
5413                if report_stats {
5414                    eprintln!(
5415                        "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=started file={} reference_byte={} raw_guards={}",
5416                        file.rel_path().display(),
5417                        reference.start_byte(),
5418                        raw_reference_guards.as_ref().map_or(0, HashSet::len),
5419                    );
5420                }
5421                let macro_environment = self.macro_environment(file, reference.start_byte());
5422                let filtered = raw_reference_guards
5423                    .clone()
5424                    .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
5425                if report_stats {
5426                    eprintln!(
5427                        "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=completed retained={} elapsed_ms={}",
5428                        filtered.is_some(),
5429                        started.elapsed().as_millis(),
5430                    );
5431                }
5432                filtered
5433            })
5434        };
5435
5436        let peers = self
5437            .visible_identifier_candidates(file, candidate.identifier())
5438            .filter(|peer| same_logical_symbol(candidate, peer))
5439            .collect::<Vec<_>>();
5440        if report_stats {
5441            let peer_sources = peers
5442                .iter()
5443                .map(|peer| peer.source().rel_path().display().to_string())
5444                .collect::<Vec<_>>();
5445            eprintln!(
5446                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=peers fqn={} sources={peer_sources:?}",
5447                candidate.fq_name(),
5448            );
5449        }
5450        let directly_visible_without_reference_environment = peers.iter().any(|peer| {
5451            declaration_guard_requirements(analyzer, self.cpp, peer)
5452                .into_iter()
5453                .any(|(declaration_byte, declaration_guards)| {
5454                    if peer.source() == file {
5455                        let visible = declaration_byte < reference.start_byte()
5456                            && declaration_guards.is_empty();
5457                        if report_stats {
5458                            eprintln!(
5459                                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=true visible={visible}",
5460                                peer.source().rel_path().display(),
5461                                declaration_guards.len(),
5462                            );
5463                        }
5464                        return visible;
5465                    }
5466                    let direct = declaration_guards.is_empty()
5467                        && self
5468                            .include_activation_for_source(
5469                                self.cpp,
5470                                file,
5471                                prepared.as_ref(),
5472                                peer.source(),
5473                            )
5474                            .is_some_and(|activation| activation <= reference.start_byte());
5475                    let compile_proven = !direct
5476                        && self.foreign_declaration_reachable_from_compile_proven_guards(
5477                            file,
5478                            prepared.as_ref(),
5479                            peer.source(),
5480                            &declaration_guards,
5481                            reference.start_byte(),
5482                        );
5483                    if report_stats {
5484                        eprintln!(
5485                            "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=false direct={direct} compile_proven={compile_proven}",
5486                            peer.source().rel_path().display(),
5487                            declaration_guards.len(),
5488                        );
5489                    }
5490                    direct || compile_proven
5491                })
5492        });
5493        if directly_visible_without_reference_environment {
5494            if report_stats {
5495                eprintln!(
5496                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=direct_or_compile_proven fqn={}",
5497                    candidate.fq_name(),
5498                );
5499            }
5500            return true;
5501        }
5502        let directly_visible = peers.iter().any(|peer| {
5503            declaration_guard_sites(analyzer, self.cpp, peer, Some(reference.start_byte()))
5504                .into_iter()
5505                .any(|site| {
5506                    let declaration_byte = site.byte;
5507                    let declaration_guards = site.guards;
5508                    if peer.source() == file {
5509                        if declaration_byte >= reference.start_byte() {
5510                            return false;
5511                        }
5512                        // The reference's own conditional context must either
5513                        // prove the declaration's requirements, or follow a
5514                        // completed family that encloses the declaration
5515                        // (#3297); either way it must not contradict them.
5516                        let active = |guards: Option<&HashSet<PreprocessorGuard>>| {
5517                            guard_requirements_hold_at_reference(&declaration_guards, guards)
5518                                || (site.completed_family_before_reference
5519                                    && guards_compatible_at_reference(&declaration_guards, guards))
5520                        };
5521                        if !active(raw_reference_guards.as_ref()) {
5522                            return false;
5523                        }
5524                        return active(reference_guards_at_site().as_ref())
5525                            && self.preprocessor_guards_stable_between(
5526                                file,
5527                                declaration_byte,
5528                                reference.start_byte(),
5529                                &declaration_guards,
5530                            );
5531                    }
5532                    let raw_feasible = self.foreign_declaration_may_be_reachable_from_raw_guards(
5533                        file,
5534                        prepared.as_ref(),
5535                        peer.source(),
5536                        &declaration_guards,
5537                        raw_reference_guards.as_ref(),
5538                        reference.start_byte(),
5539                    );
5540                    if report_stats {
5541                        eprintln!(
5542                            "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_feasibility source={} declaration_guards={} feasible={raw_feasible}",
5543                            peer.source().rel_path().display(),
5544                            declaration_guards.len(),
5545                        );
5546                    }
5547                    if !raw_feasible {
5548                        return false;
5549                    }
5550                    self.foreign_declaration_reachable_at_reference(
5551                        file,
5552                        prepared.as_ref(),
5553                        peer.source(),
5554                        &declaration_guards,
5555                        reference_guards_at_site().as_ref(),
5556                        reference.start_byte(),
5557                    )
5558                })
5559        });
5560        if directly_visible {
5561            if report_stats {
5562                eprintln!(
5563                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=filtered_reference fqn={}",
5564                    candidate.fq_name(),
5565                );
5566            }
5567            return true;
5568        }
5569        let complementary = self
5570            .visible_identifier_candidates(file, candidate.identifier())
5571            .filter(|peer| {
5572                peer.kind() == candidate.kind()
5573                    && peer.fq_name() == candidate.fq_name()
5574                    && peer.source() == candidate.source()
5575            })
5576            .collect::<Vec<_>>();
5577        // A completed #if/#else family declares the shared source-level name
5578        // before this reference. A later macro mutation cannot revoke that
5579        // declaration. The family gate below rejects declarations split across
5580        // separate conditional blocks, where mutation can change coverage.
5581        let complementary_family =
5582            self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate);
5583        let raw_candidate_branch_compatible = complementary_family
5584            && raw_reference_guards.as_ref().is_some_and(|active| {
5585                declaration_guard_requirements(analyzer, self.cpp, candidate)
5586                    .iter()
5587                    .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5588            });
5589        if report_stats {
5590            eprintln!(
5591                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=complementary fqn={} candidates={} family={} raw_compatible={}",
5592                candidate.fq_name(),
5593                complementary.len(),
5594                complementary_family,
5595                raw_candidate_branch_compatible,
5596            );
5597        }
5598        let candidate_branch_compatible = raw_candidate_branch_compatible
5599            && reference_guards_at_site().as_ref().is_some_and(|active| {
5600                declaration_guard_requirements(analyzer, self.cpp, candidate)
5601                    .iter()
5602                    .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5603            });
5604        let complementary_visible = candidate_branch_compatible
5605            && if candidate.source() == file {
5606                declaration_guard_requirements(analyzer, self.cpp, candidate)
5607                    .iter()
5608                    .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
5609            } else {
5610                self.include_activation_for_source(
5611                    self.cpp,
5612                    file,
5613                    prepared.as_ref(),
5614                    candidate.source(),
5615                )
5616                .is_some_and(|activation| activation <= reference.start_byte())
5617            };
5618        if report_stats {
5619            eprintln!(
5620                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome={} fqn={}",
5621                if complementary_visible {
5622                    "complementary"
5623                } else {
5624                    "missing"
5625                },
5626                candidate.fq_name(),
5627            );
5628        }
5629        complementary_visible
5630    }
5631
5632    pub fn is_exhaustive_same_fqn_type_declaration_family(
5633        &self,
5634        analyzer: &CppGraphSource<'_>,
5635        file: &ProjectFile,
5636        candidate: &CodeUnit,
5637    ) -> bool {
5638        let candidates = self
5639            .visible_identifier_candidates(file, candidate.identifier())
5640            .filter(|peer| {
5641                peer.kind() == candidate.kind()
5642                    && peer.fq_name() == candidate.fq_name()
5643                    && peer.source() == candidate.source()
5644            })
5645            .collect::<Vec<_>>();
5646        self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
5647    }
5648
5649    /// Prove a nested type alias used as a dependent member-pointer owner when
5650    /// its owning class has mutually-exclusive declarations.  A common C++11
5651    /// compatibility shape provides the owning class in one preprocessor
5652    /// branch and aliases it to a standard-library type in the other branch;
5653    /// the nested fallback alias is therefore not itself active in every
5654    /// branch even though the qualified owner API is.
5655    ///
5656    /// This is deliberately narrower than ordinary type visibility.  The
5657    /// caller has already recovered a member-pointer owner path from the CST;
5658    /// this helper additionally requires the target's structured parent to
5659    /// match that path, physical source visibility, and exact preprocessor
5660    /// guard agreement with the parent declaration.  Only then may the
5661    /// parent's direct/complementary same-FQN visibility stand in for the
5662    /// nested terminal's active-branch check.
5663    pub fn dependent_member_pointer_alias_visible_in_context(
5664        &self,
5665        analyzer: &CppGraphSource<'_>,
5666        file: &ProjectFile,
5667        candidate: &CodeUnit,
5668        owner_components: &[String],
5669        reference: Node<'_>,
5670    ) -> bool {
5671        if !analyzer
5672            .type_alias_provider()
5673            .is_some_and(|provider| provider.is_type_alias(candidate))
5674        {
5675            return false;
5676        }
5677        let Some((terminal, owner_prefix)) = owner_components.split_last() else {
5678            return false;
5679        };
5680        if terminal != candidate.identifier()
5681            || canonical_cpp_scope_components(candidate) != owner_components
5682        {
5683            return false;
5684        }
5685        let Some(expected_parent_fq_name) =
5686            brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
5687        else {
5688            return false;
5689        };
5690        let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
5691            return false;
5692        };
5693        if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
5694            || parent_anchor.source() != candidate.source()
5695            || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
5696        {
5697            return false;
5698        }
5699
5700        // The ordinary path already handles unguarded aliases (and preserves
5701        // same-file declaration ordering).  This fallback is only for a
5702        // physically visible declaration whose guard is the owning branch's
5703        // guard, so reject a same-file declaration that appears after the
5704        // reference before considering guard compatibility.
5705        if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
5706            || candidate.source() == file
5707                && !analyzer
5708                    .ranges(candidate)
5709                    .iter()
5710                    .any(|range| range.start_byte < reference.start_byte())
5711        {
5712            return false;
5713        }
5714
5715        let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
5716        if candidate_guards.is_empty() {
5717            return false;
5718        }
5719        let same_guard_sets =
5720            |left: &[(usize, HashSet<PreprocessorGuard>)],
5721             right: &[(usize, HashSet<PreprocessorGuard>)]| {
5722                left.iter().all(|(_, left_guards)| {
5723                    right
5724                        .iter()
5725                        .any(|(_, right_guards)| left_guards == right_guards)
5726                })
5727            };
5728        let parent_candidates = self
5729            .visible_identifier_candidates(file, parent_anchor.identifier())
5730            .filter(|peer| {
5731                peer.kind() == parent_anchor.kind()
5732                    && peer.fq_name() == expected_parent_fq_name.as_str()
5733                    && peer.source() == parent_anchor.source()
5734                    && canonical_cpp_scope_components(peer) == owner_prefix
5735            })
5736            .filter_map(|peer| {
5737                let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
5738                (candidate_guards.len() == parent_guards.len()
5739                    && same_guard_sets(&candidate_guards, &parent_guards)
5740                    && same_guard_sets(&parent_guards, &candidate_guards))
5741                .then(|| (peer.clone(), parent_guards))
5742            })
5743            .collect::<Vec<_>>();
5744        let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
5745            return false;
5746        };
5747
5748        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5749            return false;
5750        };
5751        let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
5752        else {
5753            return false;
5754        };
5755        // An external header selects its declaration branch before the
5756        // reference file is parsed. Require compatible reference guards, but
5757        // do not test the header's guard expression for stability in the
5758        // reference file. Same-file aliases still require that stability.
5759        if !candidate_guards.iter().any(|(_, target_guards)| {
5760            guards_compatible_at_reference(target_guards, Some(&reference_guards))
5761                && (candidate.source() != file
5762                    || self.preprocessor_guards_stable_between(
5763                        file,
5764                        0,
5765                        reference.start_byte(),
5766                        target_guards,
5767                    ))
5768        }) {
5769            return false;
5770        }
5771
5772        self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
5773    }
5774
5775    /// Check a type candidate's preprocessor/import context without imposing
5776    /// ordinary declaration-before-reference ordering for same-file peers.
5777    ///
5778    /// C++ class scope makes member names visible throughout the complete
5779    /// class, including a trailing return type that appears before the member
5780    /// alias declaration in source order. Callers must first prove that the
5781    /// reference is inside the candidate's indexed class owner; this helper
5782    /// only relaxes the byte-order predicate while retaining guard and include
5783    /// activation checks.
5784    pub fn external_type_candidate_guard_compatible_in_context(
5785        &self,
5786        analyzer: &CppGraphSource<'_>,
5787        file: &ProjectFile,
5788        candidate: &CodeUnit,
5789        reference: Node<'_>,
5790    ) -> bool {
5791        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5792            return false;
5793        };
5794        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5795
5796        self.visible_identifier_candidates(file, candidate.identifier())
5797            .filter(|peer| same_logical_symbol(candidate, peer))
5798            .any(|peer| {
5799                declaration_guard_requirements(analyzer, self.cpp, peer)
5800                    .into_iter()
5801                    .any(|(declaration_byte, declaration_guards)| {
5802                        if peer.source() == file {
5803                            let (start, end) = if declaration_byte <= reference.start_byte() {
5804                                (declaration_byte, reference.start_byte())
5805                            } else {
5806                                (reference.start_byte(), declaration_byte)
5807                            };
5808                            return guard_requirements_hold_at_reference(
5809                                &declaration_guards,
5810                                reference_guards.as_ref(),
5811                            ) && self.preprocessor_guards_stable_between(
5812                                file,
5813                                start,
5814                                end,
5815                                &declaration_guards,
5816                            );
5817                        }
5818                        self.foreign_declaration_reachable_at_reference(
5819                            file,
5820                            prepared.as_ref(),
5821                            peer.source(),
5822                            &declaration_guards,
5823                            reference_guards.as_ref(),
5824                            reference.start_byte(),
5825                        )
5826                    })
5827            })
5828    }
5829
5830    /// Whether a same-file callable declaration is nameable from `reference`
5831    /// after deliberately relaxing declaration-before-reference ordering.
5832    ///
5833    /// Ordinary lookup still requires an earlier declaration. Definition
5834    /// navigation for incomplete C translation units may recover a later
5835    /// definition, but only when it is at file scope and its preprocessor
5836    /// requirements hold at the call (#2404).
5837    pub fn same_file_callable_guard_compatible_ignoring_order(
5838        &self,
5839        analyzer: &CppGraphSource<'_>,
5840        file: &ProjectFile,
5841        candidate: &CodeUnit,
5842        reference: Node<'_>,
5843    ) -> bool {
5844        if candidate.source() != file || !candidate.is_callable() {
5845            return false;
5846        }
5847        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5848            return false;
5849        };
5850        let guards = OnceCell::new();
5851        let context = CallableReferenceContext {
5852            file,
5853            position: Some(CallableReferencePosition {
5854                prepared: prepared.as_ref(),
5855                byte: reference.start_byte(),
5856                guards: &guards,
5857            }),
5858        };
5859        analyzer
5860            .declaration_source_occurrences(candidate)
5861            .into_iter()
5862            .flatten()
5863            .filter_map(|occurrence| {
5864                let guards = cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
5865                Some(callable_guard_requirements_are_visible(
5866                    &guards,
5867                    occurrence.fact.callable_guard_completion_byte,
5868                    &context,
5869                ))
5870            })
5871            .any(|visible| visible)
5872    }
5873
5874    pub fn type_candidate_may_be_visible_before_reference(
5875        &self,
5876        analyzer: &CppGraphSource<'_>,
5877        file: &ProjectFile,
5878        candidate: &CodeUnit,
5879        reference_byte: usize,
5880    ) -> bool {
5881        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5882            return false;
5883        };
5884        let root = prepared.tree().root_node();
5885        let end_byte = reference_byte
5886            .saturating_add(1)
5887            .min(prepared.source().len());
5888        let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
5889            return false;
5890        };
5891        self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
5892    }
5893
5894    pub fn preprocessor_guards_stable_between(
5895        &self,
5896        file: &ProjectFile,
5897        start_byte: usize,
5898        end_byte: usize,
5899        guards: &HashSet<PreprocessorGuard>,
5900    ) -> bool {
5901        if guards.is_empty() || start_byte >= end_byte {
5902            return true;
5903        }
5904        let cell = self.macro_event_cell(file);
5905        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5906        let mut visited = HashSet::from_iter([file.clone()]);
5907        !events.iter().any(|event| {
5908            event.byte() >= start_byte
5909                && event.byte() < end_byte
5910                && self.macro_event_may_mutate_guards(event, guards, &mut visited)
5911        })
5912    }
5913
5914    fn macro_event_may_mutate_guards(
5915        &self,
5916        event: &MacroEvent,
5917        guards: &HashSet<PreprocessorGuard>,
5918        visited: &mut HashSet<ProjectFile>,
5919    ) -> bool {
5920        match event {
5921            MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
5922                guards.iter().any(|guard| guard.may_depend_on_macro(name))
5923            }
5924            MacroEvent::Include { targets, .. } => {
5925                targets.is_empty()
5926                    || targets
5927                        .iter()
5928                        .any(|target| self.source_may_mutate_guards(target, guards, visited))
5929            }
5930            MacroEvent::Invalidate { .. } => true,
5931        }
5932    }
5933
5934    fn source_may_mutate_guards(
5935        &self,
5936        file: &ProjectFile,
5937        guards: &HashSet<PreprocessorGuard>,
5938        visited: &mut HashSet<ProjectFile>,
5939    ) -> bool {
5940        if !visited.insert(file.clone()) {
5941            return false;
5942        }
5943        let cell = self.macro_event_cell(file);
5944        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5945        events
5946            .iter()
5947            .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
5948    }
5949
5950    pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
5951        let normalized = normalize_reference_name(raw_name)?;
5952        self.type_candidates(file, &normalized)
5953            .into_iter()
5954            .next()
5955            .cloned()
5956    }
5957
5958    /// Mirror forward navigation's visible-name fallback for a bare parameter
5959    /// type after lexical owner and inheritance lookup is exhausted.
5960    ///
5961    /// Generated or otherwise unindexed base classes can hide the alias that
5962    /// makes a parameter type valid C++. Accept the fallback only when every
5963    /// include-visible class or alias with that spelling canonicalizes to one
5964    /// logical type. A shadowing local type resolves lexically before this
5965    /// path, while distinct visible types keep the result ambiguous.
5966    pub fn unique_visible_parameter_type_fallback(
5967        &self,
5968        analyzer: &CppGraphSource<'_>,
5969        file: &ProjectFile,
5970        node: Node<'_>,
5971        source: &str,
5972    ) -> Option<CodeUnit> {
5973        if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
5974            return None;
5975        }
5976        let name = node_text(node, source);
5977        let candidates = self
5978            .visible_identifier_candidates(file, name)
5979            .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
5980            .filter(|candidate| {
5981                self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
5982            })
5983            .collect::<Vec<_>>();
5984        self.unique_canonical_type_candidate(analyzer, file, &candidates)
5985    }
5986
5987    pub fn resolve_type_node_result(
5988        &self,
5989        file: &ProjectFile,
5990        node: Node<'_>,
5991        source: &str,
5992    ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
5993        let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
5994            return Ok(None);
5995        };
5996        let Some(arguments) = cpp_template_reference_arguments(node, source) else {
5997            return Ok(Some(primary));
5998        };
5999        self.resolve_template_arguments(file, primary, &arguments)
6000            .map(Some)
6001    }
6002
6003    pub fn resolve_type_node_primary(
6004        &self,
6005        file: &ProjectFile,
6006        node: Node<'_>,
6007        source: &str,
6008    ) -> Option<CodeUnit> {
6009        let components = cpp_type_name_components(node, source)?;
6010        self.resolve_type(file, &components.join("::"))
6011    }
6012
6013    pub fn resolve_template_arguments(
6014        &self,
6015        file: &ProjectFile,
6016        primary: CodeUnit,
6017        arguments: &[CppTemplateExpression],
6018    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6019        self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
6020    }
6021
6022    fn resolve_template_arguments_inner(
6023        &self,
6024        file: &ProjectFile,
6025        primary: CodeUnit,
6026        arguments: &[CppTemplateExpression],
6027        seen_aliases: &mut HashSet<CodeUnit>,
6028    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6029        if let Some(metadata) = self.cpp_template_metadata.get(&primary)
6030            && let Some(alias_target) = &metadata.alias_target
6031        {
6032            if !seen_aliases.insert(primary.clone()) {
6033                return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
6034            }
6035            let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
6036                .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6037            let target_name = alias_target.components.join("::");
6038            let target_primary = if alias_target.global {
6039                unique_logical_type_candidate(self.type_candidates(file, &target_name))
6040            } else {
6041                self.resolve_unique_type_for_declaration(file, &primary, &target_name)
6042            };
6043            let Some(target_primary) = target_primary else {
6044                // A dependent or external RHS cannot be canonicalized from the
6045                // indexed graph. Preserve the alias's direct identity instead
6046                // of inventing a target from its source spelling.
6047                return Ok(primary);
6048            };
6049            let Some(target_arguments) = &alias_target.arguments else {
6050                return Ok(target_primary);
6051            };
6052            let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
6053                .ok_or(CppTemplateResolutionError::Substitution)?;
6054            return self.resolve_template_arguments_inner(
6055                file,
6056                target_primary,
6057                &target_arguments,
6058                seen_aliases,
6059            );
6060        }
6061
6062        let primary_fq_name = self
6063            .cpp_template_metadata
6064            .get(&primary)
6065            .map(|metadata| metadata.primary_fq_name.clone())
6066            .unwrap_or_else(|| primary.fq_name());
6067        let has_specialization_metadata = self
6068            .cpp_template_families
6069            .get(&primary_fq_name)
6070            .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
6071        if !has_specialization_metadata {
6072            return Ok(primary);
6073        }
6074        self.select_template_specialization(file, &primary, arguments)
6075    }
6076
6077    fn select_template_specialization(
6078        &self,
6079        file: &ProjectFile,
6080        resolved: &CodeUnit,
6081        explicit_arguments: &[CppTemplateExpression],
6082    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6083        let primary_fq_name = self
6084            .cpp_template_metadata
6085            .get(resolved)
6086            .map(|metadata| metadata.primary_fq_name.clone())
6087            .unwrap_or_else(|| resolved.fq_name());
6088        let family = self
6089            .cpp_template_families
6090            .get(&primary_fq_name)
6091            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6092        let primary_candidates = family
6093            .iter()
6094            .filter_map(|unit| {
6095                let metadata = self.cpp_template_metadata.get(unit)?;
6096                (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
6097            })
6098            .collect::<Vec<_>>();
6099        let primary_unit = primary_candidates
6100            .iter()
6101            .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
6102            .or_else(|| {
6103                primary_candidates
6104                    .iter()
6105                    .map(|(unit, _)| *unit)
6106                    .min_by_key(|unit| {
6107                        (
6108                            unit.source().to_string(),
6109                            unit.signature().unwrap_or_default(),
6110                        )
6111                    })
6112            })
6113            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6114        let primary_parameters =
6115            cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
6116                .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6117        let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
6118            .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6119
6120        let mut applicable = Vec::new();
6121        for unit in family {
6122            let Some(metadata) = self.cpp_template_metadata.get(unit) else {
6123                continue;
6124            };
6125            if metadata.is_primary() || !self.is_visible(file, unit) {
6126                continue;
6127            }
6128            if !cpp_specialization_matches(metadata, &expanded) {
6129                continue;
6130            }
6131            applicable.push((unit, metadata));
6132        }
6133        if applicable.is_empty() {
6134            return Ok(primary_unit.clone());
6135        }
6136
6137        // A scalar constraint count cannot represent C++ partial ordering:
6138        // e.g. `<T*, U>` and `<T, int>` are incomparable for `<int*, int>`.
6139        // Select only a logical candidate whose structural pattern is strictly
6140        // more specialized than every other distinct applicable candidate.
6141        let winners = applicable
6142            .iter()
6143            .filter(|(candidate, candidate_metadata)| {
6144                applicable.iter().all(|(other, other_metadata)| {
6145                    same_visible_symbol(candidate, other)
6146                        || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
6147                })
6148            })
6149            .copied()
6150            .collect::<Vec<_>>();
6151        let Some((selected, _)) = winners.first() else {
6152            // Mutually incomparable applicable candidates: every one of them
6153            // is a live contender.
6154            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6155                candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
6156            });
6157        };
6158        if winners
6159            .iter()
6160            .any(|(unit, _)| !same_visible_symbol(unit, selected))
6161        {
6162            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6163                candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
6164            });
6165        }
6166        Ok((*selected).clone())
6167    }
6168
6169    pub fn resolve_type_components_lexically(
6170        &self,
6171        analyzer: &CppGraphSource<'_>,
6172        file: &ProjectFile,
6173        components: &[String],
6174        global: bool,
6175        lexical_scope: &[String],
6176    ) -> LexicalTypeResolution {
6177        self.resolve_type_components_lexically_inner(
6178            analyzer,
6179            file,
6180            components,
6181            global,
6182            lexical_scope,
6183            TypeCandidateResolution::Canonical,
6184        )
6185    }
6186
6187    pub fn resolve_type_components_lexically_for_forward(
6188        &self,
6189        analyzer: &CppGraphSource<'_>,
6190        file: &ProjectFile,
6191        components: &[String],
6192        global: bool,
6193        lexical_scope: &[String],
6194    ) -> LexicalTypeResolution {
6195        self.resolve_type_components_lexically_inner(
6196            analyzer,
6197            file,
6198            components,
6199            global,
6200            lexical_scope,
6201            TypeCandidateResolution::PreserveAlias,
6202        )
6203    }
6204
6205    pub fn resolve_type_components_lexically_for_target(
6206        &self,
6207        analyzer: &CppGraphSource<'_>,
6208        file: &ProjectFile,
6209        components: &[String],
6210        global: bool,
6211        lexical_scope: &[String],
6212        target: &CodeUnit,
6213    ) -> LexicalTypeResolution {
6214        #[cfg(any(test, feature = "test-support"))]
6215        self.target_preserving_type_resolution_count
6216            .fetch_add(1, Ordering::Relaxed);
6217        self.resolve_type_components_lexically_inner(
6218            analyzer,
6219            file,
6220            components,
6221            global,
6222            lexical_scope,
6223            TypeCandidateResolution::PreserveTarget(target),
6224        )
6225    }
6226
6227    pub fn coarse_unqualified_type_reference_may_resolve(
6228        &self,
6229        file: &ProjectFile,
6230        name: &str,
6231    ) -> bool {
6232        if name.is_empty() {
6233            return true;
6234        }
6235        self.visible_identifier_candidates(file, name)
6236            .any(|candidate| {
6237                candidate.kind() == CodeUnitType::Class
6238                    || declared_type_alias(&self.cpp_source(), candidate)
6239            })
6240            || self.visible_parser_alias_name_is_visible(file, name)
6241    }
6242
6243    #[allow(clippy::too_many_arguments)]
6244    pub fn structured_type_reference_may_resolve_to_target(
6245        &self,
6246        analyzer: &CppGraphSource<'_>,
6247        file: &ProjectFile,
6248        components: &[String],
6249        global: bool,
6250        lexical_scope: &[String],
6251        target: &CodeUnit,
6252    ) -> bool {
6253        if components.is_empty() {
6254            return true;
6255        }
6256        let Some(terminal) = components.last() else {
6257            return true;
6258        };
6259        let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
6260            .map(|qualified| qualified.join("::"))
6261            .collect::<Vec<_>>();
6262        let target_name = cpp_name_for(target);
6263        if qualified_tiers
6264            .iter()
6265            .any(|qualified| qualified == &target_name)
6266        {
6267            return true;
6268        }
6269
6270        let mut saw_shape_candidate = false;
6271        for candidate in self.visible_identifier_candidates(file, terminal) {
6272            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6273            {
6274                continue;
6275            }
6276            let candidate_name = cpp_name_for(candidate);
6277            let shape_matches = if global || components.len() > 1 {
6278                qualified_tiers
6279                    .iter()
6280                    .any(|qualified| qualified == &candidate_name)
6281            } else {
6282                true
6283            };
6284            if !shape_matches {
6285                continue;
6286            }
6287            saw_shape_candidate = true;
6288            if same_visible_symbol(candidate, target)
6289                || self.c_tag_declaration_family_matches_target(
6290                    analyzer,
6291                    file,
6292                    std::slice::from_ref(&candidate),
6293                    target,
6294                )
6295                || self.compatible_primary_template_redeclarations(candidate, target)
6296                || (declared_type_alias(analyzer, candidate)
6297                    && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
6298            {
6299                return true;
6300            }
6301        }
6302
6303        !saw_shape_candidate
6304    }
6305
6306    /// A C tag's forward declaration and complete definition are one logical
6307    /// type even when their indexed signatures and source files differ. Keep
6308    /// this identity narrow: the complete declaration must be a top-level C
6309    /// tag, every visible candidate must be its physical forward declaration,
6310    /// and the parsed tag kind must agree. Different FQNs, competing complete
6311    /// definitions, aliases, and struct/union mismatches remain ambiguous.
6312    fn cached_c_tag_kind(
6313        &self,
6314        analyzer: &CppGraphSource<'_>,
6315        candidate: &CodeUnit,
6316    ) -> Option<CppCTagKind> {
6317        if let Some(kind) = self
6318            .c_tag_kind_cache
6319            .lock()
6320            .expect("C tag kind cache poisoned")
6321            .get(candidate)
6322        {
6323            return *kind;
6324        }
6325        let kind = indexed_c_tag_kind(analyzer, candidate);
6326        self.c_tag_kind_cache
6327            .lock()
6328            .expect("C tag kind cache poisoned")
6329            .insert(candidate.clone(), kind);
6330        kind
6331    }
6332
6333    fn cached_unique_c_tag_complete_definition(
6334        &self,
6335        analyzer: &CppGraphSource<'_>,
6336        target: &CodeUnit,
6337        target_tag: CppCTagKind,
6338    ) -> Option<CodeUnit> {
6339        if let Some(definition) = self
6340            .c_tag_complete_definition_cache
6341            .lock()
6342            .expect("C tag complete-definition cache poisoned")
6343            .get(target)
6344        {
6345            return definition.clone();
6346        }
6347        let complete_definitions = analyzer
6348            .definitions(&target.fq_name())
6349            .filter(|candidate| {
6350                candidate.is_class()
6351                    && !declared_type_alias(analyzer, candidate)
6352                    && is_c_source_file(candidate.source())
6353                    && analyzer.parent_of(candidate).is_none()
6354                    && cpp_class_declaration_strength(analyzer, candidate)
6355                        == CppClassDeclarationStrength::Full
6356                    && self.cached_c_tag_kind(analyzer, candidate) == Some(target_tag)
6357            })
6358            .collect::<HashSet<_>>();
6359        let definition = (complete_definitions.len() == 1)
6360            .then(|| complete_definitions.into_iter().next())
6361            .flatten()
6362            .filter(|candidate| same_visible_symbol(candidate, target));
6363        self.c_tag_complete_definition_cache
6364            .lock()
6365            .expect("C tag complete-definition cache poisoned")
6366            .insert(target.clone(), definition.clone());
6367        definition
6368    }
6369
6370    pub fn c_tag_declaration_family_matches_target(
6371        &self,
6372        analyzer: &CppGraphSource<'_>,
6373        visible_from: &ProjectFile,
6374        candidates: &[&CodeUnit],
6375        target: &CodeUnit,
6376    ) -> bool {
6377        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
6378            let candidate_evidence = candidates
6379                .iter()
6380                .map(|candidate| {
6381                    (
6382                        candidate.fq_name(),
6383                        candidate.source().rel_path().to_path_buf(),
6384                        cpp_class_declaration_strength(analyzer, candidate),
6385                        indexed_c_tag_kind(analyzer, candidate),
6386                        self.is_physically_visible(visible_from, candidate),
6387                    )
6388                })
6389                .collect::<Vec<_>>();
6390            eprintln!(
6391                "BIFROST_CPP_C_TAG_FAMILY_STATS visible_from={} target=({}, {}, {:?}, {:?}) candidates={candidate_evidence:?}",
6392                visible_from.rel_path().display(),
6393                target.fq_name(),
6394                target.source().rel_path().display(),
6395                cpp_class_declaration_strength(analyzer, target),
6396                indexed_c_tag_kind(analyzer, target),
6397            );
6398        }
6399        if candidates.is_empty()
6400            || !target.is_class()
6401            || declared_type_alias(analyzer, target)
6402            || !is_c_source_file(target.source())
6403            || analyzer.parent_of(target).is_some()
6404            || cpp_class_declaration_strength(analyzer, target) != CppClassDeclarationStrength::Full
6405        {
6406            return false;
6407        }
6408        let Some(target_tag) = self.cached_c_tag_kind(analyzer, target) else {
6409            return false;
6410        };
6411        if self
6412            .cached_unique_c_tag_complete_definition(analyzer, target, target_tag)
6413            .is_none()
6414        {
6415            return false;
6416        }
6417        let mut saw_visible_forward = false;
6418        for candidate in candidates.iter().copied() {
6419            if candidate == target {
6420                continue;
6421            }
6422            if !candidate.is_class()
6423                || declared_type_alias(analyzer, candidate)
6424                || candidate.fq_name() != target.fq_name()
6425                || analyzer.parent_of(candidate).is_some()
6426                || cpp_class_declaration_strength(analyzer, candidate)
6427                    != CppClassDeclarationStrength::Forward
6428                || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6429                || !self.is_physically_visible(visible_from, candidate)
6430            {
6431                return false;
6432            }
6433            saw_visible_forward = true;
6434        }
6435        saw_visible_forward
6436    }
6437
6438    /// Collapse one visible complete C tag and its visible forward declarations
6439    /// before ordinary lexical resolution sees their different signatures as
6440    /// competing types. A second complete definition, a different FQN/tag
6441    /// kind, or an unknown declaration shape remains ambiguous.
6442    fn unique_c_tag_declaration_family(
6443        &self,
6444        analyzer: &CppGraphSource<'_>,
6445        visible_from: &ProjectFile,
6446        candidates: &[&CodeUnit],
6447    ) -> Option<CodeUnit> {
6448        let first = candidates.first()?;
6449        let target_fq_name = first.fq_name();
6450        let target_tag = self.cached_c_tag_kind(analyzer, first)?;
6451        let mut full = None;
6452        let mut saw_forward = false;
6453        for candidate in candidates.iter().copied() {
6454            if !candidate.is_class()
6455                || declared_type_alias(analyzer, candidate)
6456                || candidate.fq_name() != target_fq_name
6457                || analyzer.parent_of(candidate).is_some()
6458                || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6459            {
6460                return None;
6461            }
6462            match cpp_class_declaration_strength(analyzer, candidate) {
6463                CppClassDeclarationStrength::Full
6464                    if is_c_source_file(candidate.source())
6465                        && full.replace(candidate.clone()).is_none() => {}
6466                CppClassDeclarationStrength::Forward
6467                    if self.is_physically_visible(visible_from, candidate) =>
6468                {
6469                    saw_forward = true
6470                }
6471                _ => return None,
6472            }
6473        }
6474        if saw_forward { full } else { None }
6475    }
6476
6477    pub fn target_preserving_reference_namespace(
6478        &self,
6479        analyzer: &CppGraphSource<'_>,
6480        file: &ProjectFile,
6481        identifier: &str,
6482        target: &CodeUnit,
6483    ) -> Option<Vec<String>> {
6484        let mut namespace = None;
6485        for candidate in self.visible_identifier_candidates(file, identifier) {
6486            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6487            {
6488                continue;
6489            }
6490            if !(same_visible_symbol(candidate, target)
6491                || self.compatible_primary_template_redeclarations(candidate, target)
6492                || declared_type_alias(analyzer, candidate)
6493                    && self.structured_alias_primary_preserves_target(
6494                        analyzer, file, candidate, target,
6495                    ))
6496            {
6497                continue;
6498            }
6499            if namespace
6500                .as_ref()
6501                .is_some_and(|existing| existing != candidate.package_name())
6502            {
6503                return None;
6504            }
6505            namespace = Some(candidate.package_name().to_string());
6506        }
6507        let namespace = namespace?;
6508        Some(
6509            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6510                brokk_bifrost_core::analyzer::Language::Cpp,
6511                &namespace,
6512            ),
6513        )
6514    }
6515
6516    pub fn resolve_imported_type_candidate(
6517        &self,
6518        analyzer: &CppGraphSource<'_>,
6519        file: &ProjectFile,
6520        target: &CodeUnit,
6521        target_components: &[String],
6522        direct_target: Option<&CodeUnit>,
6523        preserve_alias: bool,
6524    ) -> LexicalTypeResolution {
6525        let candidates = [target];
6526        let resolution = if preserve_alias {
6527            TypeCandidateResolution::PreserveAlias
6528        } else {
6529            direct_target.map_or(
6530                TypeCandidateResolution::Canonical,
6531                TypeCandidateResolution::PreserveTarget,
6532            )
6533        };
6534        // One candidate goes in, so a failure here is never "choose one of
6535        // these": it is the alias chain leaving the index, which must answer
6536        // missing rather than ambiguous (#1828).
6537        match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6538            Ok(unit) => LexicalTypeResolution::Resolved {
6539                unit,
6540                components: target_components.to_vec(),
6541                candidates: vec![target.clone()],
6542            },
6543            Err(failure) => failure.lexical_resolution(),
6544        }
6545    }
6546
6547    fn resolve_type_components_lexically_inner(
6548        &self,
6549        analyzer: &CppGraphSource<'_>,
6550        file: &ProjectFile,
6551        components: &[String],
6552        global: bool,
6553        lexical_scope: &[String],
6554        resolution: TypeCandidateResolution<'_>,
6555    ) -> LexicalTypeResolution {
6556        if components.is_empty() {
6557            return LexicalTypeResolution::Missing;
6558        }
6559        // A C++ class injects its own name into the class scope.  The indexed
6560        // FqName for that declaration is the class path itself (for example,
6561        // `n::raw_hash_set`), not a synthetic child named
6562        // `n::raw_hash_set::raw_hash_set`.  Ordinary lexical tiers append the
6563        // requested identifier to every scope component, so they cannot
6564        // represent that injected binding when the enclosing class is the
6565        // closest scope.  Recover the binding from the structured class path
6566        // before allowing lookup to fall through to an outer same-spelled
6567        // declaration.
6568        let mut injected = self.resolve_injected_class_name(
6569            analyzer,
6570            file,
6571            components,
6572            global,
6573            lexical_scope,
6574            resolution,
6575        );
6576        for qualified in lexical_component_tiers(components, global, lexical_scope) {
6577            let prefix_len = qualified.len().saturating_sub(components.len());
6578            if injected
6579                .as_ref()
6580                .is_some_and(|(owner_len, _)| prefix_len <= *owner_len)
6581            {
6582                return injected
6583                    .take()
6584                    .expect("injected class resolution was just present")
6585                    .1;
6586            }
6587            let qualified_name = qualified.join("::");
6588            let candidates = self
6589                .type_candidates(file, &qualified_name)
6590                .into_iter()
6591                .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6592                .collect::<Vec<_>>();
6593            if candidates.is_empty() {
6594                if !global && components.len() == 1 {
6595                    match self.resolve_inherited_type_for_lexical_scope(
6596                        analyzer,
6597                        file,
6598                        &qualified[..prefix_len],
6599                        &components[0],
6600                        resolution,
6601                    ) {
6602                        LexicalTypeResolution::Missing => {}
6603                        inherited => return inherited,
6604                    }
6605                }
6606                continue;
6607            }
6608            let candidates =
6609                self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6610            let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6611                Ok(unit) => unit,
6612                Err(failure) => return failure.lexical_resolution(),
6613            };
6614            return LexicalTypeResolution::Resolved {
6615                unit,
6616                components: qualified,
6617                candidates: candidates.into_iter().cloned().collect(),
6618            };
6619        }
6620        LexicalTypeResolution::Missing
6621    }
6622
6623    fn resolve_injected_class_name(
6624        &self,
6625        analyzer: &CppGraphSource<'_>,
6626        file: &ProjectFile,
6627        components: &[String],
6628        global: bool,
6629        lexical_scope: &[String],
6630        resolution: TypeCandidateResolution<'_>,
6631    ) -> Option<(usize, LexicalTypeResolution)> {
6632        if global
6633            || components.len() != 1
6634            || file.rel_path().extension().is_some_and(|ext| ext == "c")
6635            || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
6636        {
6637            return None;
6638        }
6639        let name = components.first()?;
6640        let mut matches: Vec<&CodeUnit> = Vec::new();
6641        let mut owner_len = 0;
6642        for candidate in self.visible_identifier_candidates(file, name) {
6643            if !candidate.is_class()
6644                || declared_type_alias(analyzer, candidate)
6645                || candidate.identifier() != name
6646            {
6647                continue;
6648            }
6649            let candidate_scope = canonical_cpp_scope_components(candidate);
6650            if candidate_scope.len() > lexical_scope.len()
6651                || !lexical_scope.starts_with(&candidate_scope)
6652                || candidate_scope.last().is_none_or(|last| last != name)
6653            {
6654                continue;
6655            }
6656            if candidate_scope.len() > owner_len {
6657                owner_len = candidate_scope.len();
6658                matches.clear();
6659            }
6660            if candidate_scope.len() == owner_len
6661                && !matches
6662                    .iter()
6663                    .any(|existing| same_logical_symbol(existing, candidate))
6664            {
6665                matches.push(candidate);
6666            }
6667        }
6668        if matches.is_empty() {
6669            return None;
6670        }
6671        // The exact current class boundary is the most important injected-name
6672        // case. Ordinary lexical lookup appends the requested name and can
6673        // otherwise select a synthetic constructor-shaped child such as
6674        // `Portal::Impl::Impl` instead of the enclosing `Portal::Impl` class.
6675        // The global and single-component gates above keep qualified receiver
6676        // and static-qualifier contexts out of this recovery.
6677        let owner_components = lexical_scope[..owner_len].to_vec();
6678        let matches = self.candidates_for_type_resolution(analyzer, file, &matches, resolution);
6679        let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
6680            Ok(unit) => LexicalTypeResolution::Resolved {
6681                unit,
6682                components: owner_components,
6683                candidates: matches.into_iter().cloned().collect(),
6684            },
6685            Err(failure) => failure.lexical_resolution(),
6686        };
6687        Some((owner_len, resolution))
6688    }
6689
6690    fn resolve_inherited_type_for_lexical_scope(
6691        &self,
6692        analyzer: &CppGraphSource<'_>,
6693        file: &ProjectFile,
6694        lexical_scope: &[String],
6695        name: &str,
6696        resolution: TypeCandidateResolution<'_>,
6697    ) -> LexicalTypeResolution {
6698        let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
6699            return LexicalTypeResolution::Missing;
6700        };
6701        let lexical_owner_name = lexical_scope.join("::");
6702        if lexical_owner_name.is_empty() {
6703            return LexicalTypeResolution::Missing;
6704        }
6705        let owner_candidates = self
6706            .type_candidates(file, &lexical_owner_name)
6707            .into_iter()
6708            .filter(|candidate| {
6709                canonical_cpp_name_matches(candidate, &lexical_owner_name)
6710                    && !declared_type_alias(analyzer, candidate)
6711            })
6712            .collect::<Vec<_>>();
6713        if owner_candidates.is_empty() {
6714            return LexicalTypeResolution::Missing;
6715        }
6716        // A visible forward declaration and the physical class definition share
6717        // one FQN, but only the definition owns hierarchy facts. When lookup is
6718        // physically inside that definition, do not let an earlier header
6719        // forward declaration erase its base edges (#2240).
6720        let physical_owner_candidates = owner_candidates
6721            .iter()
6722            .copied()
6723            .filter(|candidate| candidate.source() == file)
6724            .collect::<Vec<_>>();
6725        let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
6726            owner_candidates
6727        } else {
6728            physical_owner_candidates
6729        };
6730        let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
6731            return LexicalTypeResolution::Ambiguous;
6732        };
6733
6734        let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
6735        let mut visited_owners = HashSet::default();
6736        while !frontier.is_empty() {
6737            let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
6738            let mut next_frontier = Vec::new();
6739            for owner in frontier {
6740                if !visited_owners.insert(owner.fq_name()) {
6741                    continue;
6742                }
6743                let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
6744                let candidates = self
6745                    .type_candidates(file, &qualified_name)
6746                    .into_iter()
6747                    .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6748                    .collect::<Vec<_>>();
6749                if candidates.is_empty() {
6750                    for ancestor in hierarchy.get_direct_ancestors(&owner) {
6751                        if !next_frontier
6752                            .iter()
6753                            .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
6754                        {
6755                            next_frontier.push(ancestor);
6756                        }
6757                    }
6758                    continue;
6759                }
6760                let candidates =
6761                    self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6762                let unit =
6763                    match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6764                        Ok(unit) => unit,
6765                        Err(failure) => return failure.lexical_resolution(),
6766                    };
6767                level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
6768            }
6769            if let Some((unit, candidates)) = level_matches.first().cloned() {
6770                let Some(first_declaration) = candidates.first() else {
6771                    return LexicalTypeResolution::Ambiguous;
6772                };
6773                if !level_matches.iter().all(|(_, declarations)| {
6774                    declarations
6775                        .iter()
6776                        .all(|declaration| same_logical_symbol(first_declaration, declaration))
6777                }) {
6778                    return LexicalTypeResolution::Ambiguous;
6779                }
6780                let mut components = lexical_scope.to_vec();
6781                components.push(name.to_string());
6782                return LexicalTypeResolution::Resolved {
6783                    unit,
6784                    components,
6785                    candidates,
6786                };
6787            }
6788            frontier = next_frontier;
6789        }
6790        LexicalTypeResolution::Missing
6791    }
6792
6793    /// Resolve a base class through its injected class name at the nearest
6794    /// inheritance tier. Distinct same-named bases at that tier are ambiguous.
6795    ///
6796    /// A base whose canonical full definition cannot be pinned from `file` -
6797    /// a forward declaration the include closure completes with two different
6798    /// full definitions, or an alias chain that leaves the index - stops the
6799    /// walk only when that base is spelled `injected_name`. The mem-initializer
6800    /// names a base by that base's own injected class name, so a base spelled
6801    /// differently can never be the one it names, whichever definition it would
6802    /// have turned out to be; aborting the level on its account instead loses
6803    /// the sibling base that *is* named (#2543). A base that is spelled
6804    /// `injected_name` still fails closed, because choosing a deeper same-named
6805    /// ancestor over it would bind the initializer to the wrong constructor.
6806    /// The skipped base carries its own ancestors out of the walk with it: with
6807    /// no canonical unit, the repeated-base accounting below cannot tell one
6808    /// inherited path through it from two.
6809    pub fn inherited_injected_class_owner(
6810        &self,
6811        analyzer: &CppGraphSource<'_>,
6812        file: &ProjectFile,
6813        enclosing_owner: &CodeUnit,
6814        injected_name: &str,
6815    ) -> Option<CodeUnit> {
6816        let hierarchy = analyzer.type_hierarchy_provider()?;
6817        let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
6818        let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
6819        while !frontier.is_empty() {
6820            let mut level_matches = Vec::new();
6821            let mut next_frontier = Vec::new();
6822            for raw_owner in frontier {
6823                let Some(owner) = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
6824                else {
6825                    if raw_owner.identifier() == injected_name {
6826                        return None;
6827                    }
6828                    continue;
6829                };
6830                let propagated = propagated_counts.entry(owner.clone()).or_default();
6831                if *propagated == 2 {
6832                    continue;
6833                }
6834                *propagated += 1;
6835                if owner.identifier() == injected_name {
6836                    level_matches.push(owner.clone());
6837                }
6838                next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
6839            }
6840            match level_matches.as_slice() {
6841                [owner] => return Some(owner.clone()),
6842                [_, ..] => return None,
6843                [] => {}
6844            }
6845            frontier = next_frontier;
6846        }
6847        None
6848    }
6849
6850    /// The one type the candidates name under `resolution`, or why they do not
6851    /// name one. The two preserving modes only ever reject candidates that
6852    /// disagree with each other, which is ambiguity; canonicalization can also
6853    /// fail because the alias chain leaves the index (#1828).
6854    fn resolve_type_candidates(
6855        &self,
6856        analyzer: &CppGraphSource<'_>,
6857        file: &ProjectFile,
6858        candidates: &[&CodeUnit],
6859        resolution: TypeCandidateResolution<'_>,
6860    ) -> Result<CodeUnit, TypeCandidateFailure> {
6861        if !matches!(resolution, TypeCandidateResolution::PreserveTarget(_))
6862            && let Some(unit) = self.unique_c_tag_declaration_family(analyzer, file, candidates)
6863        {
6864            return Ok(unit);
6865        }
6866        match resolution {
6867            TypeCandidateResolution::Canonical => {
6868                self.canonical_type_candidate_resolution(analyzer, file, candidates)
6869            }
6870            TypeCandidateResolution::PreserveAlias => {
6871                // A generated index can retain identical alias spellings from
6872                // mutually exclusive headers. When the reference file
6873                // physically reaches exactly one of those source declarations,
6874                // include closure is the structured evidence that selects it;
6875                // treating the two source spellings as an overload set makes a
6876                // reachable alias appear ambiguous (#1844).
6877                let same_fqn_alias_family = candidates.len() > 1
6878                    && candidates.iter().all(|candidate| {
6879                        declared_type_alias(analyzer, candidate)
6880                            && same_logical_symbol(candidates[0], candidate)
6881                    })
6882                    && candidates
6883                        .iter()
6884                        .any(|candidate| candidate.source() != candidates[0].source());
6885                if same_fqn_alias_family {
6886                    let physically_visible = candidates
6887                        .iter()
6888                        .copied()
6889                        .filter(|candidate| self.is_physically_visible(file, candidate))
6890                        .collect::<Vec<_>>();
6891                    // The family is one logical declaration only when the
6892                    // reachable spellings agree. Two same-FQN aliases whose
6893                    // written targets differ (`using Choice = Canonical;` in
6894                    // one header, `using Choice = ::Canonical;` in another)
6895                    // are a genuine conflict, and choosing the first indexed
6896                    // one silently binds the reference to an arbitrary owner
6897                    // (#2398). Collapse only a single reachable declaration
6898                    // or reachable declarations with one structured target;
6899                    // everything else stays ambiguous below.
6900                    if let Some(alias) = select_alias_spelling_with_agreed_targets(
6901                        &physically_visible,
6902                        |candidate| self.structured_alias_target(analyzer, candidate),
6903                    ) {
6904                        return Ok(alias.clone());
6905                    }
6906                }
6907                unique_type_candidate_preserving_alias(analyzer, file, candidates)
6908                    .ok_or(TypeCandidateFailure::Ambiguous)
6909            }
6910            TypeCandidateResolution::PreserveTarget(target) => self
6911                .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
6912                .ok_or(TypeCandidateFailure::Ambiguous),
6913        }
6914    }
6915
6916    fn candidates_for_type_resolution<'b>(
6917        &self,
6918        analyzer: &CppGraphSource<'_>,
6919        file: &ProjectFile,
6920        candidates: &[&'b CodeUnit],
6921        resolution: TypeCandidateResolution<'_>,
6922    ) -> Vec<&'b CodeUnit> {
6923        if matches!(resolution, TypeCandidateResolution::PreserveAlias) && candidates.len() > 1 {
6924            let compile_proven = self.compile_proven_type_candidates(analyzer, file, candidates);
6925            if compile_proven.len() == 1 {
6926                return compile_proven;
6927            }
6928        }
6929        candidates.to_vec()
6930    }
6931
6932    /// Narrow a same-name forward lookup to the declaration selected by the
6933    /// translation unit's compile command. The lexical resolver intentionally
6934    /// does not receive a reference node, so this is the only compile-context
6935    /// evidence available at that stage. A single selected candidate is safe:
6936    /// the caller still checks include activation and the reference's own
6937    /// guards before reporting the result as visible.
6938    fn compile_proven_type_candidates<'b>(
6939        &self,
6940        analyzer: &CppGraphSource<'_>,
6941        file: &ProjectFile,
6942        candidates: &[&'b CodeUnit],
6943    ) -> Vec<&'b CodeUnit> {
6944        let proven = self.compile_proven_guards(file);
6945        if proven.is_empty() {
6946            return Vec::new();
6947        }
6948        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6949            return Vec::new();
6950        };
6951        candidates
6952            .iter()
6953            .copied()
6954            .filter(|candidate| {
6955                let declaration_guards =
6956                    declaration_guard_requirements(analyzer, self.cpp, candidate);
6957                if declaration_guards.is_empty() {
6958                    return false;
6959                }
6960                if candidate.source() == file {
6961                    return declaration_guards.iter().any(|(_, required)| {
6962                        guard_requirements_hold_at_reference(required, Some(proven.as_ref()))
6963                    });
6964                }
6965                declaration_guards.iter().any(|(_, required)| {
6966                    self.foreign_declaration_reachable_from_compile_proven_guards(
6967                        file,
6968                        prepared.as_ref(),
6969                        candidate.source(),
6970                        required,
6971                        usize::MAX,
6972                    )
6973                })
6974            })
6975            .collect()
6976    }
6977
6978    pub fn resolve_callable_value_components_lexically(
6979        &self,
6980        analyzer: &CppGraphSource<'_>,
6981        file: &ProjectFile,
6982        owner_components: &[String],
6983        member_name: &str,
6984        global: bool,
6985        lexical_scope: &[String],
6986    ) -> LexicalCallableValueResolution {
6987        if owner_components.is_empty() || member_name.is_empty() {
6988            return LexicalCallableValueResolution::Missing;
6989        }
6990        for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
6991            let owner_name = qualified_owner.join("::");
6992            let type_candidates = self
6993                .type_candidates(file, &owner_name)
6994                .into_iter()
6995                .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
6996                .collect::<Vec<_>>();
6997            let resolved_type = if type_candidates.is_empty() {
6998                None
6999            } else {
7000                let Some(unit) =
7001                    self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
7002                else {
7003                    return LexicalCallableValueResolution::Ambiguous;
7004                };
7005                Some(unit)
7006            };
7007
7008            let mut qualified_callable = qualified_owner;
7009            qualified_callable.push(member_name.to_string());
7010            let callable_name = qualified_callable.join("::");
7011            let free_function = self
7012                .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
7013                .into_iter()
7014                .find(|candidate| {
7015                    canonical_cpp_name_matches(candidate, &callable_name)
7016                        && type_owner_of(analyzer, candidate).is_none()
7017                })
7018                .cloned();
7019
7020            match (resolved_type, free_function) {
7021                (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
7022                (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
7023                (None, Some(function)) => {
7024                    return LexicalCallableValueResolution::FreeFunction(function);
7025                }
7026                (None, None) => {}
7027            }
7028        }
7029        LexicalCallableValueResolution::Missing
7030    }
7031
7032    fn resolve_type_for_declaration(
7033        &self,
7034        visible_from: &ProjectFile,
7035        declaration: &CodeUnit,
7036        raw_name: &str,
7037    ) -> Option<CodeUnit> {
7038        let normalized = normalize_reference_name(raw_name)?;
7039        if !normalized.contains("::")
7040            && let Some(namespace) = cpp_namespace_for(declaration)
7041        {
7042            for prefix in namespace_prefixes(&namespace) {
7043                let qualified = format!("{prefix}::{normalized}");
7044                if let Some(unit) = self
7045                    .type_candidates(visible_from, &qualified)
7046                    .into_iter()
7047                    .next()
7048                {
7049                    return Some(unit.clone());
7050                }
7051            }
7052        }
7053        self.resolve_type(visible_from, raw_name)
7054    }
7055
7056    fn resolve_unique_canonical_type_for_declaration(
7057        &self,
7058        analyzer: &CppGraphSource<'_>,
7059        visible_from: &ProjectFile,
7060        declaration: &CodeUnit,
7061        raw_name: &str,
7062    ) -> Option<CodeUnit> {
7063        let current = self.resolve_defining_type_for_declaration(
7064            analyzer,
7065            visible_from,
7066            declaration,
7067            raw_name,
7068        )?;
7069        self.canonical_type_resolution(analyzer, visible_from, &current)
7070            .ok()
7071    }
7072
7073    pub fn canonical_type_unit(
7074        &self,
7075        analyzer: &CppGraphSource<'_>,
7076        visible_from: &ProjectFile,
7077        unit: &CodeUnit,
7078    ) -> Option<CodeUnit> {
7079        self.canonical_type_resolution(analyzer, visible_from, unit)
7080            .ok()
7081    }
7082
7083    /// Follow an alias only when it is visible at `reference`.
7084    ///
7085    /// The consumer need not spell the alias target. In particular, a
7086    /// conditional include can make `PublicPtr` visible at the reference while
7087    /// ordinary physical-include visibility is false. Prove the public alias
7088    /// with the reference's guard environment, then use the existing structured
7089    /// alias-chain resolver over the consumer's bounded declaration index.
7090    pub fn canonical_type_unit_in_context(
7091        &self,
7092        analyzer: &CppGraphSource<'_>,
7093        visible_from: &ProjectFile,
7094        reference: Node<'_>,
7095        unit: &CodeUnit,
7096    ) -> Option<CodeUnit> {
7097        if !self.external_type_candidate_visible_in_context(analyzer, visible_from, unit, reference)
7098        {
7099            return None;
7100        }
7101        self.canonical_type_resolution(analyzer, visible_from, unit)
7102            .ok()
7103    }
7104
7105    /// Follow `unit`'s alias chain to the class it names, or report why the
7106    /// chain does not end at one indexed class.
7107    ///
7108    /// A chain that leaves the index - an alias to a template parameter, to a
7109    /// standard-library type, or to any other declaration the workspace does
7110    /// not hold - is `Unresolvable`, not `Ambiguous` (#1828). So is a cycle:
7111    /// there is still nothing to choose between.
7112    fn canonical_type_resolution(
7113        &self,
7114        analyzer: &CppGraphSource<'_>,
7115        visible_from: &ProjectFile,
7116        unit: &CodeUnit,
7117    ) -> Result<CodeUnit, TypeCandidateFailure> {
7118        let mut current = unit.clone();
7119        let mut seen_aliases = HashSet::default();
7120        loop {
7121            let Some(target) = self.structured_alias_target(analyzer, &current) else {
7122                return current
7123                    .is_class()
7124                    .then_some(current)
7125                    .ok_or(TypeCandidateFailure::Unresolvable);
7126            };
7127            if matches!(target, StructuredAliasTarget::Builtin) {
7128                return current
7129                    .is_class()
7130                    .then_some(current)
7131                    .ok_or(TypeCandidateFailure::Unresolvable);
7132            }
7133            if !seen_aliases.insert(current.clone()) {
7134                return Err(TypeCandidateFailure::Unresolvable);
7135            }
7136            current = self.structured_alias_target_resolution(visible_from, &current, &target)?;
7137        }
7138    }
7139
7140    pub fn canonical_visible_full_type_unit(
7141        &self,
7142        analyzer: &CppGraphSource<'_>,
7143        visible_from: &ProjectFile,
7144        unit: &CodeUnit,
7145    ) -> Option<CodeUnit> {
7146        let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
7147        if cpp_class_declaration_strength(analyzer, &canonical)
7148            != CppClassDeclarationStrength::Forward
7149        {
7150            return Some(canonical);
7151        }
7152        let mut full = Vec::new();
7153        for candidate in self
7154            .visible_identifier_candidates(visible_from, canonical.identifier())
7155            .filter(|candidate| {
7156                candidate.is_class()
7157                    && candidate.fq_name() == canonical.fq_name()
7158                    && cpp_class_declaration_strength(analyzer, candidate)
7159                        == CppClassDeclarationStrength::Full
7160            })
7161        {
7162            if !full.iter().any(|existing| same_symbol(existing, candidate)) {
7163                full.push(candidate.clone());
7164            }
7165        }
7166        match full.len() {
7167            0 => Some(canonical),
7168            1 => full.pop(),
7169            _ => None,
7170        }
7171    }
7172
7173    fn resolve_structured_alias_target(
7174        &self,
7175        visible_from: &ProjectFile,
7176        declaration: &CodeUnit,
7177        target: &StructuredAliasTarget,
7178    ) -> Option<CodeUnit> {
7179        self.structured_alias_target_resolution(visible_from, declaration, target)
7180            .ok()
7181    }
7182
7183    fn structured_alias_target_resolution(
7184        &self,
7185        visible_from: &ProjectFile,
7186        declaration: &CodeUnit,
7187        target: &StructuredAliasTarget,
7188    ) -> Result<CodeUnit, TypeCandidateFailure> {
7189        let primary =
7190            self.structured_alias_primary_resolution(visible_from, declaration, target)?;
7191        let StructuredAliasTarget::Named { arguments, .. } = target else {
7192            return Err(TypeCandidateFailure::Unresolvable);
7193        };
7194        match arguments {
7195            Some(arguments) => self
7196                .resolve_template_arguments(visible_from, primary, arguments)
7197                .map_err(|error| match error {
7198                    CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
7199                        TypeCandidateFailure::Ambiguous
7200                    }
7201                    _ => TypeCandidateFailure::Unresolvable,
7202                }),
7203            None => Ok(primary),
7204        }
7205    }
7206
7207    fn resolve_structured_alias_primary(
7208        &self,
7209        visible_from: &ProjectFile,
7210        declaration: &CodeUnit,
7211        target: &StructuredAliasTarget,
7212    ) -> Option<CodeUnit> {
7213        self.structured_alias_primary_resolution(visible_from, declaration, target)
7214            .ok()
7215    }
7216
7217    fn structured_alias_primary_resolution(
7218        &self,
7219        visible_from: &ProjectFile,
7220        declaration: &CodeUnit,
7221        target: &StructuredAliasTarget,
7222    ) -> Result<CodeUnit, TypeCandidateFailure> {
7223        let StructuredAliasTarget::Named {
7224            components, global, ..
7225        } = target
7226        else {
7227            return Err(TypeCandidateFailure::Unresolvable);
7228        };
7229        let qualified = components.join("::");
7230        let candidates = if *global {
7231            // `::A::B` anchors at the root scope, so a candidate whose
7232            // canonical path merely ends with the spelled components does not
7233            // qualify. Without this filter a global `::Canonical` target also
7234            // collects `alpha::Canonical`, the lookup reports a false
7235            // ambiguity, and the alias arm silently drops out of its
7236            // conflicting family instead of proving the conflict (#2398).
7237            let mut candidates = self.type_candidates(visible_from, &qualified);
7238            candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
7239            candidates
7240        } else {
7241            self.type_candidates_for_declaration(visible_from, declaration, &qualified)
7242        };
7243        logical_type_candidate(candidates)
7244    }
7245
7246    pub fn structured_alias_primary_preserves_target(
7247        &self,
7248        analyzer: &CppGraphSource<'_>,
7249        visible_from: &ProjectFile,
7250        candidate: &CodeUnit,
7251        target: &CodeUnit,
7252    ) -> bool {
7253        let mut current = candidate.clone();
7254        let mut seen = HashSet::default();
7255        let mut matched_target = false;
7256        loop {
7257            if same_visible_symbol(&current, target)
7258                || self.compatible_primary_template_redeclarations(&current, target)
7259            {
7260                matched_target = true;
7261            }
7262            if !seen.insert(current.clone()) {
7263                return false;
7264            }
7265            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
7266                return matched_target;
7267            };
7268            if matches!(alias_target, StructuredAliasTarget::Builtin) {
7269                return matched_target;
7270            };
7271            let Some(primary) =
7272                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
7273            else {
7274                // A dependent member target such as `Detector<T>::type`
7275                // cannot be reduced to an indexed primary, but a preceding
7276                // structured alias hop may already have proven the requested
7277                // alias identity. Cycles still resolve a primary and are
7278                // rejected by `seen` above.
7279                return matched_target;
7280            };
7281            current = primary;
7282        }
7283    }
7284
7285    fn lexical_type_candidates_match_target(
7286        &self,
7287        analyzer: &CppGraphSource<'_>,
7288        unit: &CodeUnit,
7289        candidates: &[CodeUnit],
7290        target: &CodeUnit,
7291    ) -> bool {
7292        same_visible_symbol(unit, target)
7293            || self.same_template_member_identity(analyzer, unit, target)
7294            || candidates.iter().any(|candidate| {
7295                same_visible_symbol(candidate, target)
7296                    || self.same_template_member_identity(analyzer, candidate, target)
7297            })
7298    }
7299
7300    pub fn structured_class_alias_resolves_to_target(
7301        &self,
7302        analyzer: &CppGraphSource<'_>,
7303        visible_from: &ProjectFile,
7304        alias: &CodeUnit,
7305        target: &CodeUnit,
7306    ) -> bool {
7307        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7308            return false;
7309        };
7310        let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
7311            return false;
7312        };
7313        let StructuredAliasTarget::Named {
7314            components, global, ..
7315        } = &alias_target
7316        else {
7317            return false;
7318        };
7319        let lexical_scope = canonical_cpp_scope_components(&owner);
7320        match self.resolve_type_components_lexically_for_target(
7321            analyzer,
7322            visible_from,
7323            components,
7324            *global,
7325            &lexical_scope,
7326            target,
7327        ) {
7328            LexicalTypeResolution::Resolved {
7329                unit, candidates, ..
7330            } => self.lexical_type_candidates_match_target(analyzer, &unit, &candidates, target),
7331            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7332                self.structured_alias_primary_preserves_target(
7333                    analyzer,
7334                    visible_from,
7335                    alias,
7336                    target,
7337                ) || self.flattened_macro_namespace_alias_target_matches(
7338                    analyzer,
7339                    visible_from,
7340                    alias,
7341                    &alias_target,
7342                    target,
7343                )
7344            }
7345        }
7346    }
7347
7348    /// Return true when a class-owned alias names the requested type as one
7349    /// structured qualifier in its target path.
7350    ///
7351    /// A dependent target such as `Primary<T>::Type` cannot resolve to one
7352    /// indexed class. Forward lookup can still retain `Primary` as its bounded
7353    /// canonical identity. Inverse lookup needs the same evidence when later
7354    /// references use only the alias spelling.
7355    pub fn structured_class_alias_path_preserves_target(
7356        &self,
7357        analyzer: &CppGraphSource<'_>,
7358        visible_from: &ProjectFile,
7359        alias: &CodeUnit,
7360        target: &CodeUnit,
7361    ) -> bool {
7362        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7363            return false;
7364        };
7365        let Some(StructuredAliasTarget::Named {
7366            components, global, ..
7367        }) = self.structured_alias_target(analyzer, alias)
7368        else {
7369            return false;
7370        };
7371        let lexical_scope = canonical_cpp_scope_components(&owner);
7372        (1..components.len()).rev().any(|component_count| {
7373            matches!(
7374                self.resolve_type_components_lexically_for_target(
7375                    analyzer,
7376                    visible_from,
7377                    &components[..component_count],
7378                    global,
7379                    &lexical_scope,
7380                    target,
7381                ),
7382                LexicalTypeResolution::Resolved {
7383                    ref unit,
7384                    ref candidates,
7385                    ..
7386                } if self.lexical_type_candidates_match_target(
7387                    analyzer,
7388                    unit,
7389                    candidates,
7390                    target,
7391                )
7392            )
7393        })
7394    }
7395
7396    fn flattened_macro_namespace_alias_target_matches(
7397        &self,
7398        analyzer: &CppGraphSource<'_>,
7399        visible_from: &ProjectFile,
7400        alias: &CodeUnit,
7401        alias_target: &StructuredAliasTarget,
7402        target: &CodeUnit,
7403    ) -> bool {
7404        let StructuredAliasTarget::Named {
7405            components,
7406            global: false,
7407            arguments: None,
7408        } = alias_target
7409        else {
7410            return false;
7411        };
7412        let Some((target_name, namespace_components)) = components.split_last() else {
7413            return false;
7414        };
7415        if namespace_components.is_empty()
7416            || target_name != target.identifier()
7417            || alias.source() != target.source()
7418            || alias.source() != visible_from
7419            || !target.is_class()
7420            || declared_type_alias(analyzer, target)
7421        {
7422            return false;
7423        }
7424        if self
7425            .resolve_structured_alias_target(visible_from, alias, alias_target)
7426            .is_some()
7427        {
7428            return false;
7429        }
7430
7431        let Some(alias_start) = analyzer
7432            .declaration_source_occurrences(alias)
7433            .into_iter()
7434            .flatten()
7435            .map(|occurrence| occurrence.range.start_byte)
7436            .min()
7437        else {
7438            return false;
7439        };
7440        let has_matching_declaration = analyzer
7441            .declaration_source_occurrences(target)
7442            .into_iter()
7443            .flatten()
7444            .any(|occurrence| {
7445                occurrence.range.end_byte <= alias_start
7446                    && occurrence.fact.flattened_macro_namespace.as_deref()
7447                        == Some(namespace_components)
7448            });
7449        if !has_matching_declaration {
7450            return false;
7451        }
7452
7453        let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
7454        let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
7455        guard_requirement_sets_match(&alias_guards, &target_guards)
7456    }
7457
7458    pub fn template_alias_arguments_preserve_target(
7459        &self,
7460        analyzer: &CppGraphSource<'_>,
7461        visible_from: &ProjectFile,
7462        alias: &CodeUnit,
7463        arguments: &[CppTemplateExpression],
7464        target: &CodeUnit,
7465    ) -> bool {
7466        let Some(metadata) = self.cpp_template_metadata.get(alias) else {
7467            return false;
7468        };
7469        if metadata.alias_target.is_none()
7470            || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
7471        {
7472            return false;
7473        }
7474        self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
7475    }
7476
7477    pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
7478        self.cpp_template_metadata
7479            .get(unit)
7480            .is_some_and(CppTemplateMetadata::is_primary)
7481    }
7482
7483    pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
7484        self.cpp_template_metadata
7485            .get(unit)
7486            .is_some_and(CppTemplateMetadata::is_specialization)
7487    }
7488
7489    pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
7490        same_visible_symbol(left, right)
7491            || self.compatible_primary_template_redeclarations(left, right)
7492    }
7493
7494    pub fn same_template_member_identity(
7495        &self,
7496        analyzer: &CppGraphSource<'_>,
7497        left: &CodeUnit,
7498        right: &CodeUnit,
7499    ) -> bool {
7500        if same_visible_symbol(left, right) {
7501            return true;
7502        }
7503        if left.kind() != right.kind()
7504            || left.identifier() != right.identifier()
7505            || left.signature() != right.signature()
7506        {
7507            return false;
7508        }
7509        let (Some(left_owner), Some(right_owner)) =
7510            (analyzer.parent_of(left), analyzer.parent_of(right))
7511        else {
7512            return false;
7513        };
7514        left_owner.is_class()
7515            && right_owner.is_class()
7516            && self.same_template_owner_identity(&left_owner, &right_owner)
7517    }
7518
7519    fn unique_canonical_type_candidate(
7520        &self,
7521        analyzer: &CppGraphSource<'_>,
7522        visible_from: &ProjectFile,
7523        candidates: &[&CodeUnit],
7524    ) -> Option<CodeUnit> {
7525        self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
7526            .ok()
7527    }
7528
7529    fn canonical_type_candidate_resolution(
7530        &self,
7531        analyzer: &CppGraphSource<'_>,
7532        visible_from: &ProjectFile,
7533        candidates: &[&CodeUnit],
7534    ) -> Result<CodeUnit, TypeCandidateFailure> {
7535        let mut canonical = Vec::new();
7536        for candidate in candidates {
7537            let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
7538            if canonical
7539                .iter()
7540                .any(|existing| same_visible_symbol(existing, &resolved))
7541            {
7542                continue;
7543            }
7544            if let Some(existing) = canonical.iter_mut().find(|existing| {
7545                self.compatible_primary_template_redeclarations(existing, &resolved)
7546            }) {
7547                // A forward declaration and its full primary-template
7548                // definition are one C++ type even when they live in
7549                // different headers and alpha-rename their parameters. The
7550                // target-preserving path already reconciles this family; do
7551                // the same for ordinary canonical lookup so an out-of-line
7552                // member's lexical owner is not made ambiguous by its own
7553                // forward declaration. Retain the strongest physical
7554                // declaration for later owner/range queries.
7555                if matches!(
7556                    (
7557                        cpp_class_declaration_strength(analyzer, existing),
7558                        cpp_class_declaration_strength(analyzer, &resolved),
7559                    ),
7560                    (
7561                        CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
7562                        CppClassDeclarationStrength::Full,
7563                    ) | (
7564                        CppClassDeclarationStrength::Unknown,
7565                        CppClassDeclarationStrength::Forward,
7566                    )
7567                ) {
7568                    *existing = resolved;
7569                }
7570                continue;
7571            }
7572            canonical.push(resolved);
7573            if canonical.len() > 1 {
7574                return Err(TypeCandidateFailure::Ambiguous);
7575            }
7576        }
7577        canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
7578    }
7579
7580    pub fn unique_type_candidate_preserving_target(
7581        &self,
7582        analyzer: &CppGraphSource<'_>,
7583        visible_from: &ProjectFile,
7584        candidates: &[&CodeUnit],
7585        target: &CodeUnit,
7586    ) -> Option<CodeUnit> {
7587        // C++ headers often expose one logical type through mutually exclusive
7588        // physical declarations, for example a class in the fallback branch
7589        // and a `using` alias to the standard-library type in the configured
7590        // branch. The index intentionally retains both declarations so forward
7591        // lookup can report each target. Preserve the requested target when
7592        // that is the only ambiguity: every candidate has the same type kind,
7593        // exact canonical FQN, and source file, and the requested declaration
7594        // itself is one of the physical candidates. Do not merge same-named
7595        // declarations from different files or namespaces; those remain
7596        // ambiguous and fail closed below.
7597        if self.c_tag_declaration_family_matches_target(analyzer, visible_from, candidates, target)
7598            || self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7599        {
7600            return Some(target.clone());
7601        }
7602        let mut resolved_candidates = Vec::new();
7603        for candidate in candidates {
7604            // An ifdef branch that aliases an unindexed system type (for
7605            // example `typedef pthread_mutex_t k5_os_mutex`) cannot be
7606            // canonicalized. That branch does not name `target`. Dropping it
7607            // keeps the branch that does. Failing the whole family here would
7608            // deny every usage of the reachable spelling (#2368).
7609            let Some(resolved) =
7610                self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7611            else {
7612                continue;
7613            };
7614            if resolved_candidates
7615                .iter()
7616                .any(|existing| same_visible_symbol(existing, &resolved))
7617            {
7618                continue;
7619            }
7620            resolved_candidates.push(resolved);
7621        }
7622        match resolved_candidates.as_slice() {
7623            [] => None,
7624            [single] => Some(single.clone()),
7625            // The branches disagree about what the name aliases. When they are
7626            // spellings of one entity (#1845) that disagreement is a build
7627            // configuration, not a choice between types, so it must not deny
7628            // the requested target its reference.
7629            _ => self
7630                .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
7631                .map(|_| target.clone()),
7632        }
7633    }
7634
7635    /// The declaration a same-file same-FQN family stands for when a reference
7636    /// names `target`, or `None` when the candidates are not one family or the
7637    /// family does not name `target`.
7638    ///
7639    /// A translation unit cannot hold two different types under one qualified
7640    /// name, so several same-kind declarations of one FQN in one file are
7641    /// alternate spellings of one entity - the configuration branches of an
7642    /// `#if` family, for example log4cxx's `logchar`, which aliases `char` in
7643    /// the UTF-8 branch and `UniChar` in the unichar branch. Their alias
7644    /// targets differ; canonicalizing each branch on its own and then demanding
7645    /// agreement reports an ambiguity that denies every declaration in the
7646    /// family its usages (#1845). The family names `target` when it declares
7647    /// it, or when one branch's alias chain reaches it.
7648    ///
7649    /// Declarations in different files or namespaces are distinct entities and
7650    /// are deliberately excluded: their disagreement is a real ambiguity.
7651    pub fn same_fqn_type_spelling_for_target<'b>(
7652        &self,
7653        analyzer: &CppGraphSource<'_>,
7654        visible_from: &ProjectFile,
7655        candidates: &[&'b CodeUnit],
7656        target: &CodeUnit,
7657    ) -> Option<&'b CodeUnit> {
7658        let [first, rest @ ..] = candidates else {
7659            return None;
7660        };
7661        if rest.is_empty()
7662            || !rest.iter().all(|candidate| {
7663                candidate.kind() == first.kind()
7664                    && candidate.fq_name() == first.fq_name()
7665                    && candidate.source() == first.source()
7666            })
7667        {
7668            return None;
7669        }
7670        candidates
7671            .iter()
7672            .copied()
7673            .find(|candidate| same_symbol(candidate, target))
7674            .or_else(|| {
7675                candidates.iter().copied().find(|candidate| {
7676                    self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7677                        .is_some_and(|resolved| same_visible_symbol(&resolved, target))
7678                })
7679            })
7680    }
7681
7682    pub fn alternate_same_fqn_type_declarations(
7683        &self,
7684        analyzer: &CppGraphSource<'_>,
7685        candidates: &[&CodeUnit],
7686        target: &CodeUnit,
7687    ) -> bool {
7688        let Some(first) = candidates.first() else {
7689            return false;
7690        };
7691        let same_api = first.kind() == target.kind()
7692            && first.fq_name() == target.fq_name()
7693            && first.source() == target.source()
7694            && candidates.iter().all(|candidate| {
7695                candidate.kind() == target.kind()
7696                    && candidate.fq_name() == target.fq_name()
7697                    && candidate.source() == target.source()
7698            })
7699            && candidates
7700                .iter()
7701                .any(|candidate| same_symbol(candidate, target))
7702            && candidates
7703                .iter()
7704                .any(|candidate| !same_logical_symbol(candidate, target));
7705        if !same_api {
7706            return false;
7707        }
7708
7709        let requirements = candidates
7710            .iter()
7711            .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7712            .collect::<Vec<_>>();
7713        requirements.len() > 1
7714            && requirements
7715                .iter()
7716                .all(|requirement| !requirement.is_empty())
7717            && requirements.iter().enumerate().all(|(index, left)| {
7718                requirements[index + 1..].iter().all(|right| {
7719                    left.iter().all(|(_, left_guards)| {
7720                        right.iter().all(|(_, right_guards)| {
7721                            merge_preprocessor_guards(left_guards, right_guards).is_none()
7722                        })
7723                    })
7724                })
7725            })
7726    }
7727
7728    fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
7729        let mut pending = vec![terms.to_vec()];
7730        while let Some(branch_terms) = pending.pop() {
7731            let mut normalized = Vec::new();
7732            let mut covers_branch = false;
7733            for term in branch_terms {
7734                if term.iter().any(|guard| term.contains(&guard.negated())) {
7735                    continue;
7736                }
7737                if term.is_empty() {
7738                    covers_branch = true;
7739                    break;
7740                }
7741                if !normalized.iter().any(|existing| existing == &term) {
7742                    normalized.push(term);
7743                }
7744            }
7745            if covers_branch {
7746                continue;
7747            }
7748            let Some(split_guard) = normalized
7749                .iter()
7750                .flat_map(|term| term.iter())
7751                .next()
7752                .cloned()
7753            else {
7754                return false;
7755            };
7756            let negated_guard = split_guard.negated();
7757            let mut when_defined = Vec::new();
7758            let mut when_undefined = Vec::new();
7759            for term in normalized {
7760                if term.contains(&negated_guard) {
7761                    // This term cannot hold when `split_guard` is true.
7762                } else if term.contains(&split_guard) {
7763                    let mut reduced = term.clone();
7764                    reduced.remove(&split_guard);
7765                    when_defined.push(reduced);
7766                } else {
7767                    when_defined.push(term.clone());
7768                }
7769                if term.contains(&split_guard) {
7770                    // This term cannot hold when `split_guard` is false.
7771                } else if term.contains(&negated_guard) {
7772                    let mut reduced = term;
7773                    reduced.remove(&negated_guard);
7774                    when_undefined.push(reduced);
7775                } else {
7776                    when_undefined.push(term);
7777                }
7778            }
7779            pending.push(when_defined);
7780            pending.push(when_undefined);
7781        }
7782        true
7783    }
7784
7785    /// The byte range of the one `#if` family with a terminal `#else` that holds
7786    /// every physical declaration of every candidate, or `None` when they do not
7787    /// share one such family.
7788    ///
7789    /// Guard terms alone cannot distinguish one `#if` family from separate blocks
7790    /// whose macros changed between declarations. Require every physical range to
7791    /// belong to one syntax-tree family with a terminal `#else` before the terms
7792    /// can prove branch coverage.
7793    fn declarations_share_exhaustive_conditional_family(
7794        &self,
7795        analyzer: &CppGraphSource<'_>,
7796        candidates: &[&CodeUnit],
7797    ) -> Option<(usize, usize)> {
7798        let mut family_range = None;
7799        for candidate in candidates {
7800            let mut candidate_family = None;
7801            for occurrence in analyzer.declaration_source_occurrences(candidate)? {
7802                let key = occurrence.exhaustive_family?;
7803                if candidate_family.is_some_and(|existing| existing != key) {
7804                    return None;
7805                }
7806                candidate_family = Some(key);
7807            }
7808            let candidate_family = candidate_family?;
7809            if family_range.is_some_and(|existing| existing != candidate_family) {
7810                return None;
7811            }
7812            family_range = Some(candidate_family);
7813        }
7814        family_range
7815    }
7816
7817    pub fn complementary_same_fqn_type_declarations(
7818        &self,
7819        analyzer: &CppGraphSource<'_>,
7820        candidates: &[&CodeUnit],
7821        target: &CodeUnit,
7822    ) -> bool {
7823        if candidates.len() < 2
7824            || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7825            || self
7826                .declarations_share_exhaustive_conditional_family(analyzer, candidates)
7827                .is_none()
7828        {
7829            return false;
7830        }
7831        Self::preprocessor_guard_terms_cover_all_paths(
7832            &self.declaration_family_guard_terms(analyzer, candidates),
7833        )
7834    }
7835
7836    fn declaration_family_guard_terms(
7837        &self,
7838        analyzer: &CppGraphSource<'_>,
7839        candidates: &[&CodeUnit],
7840    ) -> Vec<HashSet<PreprocessorGuard>> {
7841        candidates
7842            .iter()
7843            .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7844            .map(|(_, guards)| guards)
7845            .collect()
7846    }
7847
7848    /// A callable name declared on every branch of one completed `#if`/`#else`
7849    /// family is declared on every configuration path, so a reference below the
7850    /// whole family sees one of the branches whatever the preprocessor decides.
7851    /// Answer the family's end byte: only past `#endif` is every branch's
7852    /// declaration behind the reference.
7853    ///
7854    /// This is the callable analogue of `complementary_same_fqn_type_declarations`
7855    /// and shares both of its primitives. It does not require two distinct
7856    /// `CodeUnit`s: branches that declare the same signature can collapse into
7857    /// one unit carrying one physical range per branch.
7858    ///
7859    /// The branches are alternate spellings of one declaration, never competing
7860    /// declarations, so only the first branch stands for the family. Reporting
7861    /// every branch as visible would turn a name the source declares exactly
7862    /// once into an ambiguity between build configurations.
7863    fn exhaustive_guard_family_activation(
7864        &self,
7865        analyzer: &CppGraphSource<'_>,
7866        candidate: &CodeUnit,
7867        reference: &CallableReferenceContext<'_>,
7868    ) -> Option<usize> {
7869        // Branch coverage says nothing about scope: a block-local declaration
7870        // stays invisible however many branches declare it.
7871        if !analyzer
7872            .declaration_source_occurrences(candidate)?
7873            .iter()
7874            .any(|occurrence| occurrence.fact.callable_activation.is_some())
7875        {
7876            return None;
7877        }
7878        let family = self
7879            .visible_identifier_candidates(candidate.source(), candidate.identifier())
7880            .filter(|peer| {
7881                peer.kind() == candidate.kind()
7882                    && peer.fq_name() == candidate.fq_name()
7883                    && peer.source() == candidate.source()
7884            })
7885            .collect::<Vec<_>>();
7886        let (_, family_end) =
7887            self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
7888        if !Self::preprocessor_guard_terms_cover_all_paths(
7889            &self.declaration_family_guard_terms(analyzer, &family),
7890        ) {
7891            return None;
7892        }
7893        // A reference whose own guards pick one branch already reaches that
7894        // branch through the ordinary same-guard path; the family must not
7895        // resurrect the branch the reference contradicts.
7896        if !declaration_guard_requirements(analyzer, self.cpp, candidate)
7897            .iter()
7898            .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
7899        {
7900            return None;
7901        }
7902        (first_declaration_byte(analyzer, candidate)?
7903            == family
7904                .iter()
7905                .filter_map(|peer| first_declaration_byte(analyzer, peer))
7906                .min()?)
7907        .then_some(family_end)
7908    }
7909
7910    fn type_candidate_preserving_target(
7911        &self,
7912        analyzer: &CppGraphSource<'_>,
7913        visible_from: &ProjectFile,
7914        candidate: &CodeUnit,
7915        target: &CodeUnit,
7916    ) -> Option<CodeUnit> {
7917        let mut current = candidate.clone();
7918        let mut matched_target = same_visible_symbol(&current, target)
7919            || self.compatible_primary_template_redeclarations(&current, target);
7920        let mut seen = HashSet::default();
7921        loop {
7922            if !seen.insert(current.clone()) {
7923                return None;
7924            }
7925            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
7926                return matched_target
7927                    .then(|| target.clone())
7928                    .or_else(|| current.is_class().then_some(current));
7929            };
7930            if self.flattened_macro_namespace_alias_target_matches(
7931                analyzer,
7932                visible_from,
7933                &current,
7934                &alias_target,
7935                target,
7936            ) {
7937                return Some(target.clone());
7938            }
7939            if matches!(alias_target, StructuredAliasTarget::Builtin) {
7940                return matched_target
7941                    .then(|| target.clone())
7942                    .or_else(|| current.is_class().then_some(current));
7943            }
7944            // A non-template alias can name a template alias with explicit
7945            // arguments (for example, `using Result = Expected<int>`).  When
7946            // the requested target is that alias's primary declaration, keep
7947            // the primary identity before expanding the RHS arguments.  The
7948            // expansion would otherwise canonicalize through the underlying
7949            // implementation type and lose the target spelling used by the
7950            // forward resolver.
7951            if !self.cpp_template_metadata.contains_key(&current)
7952                && let Some(primary) =
7953                    self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
7954                && (same_visible_symbol(&primary, target)
7955                    || self.compatible_primary_template_redeclarations(&primary, target))
7956            {
7957                return Some(target.clone());
7958            }
7959            if same_visible_symbol(&current, target) {
7960                return Some(target.clone());
7961            }
7962            if self.cpp_template_metadata.contains_key(&current) {
7963                return None;
7964            }
7965            let Some(next) =
7966                self.resolve_structured_alias_target(visible_from, &current, &alias_target)
7967            else {
7968                return matched_target.then(|| target.clone());
7969            };
7970            current = next;
7971            matched_target |= same_visible_symbol(&current, target)
7972                || self.compatible_primary_template_redeclarations(&current, target);
7973        }
7974    }
7975
7976    fn compatible_primary_template_redeclarations(
7977        &self,
7978        left: &CodeUnit,
7979        right: &CodeUnit,
7980    ) -> bool {
7981        let (Some(left_metadata), Some(right_metadata)) = (
7982            self.cpp_template_metadata.get(left),
7983            self.cpp_template_metadata.get(right),
7984        ) else {
7985            return false;
7986        };
7987        left_metadata.primary_fq_name == right_metadata.primary_fq_name
7988            && left_metadata.is_primary()
7989            && right_metadata.is_primary()
7990            && cpp_reconcile_primary_template_parameters(
7991                &[(left, left_metadata), (right, right_metadata)],
7992                right,
7993            )
7994            .is_some()
7995    }
7996
7997    fn alias_candidate_may_preserve_target(
7998        &self,
7999        analyzer: &CppGraphSource<'_>,
8000        visible_from: &ProjectFile,
8001        candidate: &CodeUnit,
8002        target: &CodeUnit,
8003    ) -> bool {
8004        let mut current = candidate.clone();
8005        let mut seen = HashSet::default();
8006        loop {
8007            if same_visible_symbol(&current, target)
8008                || self.compatible_primary_template_redeclarations(&current, target)
8009            {
8010                return true;
8011            }
8012            if self.cpp_template_metadata.contains_key(&current) {
8013                return true;
8014            }
8015            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
8016                return false;
8017            };
8018            let StructuredAliasTarget::Named {
8019                components,
8020                global,
8021                arguments,
8022            } = alias_target
8023            else {
8024                return false;
8025            };
8026            if arguments.is_some() || !seen.insert(current.clone()) {
8027                return true;
8028            }
8029            let qualified = components.join("::");
8030            let next = if global {
8031                unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
8032            } else {
8033                self.resolve_unique_type_for_declaration(visible_from, &current, &qualified)
8034            };
8035            let Some(next) = next else {
8036                return true;
8037            };
8038            current = next;
8039        }
8040    }
8041
8042    fn alias_candidate_structurally_reaches_target(
8043        &self,
8044        analyzer: &CppGraphSource<'_>,
8045        visible_from: &ProjectFile,
8046        candidate: &CodeUnit,
8047        target: &CodeUnit,
8048    ) -> bool {
8049        let mut current = candidate.clone();
8050        let mut seen = HashSet::default();
8051        loop {
8052            if same_visible_symbol(&current, target)
8053                || self.compatible_primary_template_redeclarations(&current, target)
8054                || self
8055                    .cpp_template_metadata
8056                    .get(&current)
8057                    .zip(self.cpp_template_metadata.get(target))
8058                    .is_some_and(|(current, target)| {
8059                        current.primary_fq_name == target.primary_fq_name
8060                    })
8061            {
8062                return true;
8063            }
8064            if !seen.insert(current.clone()) {
8065                return false;
8066            }
8067            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
8068                return false;
8069            };
8070            if let StructuredAliasTarget::Named { components, .. } = &alias_target
8071                && components[..components.len().saturating_sub(1)]
8072                    .iter()
8073                    .any(|component| {
8074                        self.type_reference_component_directly_names_target(component, target)
8075                    })
8076                && self.structured_class_alias_path_preserves_target(
8077                    analyzer,
8078                    visible_from,
8079                    &current,
8080                    target,
8081                )
8082            {
8083                return true;
8084            }
8085            if matches!(alias_target, StructuredAliasTarget::Builtin) {
8086                return false;
8087            }
8088            let Some(primary) =
8089                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
8090            else {
8091                return false;
8092            };
8093            current = primary;
8094        }
8095    }
8096
8097    /// Whether one indexed alias component in a qualified type reference has
8098    /// a structured path to `target`.
8099    ///
8100    /// Alias terminal names such as `type` are deliberately common. A
8101    /// file-wide name set cannot distinguish `Gen<T>::type`, whose indexed RHS
8102    /// names the target, from thousands of unrelated `Traits<T>::type`
8103    /// references. Match every qualified prefix against the alias's canonical
8104    /// scope before following its structured RHS. An unresolved dependent RHS
8105    /// fails this coarse gate only when no other component names the target;
8106    /// references such as `Identity<Target>::type` are admitted by that target
8107    /// component before this helper is called.
8108    pub fn qualified_alias_reference_may_reach_target(
8109        &self,
8110        analyzer: &CppGraphSource<'_>,
8111        file: &ProjectFile,
8112        components: &[String],
8113        global: bool,
8114        target: &CodeUnit,
8115    ) -> bool {
8116        for end in 0..components.len() {
8117            let spelled = &components[..=end];
8118            for candidate in self
8119                .visible_identifier_candidates(file, &components[end])
8120                .filter(|candidate| declared_type_alias(analyzer, candidate))
8121            {
8122                let candidate_components = canonical_cpp_scope_components(candidate);
8123                let shape_matches = if global {
8124                    candidate_components == spelled
8125                } else {
8126                    candidate_components.ends_with(spelled)
8127                };
8128                if shape_matches
8129                    && self.alias_candidate_structurally_reaches_target(
8130                        analyzer, file, candidate, target,
8131                    )
8132                {
8133                    return true;
8134                }
8135            }
8136        }
8137        false
8138    }
8139
8140    /// Every indexed type declaration `raw_name` names when it is written in
8141    /// `declaration`'s namespace: the innermost enclosing namespace that holds
8142    /// the name wins, otherwise the name is looked up unqualified.
8143    fn type_candidates_for_declaration<'b>(
8144        &'b self,
8145        visible_from: &ProjectFile,
8146        declaration: &CodeUnit,
8147        raw_name: &str,
8148    ) -> Vec<&'b CodeUnit> {
8149        let Some(normalized) = normalize_reference_name(raw_name) else {
8150            return Vec::new();
8151        };
8152        if let Some(namespace) = cpp_namespace_for(declaration) {
8153            for prefix in namespace_prefixes(&namespace) {
8154                let qualified = format!("{prefix}::{normalized}");
8155                let candidates = self.type_candidates(visible_from, &qualified);
8156                if !candidates.is_empty() {
8157                    return candidates;
8158                }
8159            }
8160        }
8161        self.type_candidates(visible_from, &normalized)
8162    }
8163
8164    fn resolve_unique_type_for_declaration(
8165        &self,
8166        visible_from: &ProjectFile,
8167        declaration: &CodeUnit,
8168        raw_name: &str,
8169    ) -> Option<CodeUnit> {
8170        unique_logical_type_candidate(self.type_candidates_for_declaration(
8171            visible_from,
8172            declaration,
8173            raw_name,
8174        ))
8175    }
8176
8177    /// The type `raw_name` names at `declaration`, answered with the
8178    /// declaration that defines it.
8179    ///
8180    /// C spells one type twice. `typedef struct T T;` in the header that
8181    /// publishes the handle indexes an incomplete `struct T` beside the
8182    /// complete `struct T { ... }` in the header that declares the members, and
8183    /// both carry the same kind and fully qualified name, so
8184    /// [`logical_type_candidate`] is free to answer with either. mbedtls writes
8185    /// exactly that: `include/mbedtls/ssl.h` forward-declares
8186    /// `mbedtls_ssl_handshake_params` for the pointer member
8187    /// `mbedtls_ssl_handshake_params *MBEDTLS_PRIVATE(handshake)` while
8188    /// `library/ssl_misc.h` defines its fields. A receiver typed from a field
8189    /// declaration is asked for its members on the next link of the chain, and
8190    /// only the definition owns them (#2982).
8191    ///
8192    /// Competing definitions leave nothing to choose between, so keep the
8193    /// logical answer rather than picking one of them.
8194    fn resolve_defining_type_for_declaration(
8195        &self,
8196        analyzer: &CppGraphSource<'_>,
8197        visible_from: &ProjectFile,
8198        declaration: &CodeUnit,
8199        raw_name: &str,
8200    ) -> Option<CodeUnit> {
8201        let candidates = self.type_candidates_for_declaration(visible_from, declaration, raw_name);
8202        let logical = unique_logical_type_candidate(candidates.clone())?;
8203        let mut defining = candidates.into_iter().filter(|candidate| {
8204            candidate.is_class()
8205                && cpp_class_declaration_strength(analyzer, candidate)
8206                    == CppClassDeclarationStrength::Full
8207        });
8208        match (defining.next(), defining.next()) {
8209            (Some(unique_definition), None) => Some(unique_definition.clone()),
8210            _ => Some(logical),
8211        }
8212    }
8213
8214    pub fn resolves_to_type(
8215        &self,
8216        analyzer: &CppGraphSource<'_>,
8217        file: &ProjectFile,
8218        raw_name: &str,
8219        target: &CodeUnit,
8220    ) -> bool {
8221        let Some(normalized) = normalize_reference_name(raw_name) else {
8222            return false;
8223        };
8224        let candidates = self.type_candidates(file, &normalized);
8225        if candidates.is_empty() {
8226            return self.parser_alias_resolves_to_type(file, raw_name, target);
8227        }
8228        let Some(resolved) =
8229            self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
8230        else {
8231            return false;
8232        };
8233        same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
8234    }
8235
8236    pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
8237        let mut current = alias.clone();
8238        let mut visited = HashSet::default();
8239        while visited.insert(current.clone()) {
8240            let raw_target = cpp_canonical_alias_target_text(&self.cpp_source(), &current)?;
8241            let resolved =
8242                self.resolve_type_for_declaration(current.source(), &current, &raw_target)?;
8243            if resolved.is_class() {
8244                return Some(resolved);
8245            }
8246            if !declared_type_alias(&self.cpp_source(), &resolved) {
8247                return None;
8248            }
8249            current = resolved;
8250        }
8251        None
8252    }
8253
8254    /// Whether two callable declarations declare one function.
8255    ///
8256    /// [`same_logical_symbol`] compares the persisted signature strings, which
8257    /// embed each parameter type exactly as it was spelled. A header
8258    /// declaration written inside `namespace zmq { class dist_t { ... } }` says
8259    /// `send_to_matching(msg_t *)` while its out-of-line body at file scope
8260    /// says `zmq::msg_t *`, so the string comparison reports two symbols where
8261    /// C++ ([basic.def], [dcl.fct]) sees one declaration and one definition.
8262    /// This resolves the written parameter names before comparing them and
8263    /// reports the same answer the language does for the cases it can prove.
8264    ///
8265    /// Everything it cannot prove stays two symbols: a template declaration, a
8266    /// parameter with no comparable shape, a name that resolves on one side
8267    /// only, and an alias chain it cannot follow safely (#2010).
8268    pub fn same_logical_callable(
8269        &self,
8270        analyzer: &CppGraphSource<'_>,
8271        left: &CodeUnit,
8272        right: &CodeUnit,
8273    ) -> bool {
8274        if same_logical_symbol(left, right) {
8275            return true;
8276        }
8277        if left.kind() != right.kind()
8278            || !left.is_callable()
8279            || !right.is_callable()
8280            || left.fq_name() != right.fq_name()
8281        {
8282            return false;
8283        }
8284        // A template declaration and its out-of-line body can also diverge
8285        // outside the parameter list - `template <class T>` against
8286        // `template <typename T>` - and the template head is part of the
8287        // persisted signature. Deciding template-head equivalence is a
8288        // separate question, so templates keep string identity.
8289        if self.callable_is_template_declaration(analyzer, left)
8290            || self.callable_is_template_declaration(analyzer, right)
8291        {
8292            return false;
8293        }
8294        let (Some(left_comparable), Some(right_comparable)) = (
8295            self.callable_comparable(analyzer, left),
8296            self.callable_comparable(analyzer, right),
8297        ) else {
8298            return false;
8299        };
8300        // The trailing member `const`, ref-qualifier, `noexcept`, trailing
8301        // return type and requires-clause are part of C++ callable identity and
8302        // an out-of-line definition repeats them verbatim, so they must agree
8303        // as written.
8304        if left_comparable.suffix != right_comparable.suffix
8305            || left_comparable.shapes.len() != right_comparable.shapes.len()
8306        {
8307            return false;
8308        }
8309        left_comparable
8310            .shapes
8311            .iter()
8312            .zip(right_comparable.shapes.iter())
8313            .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
8314                (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
8315                (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
8316                    self.comparable_shapes_agree(analyzer, left_shape, right_shape)
8317                }
8318                // An unstructured parameter records that the reduction failed,
8319                // not that the two spellings mean the same type, so it agrees
8320                // with nothing - including another unstructured parameter.
8321                _ => false,
8322            })
8323    }
8324
8325    /// Compare two parameter shapes node by node with an explicit paired stack.
8326    ///
8327    /// Shape variants and cv-qualifiers must agree exactly at every level; only
8328    /// the named leaves may be spelled differently, and they agree when they
8329    /// resolve to one type declaration.
8330    fn comparable_shapes_agree(
8331        &self,
8332        analyzer: &CppGraphSource<'_>,
8333        left: &CppComparableParameter,
8334        right: &CppComparableParameter,
8335    ) -> bool {
8336        let mut stack = vec![(left.root(), right.root())];
8337        while let Some((left_index, right_index)) = stack.pop() {
8338            match (left.node(left_index), right.node(right_index)) {
8339                (
8340                    CppComparableNode::Named {
8341                        name: left_name,
8342                        primitive: left_primitive,
8343                        konst: left_konst,
8344                        volatil: left_volatil,
8345                    },
8346                    CppComparableNode::Named {
8347                        name: right_name,
8348                        primitive: right_primitive,
8349                        konst: right_konst,
8350                        volatil: right_volatil,
8351                    },
8352                ) => {
8353                    if left_konst != right_konst
8354                        || left_volatil != right_volatil
8355                        || left_primitive != right_primitive
8356                        || !self.comparable_names_agree(
8357                            analyzer,
8358                            left_name,
8359                            right_name,
8360                            *left_primitive,
8361                        )
8362                    {
8363                        return false;
8364                    }
8365                }
8366                (
8367                    CppComparableNode::Pointer {
8368                        inner: left_inner,
8369                        konst: left_konst,
8370                        volatil: left_volatil,
8371                    },
8372                    CppComparableNode::Pointer {
8373                        inner: right_inner,
8374                        konst: right_konst,
8375                        volatil: right_volatil,
8376                    },
8377                ) => {
8378                    if left_konst != right_konst || left_volatil != right_volatil {
8379                        return false;
8380                    }
8381                    stack.push((*left_inner, *right_inner));
8382                }
8383                (
8384                    CppComparableNode::Reference { inner: left_inner },
8385                    CppComparableNode::Reference { inner: right_inner },
8386                )
8387                | (
8388                    CppComparableNode::Array { inner: left_inner },
8389                    CppComparableNode::Array { inner: right_inner },
8390                ) => stack.push((*left_inner, *right_inner)),
8391                (
8392                    CppComparableNode::Generic {
8393                        base: left_base,
8394                        arguments: left_arguments,
8395                    },
8396                    CppComparableNode::Generic {
8397                        base: right_base,
8398                        arguments: right_arguments,
8399                    },
8400                ) => {
8401                    if left_arguments.len() != right_arguments.len() {
8402                        return false;
8403                    }
8404                    stack.push((*left_base, *right_base));
8405                    stack.extend(
8406                        left_arguments.iter().zip(right_arguments.iter()).map(
8407                            |(left_argument, right_argument)| (*left_argument, *right_argument),
8408                        ),
8409                    );
8410                }
8411                _ => return false,
8412            }
8413        }
8414        true
8415    }
8416
8417    /// Whether two written type names denote one type.
8418    ///
8419    /// A primitive denotes the same type in every scope, so its recorded
8420    /// lexical scope is noise and its spelling decides. A nominal name is
8421    /// resolved on each side independently: two resolved names agree when they
8422    /// reach one type declaration, and two unresolved names agree only on
8423    /// exact agreement of what was written, which is no weaker than the
8424    /// whole-signature string equality this comparison replaces. Resolution on
8425    /// one side only is evidence of difference, never of agreement.
8426    fn comparable_names_agree(
8427        &self,
8428        analyzer: &CppGraphSource<'_>,
8429        left: &StructuredTypeName,
8430        right: &StructuredTypeName,
8431        primitive: bool,
8432    ) -> bool {
8433        if primitive {
8434            return left.path() == right.path();
8435        }
8436        match (
8437            self.comparable_name_terminal(analyzer, left),
8438            self.comparable_name_terminal(analyzer, right),
8439        ) {
8440            (Some(left_terminal), Some(right_terminal)) => {
8441                same_logical_symbol(&left_terminal, &right_terminal)
8442            }
8443            (None, None) => {
8444                left.path() == right.path() && left.is_absolute() == right.is_absolute()
8445            }
8446            _ => false,
8447        }
8448    }
8449
8450    /// The class declaration a written type name denotes, or `None` when the
8451    /// workspace cannot prove one.
8452    ///
8453    /// The lookup is a closure-independent lexical-scope prefix walk over the
8454    /// stored C++ identifier index rather than a visibility lookup: a body's
8455    /// `.cpp` is almost never in the reference file's include closure. Any name
8456    /// this walk resolves is one an enclosing-scope lookup could resolve, so it
8457    /// cannot invent a type the compiler could not see; `using`-directives are
8458    /// not modelled, and a name that needs one stays unresolved.
8459    fn comparable_name_terminal(
8460        &self,
8461        analyzer: &CppGraphSource<'_>,
8462        name: &StructuredTypeName,
8463    ) -> Option<CodeUnit> {
8464        let mut current = self.comparable_name_declaration(analyzer, name)?;
8465        let mut visited = HashSet::default();
8466        for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
8467            // The alias question is asked before the class question, and
8468            // through `declared_type_alias` rather than `is_type_alias`,
8469            // because extraction records `using A8 = A7;` as a *Class* unit
8470            // whose signature is the alias declaration. Reading the kind first
8471            // would end the chase on the alias itself and report an alias
8472            // spelling and its underlying class as two types (#2010).
8473            if !declared_type_alias(analyzer, &current) {
8474                return current.is_class().then_some(current);
8475            }
8476            if !visited.insert(current.clone()) {
8477                return None;
8478            }
8479            let facts = analyzer.declaration_source_properties(&current)?;
8480            if facts.iter().any(|fact| fact.adds_indirection) {
8481                return None;
8482            }
8483            let raw_target = cpp_canonical_alias_target_text(analyzer, &current)?;
8484            current = self.comparable_alias_target(analyzer, &current, &raw_target)?;
8485        }
8486        None
8487    }
8488
8489    /// The declaration one alias hop lands on: the type `raw_target` names,
8490    /// looked up from the alias declaration's own enclosing namespace.
8491    ///
8492    /// The hop takes the same closure-independent prefix walk the first lookup
8493    /// took, and deliberately not `resolve_type_for_declaration`: that one
8494    /// answers out of the `VisibilityIndex`, which is rooted at the reference
8495    /// file, while the alias declaration this hop starts from is reached
8496    /// through the workspace definition index and its file need not be in that
8497    /// root's include closure - where the visibility lookup answers nothing and
8498    /// the chase would stop on the alias itself (#2010).
8499    fn comparable_alias_target(
8500        &self,
8501        analyzer: &CppGraphSource<'_>,
8502        alias: &CodeUnit,
8503        raw_target: &str,
8504    ) -> Option<CodeUnit> {
8505        // `raw_target` is the alias declaration's written type text, so it is a
8506        // plain `::`-joined qualified-id: the same domain the shared symbol-path
8507        // parser reads, and the same leading `::` that marks an absolute name
8508        // everywhere else this crate normalizes a reference.
8509        let absolute = raw_target.trim_start().starts_with("::");
8510        let normalized = normalize_reference_name(raw_target)?;
8511        let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8512            brokk_bifrost_core::analyzer::Language::Cpp,
8513            &normalized,
8514        );
8515        let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
8516            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8517                brokk_bifrost_core::analyzer::Language::Cpp,
8518                &namespace,
8519            )
8520        });
8521        let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
8522        self.comparable_name_declaration(analyzer, &name)
8523    }
8524
8525    /// The one type declaration `name` names, by enclosing scope, innermost
8526    /// first.
8527    ///
8528    /// The first prefix depth that names anything decides: an inner scope hides
8529    /// an outer one, so a match there is the answer even when an outer scope
8530    /// also declares the name. Several logically distinct declarations at that
8531    /// depth are an ambiguity this comparison must not guess at.
8532    fn comparable_name_declaration(
8533        &self,
8534        analyzer: &CppGraphSource<'_>,
8535        name: &StructuredTypeName,
8536    ) -> Option<CodeUnit> {
8537        // The same parameter-name pair is commonly compared once for every
8538        // physical declaration in a candidate set. Keep the index lookup under
8539        // the cache lock so concurrent workers cannot all miss the same key
8540        // and repeat the workspace candidate filter. `None` is a meaningful
8541        // result too: unresolved and ambiguous names must remain unresolved on
8542        // every later comparison.
8543        let mut cache = self
8544            .comparable_name_declarations
8545            .lock()
8546            .expect("C++ comparable name declaration cache poisoned");
8547        if let Some(cached) = cache.get(name) {
8548            return cached.clone();
8549        }
8550        let interner = segment_interner();
8551        let identifier = name.path().last()?;
8552        let candidates_by_identifier = self.cpp.visibility_identifier_candidates(identifier);
8553        let first_depth = if name.is_absolute() {
8554            0
8555        } else {
8556            name.lexical_scope().len()
8557        };
8558        let mut resolved = None;
8559        for depth in (0..=first_depth).rev() {
8560            let mut structured = FqName::new();
8561            for component in name.lexical_scope()[..depth].iter().chain(name.path()) {
8562                structured.push(interner.intern(component, SegmentKind::Unknown));
8563            }
8564            let mut candidates = candidates_by_identifier
8565                .iter()
8566                .filter(|unit| unit.fq().same_segment_texts(&structured))
8567                .filter(|unit| {
8568                    unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
8569                })
8570                .cloned();
8571            let Some(first) = candidates.next() else {
8572                continue;
8573            };
8574            resolved = candidates
8575                .all(|unit| same_logical_symbol(&unit, &first))
8576                .then_some(first);
8577            break;
8578        }
8579        cache.insert(name.clone(), resolved.clone());
8580        resolved
8581    }
8582
8583    /// The comparison inputs of one callable declaration, extracted once.
8584    ///
8585    /// The comparison itself runs only when two candidates share kind and fully
8586    /// qualified name but not signature, which is rare; re-reading the same
8587    /// declaration for every pair in a candidate set is not.
8588    fn callable_comparable(
8589        &self,
8590        analyzer: &CppGraphSource<'_>,
8591        unit: &CodeUnit,
8592    ) -> Option<Arc<ExtractedComparable>> {
8593        if let Some(cached) = self
8594            .callable_comparables
8595            .lock()
8596            .expect("C++ callable comparable cache poisoned")
8597            .get(unit)
8598            .cloned()
8599        {
8600            return cached;
8601        }
8602        let extracted = self
8603            .extract_callable_comparable(analyzer, unit)
8604            .map(Arc::new);
8605        self.callable_comparables
8606            .lock()
8607            .expect("C++ callable comparable cache poisoned")
8608            .insert(unit.clone(), extracted.clone());
8609        extracted
8610    }
8611
8612    fn extract_callable_comparable(
8613        &self,
8614        analyzer: &CppGraphSource<'_>,
8615        unit: &CodeUnit,
8616    ) -> Option<ExtractedComparable> {
8617        let fact = analyzer
8618            .declaration_source_properties(unit)?
8619            .into_iter()
8620            .find(|fact| fact.callable_comparable_shapes.is_some())?;
8621        Some(ExtractedComparable {
8622            shapes: fact.callable_comparable_shapes?,
8623            suffix: fact.callable_identity_suffix?,
8624        })
8625    }
8626
8627    pub fn canonical_type_for_reference(
8628        &self,
8629        file: &ProjectFile,
8630        raw_name: &str,
8631    ) -> Option<CodeUnit> {
8632        let resolved = self.resolve_type(file, raw_name)?;
8633        self.alias_target(&resolved).or(Some(resolved))
8634    }
8635
8636    pub fn parser_alias_resolves_to_type(
8637        &self,
8638        file: &ProjectFile,
8639        raw_name: &str,
8640        target: &CodeUnit,
8641    ) -> bool {
8642        let Some(alias_name) = normalize_reference_name(raw_name) else {
8643            return false;
8644        };
8645        any_visible_source_file_matches(&self.visible_source_files_by_root, file, |source_file| {
8646            self.file_alias_matches(self.cpp, source_file, &alias_name, target)
8647        })
8648    }
8649
8650    fn file_alias_matches(
8651        &self,
8652        cpp: &dyn CppSource,
8653        file: &ProjectFile,
8654        alias_name: &str,
8655        target: &CodeUnit,
8656    ) -> bool {
8657        self.canonical_alias_cell(cpp, file).is_some_and(|cell| {
8658            cell.get()
8659                .expect("published canonical aliases")
8660                .iter()
8661                .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
8662        })
8663    }
8664
8665    fn canonical_alias_cell(
8666        &self,
8667        cpp: &dyn CppSource,
8668        file: &ProjectFile,
8669    ) -> Option<Arc<OnceLock<Box<[CppAlias]>>>> {
8670        let cell = {
8671            let mut cells = self.alias_cells.lock().expect("alias cell map lock");
8672            Arc::clone(
8673                cells
8674                    .entry(file.clone())
8675                    .or_insert_with(|| Arc::new(OnceLock::new())),
8676            )
8677        };
8678        if cell.get().is_none() {
8679            let aliases = aliases_from_canonical_source(cpp, self.token, file)?;
8680            cell.get_or_init(|| {
8681                self.parser_alias_source_parses
8682                    .fetch_add(1, Ordering::Relaxed);
8683                #[cfg(any(test, feature = "test-support"))]
8684                {
8685                    *self
8686                        .alias_source_parse_counts
8687                        .lock()
8688                        .expect("alias source parse count lock")
8689                        .entry(file.clone())
8690                        .or_default() += 1;
8691                }
8692                aliases.into_boxed_slice()
8693            });
8694        }
8695        Some(cell)
8696    }
8697
8698    #[cfg(any(test, feature = "test-support"))]
8699    pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
8700        self.visible_source_files_by_root
8701            .get(file)
8702            .cloned()
8703            .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
8704    }
8705
8706    #[cfg(any(test, feature = "test-support"))]
8707    pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
8708        self.alias_source_parse_counts
8709            .lock()
8710            .expect("alias source parse count lock")
8711            .get(file)
8712            .copied()
8713            .unwrap_or(0)
8714    }
8715
8716    #[cfg(any(test, feature = "test-support"))]
8717    pub fn parser_alias_fallback_file_count_for_test(&self) -> usize {
8718        self.parser_alias_fallback_files.load(Ordering::Relaxed)
8719    }
8720
8721    pub fn resolve_named(
8722        &self,
8723        file: &ProjectFile,
8724        raw_name: &str,
8725        kind: TargetKind,
8726    ) -> Option<CodeUnit> {
8727        let normalized = normalize_reference_name(raw_name)?;
8728        self.named_candidates_for_normalized(file, &normalized, kind)
8729            .into_iter()
8730            .next()
8731            .cloned()
8732    }
8733
8734    pub fn contains_named_symbol(
8735        &self,
8736        file: &ProjectFile,
8737        raw_name: &str,
8738        kind: TargetKind,
8739        target: &CodeUnit,
8740    ) -> bool {
8741        let Some(normalized) = normalize_reference_name(raw_name) else {
8742            return false;
8743        };
8744        self.named_candidates_for_normalized(file, &normalized, kind)
8745            .into_iter()
8746            .any(|unit| {
8747                matches_kind_for_lookup(unit, kind)
8748                    && reference_matches_unit(&normalized, unit)
8749                    && same_visible_symbol(unit, target)
8750            })
8751    }
8752
8753    pub fn named_candidates(
8754        &self,
8755        file: &ProjectFile,
8756        raw_name: &str,
8757        kind: TargetKind,
8758    ) -> Vec<CodeUnit> {
8759        let Some(normalized) = normalize_reference_name(raw_name) else {
8760            return Vec::new();
8761        };
8762        self.named_candidates_for_normalized(file, &normalized, kind)
8763            .into_iter()
8764            .cloned()
8765            .collect()
8766    }
8767
8768    pub fn resolve_known_non_target(
8769        &self,
8770        file: &ProjectFile,
8771        raw_name: &str,
8772        kind: TargetKind,
8773        target: &CodeUnit,
8774    ) -> bool {
8775        let Some(normalized) = normalize_reference_name(raw_name) else {
8776            return false;
8777        };
8778        normalized.contains("::")
8779            && self
8780                .named_candidates_for_normalized(file, &normalized, kind)
8781                .into_iter()
8782                .any(|unit| {
8783                    matches_kind_for_lookup(unit, kind)
8784                        && reference_matches_unit(&normalized, unit)
8785                        && !same_visible_symbol(unit, target)
8786                })
8787    }
8788
8789    pub fn resolve_call_return_binding(
8790        &self,
8791        analyzer: &CppGraphSource<'_>,
8792        file: &ProjectFile,
8793        raw_name: &str,
8794        arity: usize,
8795        lexical_namespace: Option<&str>,
8796        direct_type: Option<&CodeUnit>,
8797    ) -> Option<CppScanBinding> {
8798        let normalized = normalize_reference_name(raw_name)?;
8799        let mut candidates = Vec::new();
8800        for function in
8801            self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8802        {
8803            if cpp_callable_arity(analyzer, function).is_none_or(|known| known.accepts(arity))
8804                && !direct_type.is_some_and(|direct_type| {
8805                    self.callable_is_constructor_declaration(analyzer, function)
8806                        && type_owner_of(analyzer, function)
8807                            .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8808                })
8809            {
8810                candidates.push(function.clone());
8811            }
8812        }
8813        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8814        unanimous_return_binding(analyzer, self, file, &candidates)
8815    }
8816
8817    pub fn resolve_call_return_binding_without_arity(
8818        &self,
8819        analyzer: &CppGraphSource<'_>,
8820        file: &ProjectFile,
8821        raw_name: &str,
8822        lexical_namespace: Option<&str>,
8823        direct_type: Option<&CodeUnit>,
8824    ) -> (bool, Option<CppScanBinding>) {
8825        let Some(normalized) = normalize_reference_name(raw_name) else {
8826            return (false, None);
8827        };
8828        let mut candidates = self
8829            .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8830            .into_iter()
8831            .filter(|function| {
8832                function.is_function()
8833                    && !direct_type.is_some_and(|direct_type| {
8834                        self.callable_is_constructor_declaration(analyzer, function)
8835                            && type_owner_of(analyzer, function)
8836                                .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8837                    })
8838            })
8839            .cloned()
8840            .collect::<Vec<_>>();
8841        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8842        let has_candidates = !candidates.is_empty();
8843        (
8844            has_candidates,
8845            unanimous_return_binding(analyzer, self, file, &candidates),
8846        )
8847    }
8848
8849    pub fn visible_identifier_candidates<'b>(
8850        &'b self,
8851        file: &ProjectFile,
8852        identifier: &str,
8853    ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
8854        self.visible_by_identifier
8855            .get(file)
8856            .and_then(|by_name| by_name.get(identifier))
8857            .into_iter()
8858            .flatten()
8859    }
8860
8861    /// Whether a grammar component is the direct spelling of `target`.
8862    ///
8863    /// Concrete template specializations are indexed with their arguments in
8864    /// `identifier`, while a reference's name component contains only the
8865    /// primary name. Argument matching remains the later resolver's job.
8866    pub fn type_reference_component_directly_names_target(
8867        &self,
8868        component: &str,
8869        target: &CodeUnit,
8870    ) -> bool {
8871        component == target.identifier()
8872            || self
8873                .cpp_template_metadata
8874                .get(target)
8875                .is_some_and(|metadata| component == metadata.primary_name)
8876    }
8877
8878    /// Return terminal reference names that can denote `target` from `file`.
8879    ///
8880    /// The indexed candidate table covers ordinary declarations and aliases;
8881    /// Parser-only aliases are tested lazily when their spelling is actually
8882    /// encountered in a scanned type node. Enumerating them here would parse
8883    /// every source in the include closure even when the target's direct name
8884    /// is the only spelling present in the file.
8885    pub fn visible_type_reference_component_names_for_target(
8886        &self,
8887        analyzer: &CppGraphSource<'_>,
8888        file: &ProjectFile,
8889        target: &CodeUnit,
8890    ) -> HashSet<String> {
8891        let mut names = HashSet::from_iter([target.identifier().to_string()]);
8892        if let Some(metadata) = self.cpp_template_metadata.get(target) {
8893            names.insert(metadata.primary_name.clone());
8894        }
8895
8896        if let Some(by_identifier) = self.visible_by_identifier.get(file) {
8897            for (identifier, candidates) in by_identifier {
8898                if candidates.iter().any(|candidate| {
8899                    (candidate.is_class()
8900                        && (same_visible_symbol(candidate, target)
8901                            || self.compatible_primary_template_redeclarations(candidate, target)))
8902                        || (declared_type_alias(analyzer, candidate)
8903                            && self.alias_candidate_structurally_reaches_target(
8904                                analyzer, file, candidate, target,
8905                            ))
8906                }) {
8907                    names.insert(identifier.clone());
8908                }
8909            }
8910        }
8911
8912        names
8913    }
8914
8915    pub fn indexed_structural_class_scope(
8916        &self,
8917        file: &ProjectFile,
8918        class: Node<'_>,
8919        source: &str,
8920    ) -> Option<Vec<String>> {
8921        let key = (file.clone(), class.start_byte(), class.end_byte());
8922        if let Some(cached) = self
8923            .indexed_structural_class_scopes
8924            .lock()
8925            .expect("C++ indexed structural-class scope cache poisoned")
8926            .get(&key)
8927            .cloned()
8928        {
8929            return cached;
8930        }
8931        let resolved = (|| {
8932            let name = class.child_by_field_name("name")?;
8933            let identifier = if name.kind() == "template_type" {
8934                node_text(name.child_by_field_name("name")?, source).to_string()
8935            } else {
8936                let mut components = Vec::new();
8937                append_cpp_name_components(name, source, &mut components)?;
8938                components.last()?.clone()
8939            };
8940            let visible = self
8941                .visible_identifier_candidates(file, &identifier)
8942                .cloned()
8943                .collect::<Vec<_>>();
8944            let mut visible = visible;
8945            for candidate in
8946                self.visible_by_file
8947                    .get(file)
8948                    .into_iter()
8949                    .flatten()
8950                    .filter(|candidate| {
8951                        self.cpp_template_metadata
8952                            .get(candidate)
8953                            .is_some_and(|metadata| metadata.primary_name == identifier)
8954                    })
8955            {
8956                if !visible
8957                    .iter()
8958                    .any(|existing| same_logical_symbol(existing, candidate))
8959                {
8960                    visible.push(candidate.clone());
8961                }
8962            }
8963            // Built once per call rather than per candidate; `cpp_source` rebuilds
8964            // the five-field source from the same `self.cpp` on every call.
8965            let cpp_source = self.cpp_source();
8966            let candidates = visible
8967                .iter()
8968                .filter(|candidate| {
8969                    candidate.source() == file
8970                        && candidate.is_class()
8971                        && !declared_type_alias(&cpp_source, candidate)
8972                        && self.cpp.ranges(candidate).iter().any(|range| {
8973                            range.start_byte <= class.start_byte()
8974                                && class.end_byte() <= range.end_byte
8975                        })
8976                })
8977                .collect::<Vec<_>>();
8978            let owner = if name.kind() == "template_type" {
8979                let expected = normalize_cpp_whitespace(node_text(name, source));
8980                let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
8981                let exact = candidates
8982                    .iter()
8983                    .copied()
8984                    .filter(|candidate| {
8985                        candidate
8986                            .fq()
8987                            .segments()
8988                            .iter()
8989                            .rev()
8990                            .find_map(|&segment| {
8991                                let (text, kind) = interner.resolve(segment);
8992                                matches!(
8993                                    kind,
8994                                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
8995                                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
8996                                )
8997                                .then_some(text)
8998                            })
8999                            .is_some_and(|text| text == expected)
9000                    })
9001                    .collect::<Vec<_>>();
9002                unique_logical_type_candidate(exact)
9003                    .or_else(|| unique_logical_type_candidate(candidates.clone()))?
9004            } else {
9005                unique_logical_type_candidate(candidates)?
9006            };
9007            Some(canonical_cpp_scope_components(&owner))
9008        })();
9009        self.indexed_structural_class_scopes
9010            .lock()
9011            .expect("C++ indexed structural-class scope cache poisoned")
9012            .insert(key, resolved.clone());
9013        resolved
9014    }
9015
9016    pub fn indexed_enclosing_owner_scope(
9017        &self,
9018        analyzer: &CppGraphSource<'_>,
9019        file: &ProjectFile,
9020        node: Node<'_>,
9021    ) -> Option<Vec<String>> {
9022        let anchor = std::iter::successors(Some(node), |current| current.parent())
9023            .find(|current| {
9024                matches!(
9025                    current.kind(),
9026                    "function_definition"
9027                        | "class_specifier"
9028                        | "struct_specifier"
9029                        | "union_specifier"
9030                )
9031            })
9032            .unwrap_or(node);
9033        let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
9034        if let Some(cached) = self
9035            .indexed_enclosing_owner_scopes
9036            .lock()
9037            .expect("C++ indexed enclosing-owner scope cache poisoned")
9038            .get(&key)
9039            .cloned()
9040        {
9041            return cached;
9042        }
9043        let resolved = (|| {
9044            let range = Range {
9045                start_byte: node.start_byte(),
9046                end_byte: node.end_byte(),
9047                start_line: node.start_position().row,
9048                end_line: node.end_position().row,
9049            };
9050            let start = analyzer.enclosing_code_unit(file, &range)?;
9051            let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
9052                start,
9053                |unit| self.cached_precise_parent_of(analyzer, unit),
9054            )
9055            .find(|unit| {
9056                unit.is_class()
9057                    && !analyzer
9058                        .type_alias_provider()
9059                        .is_some_and(|provider| provider.is_type_alias(unit))
9060            })?;
9061            Some(canonical_cpp_scope_components(&owner))
9062        })();
9063        self.indexed_enclosing_owner_scopes
9064            .lock()
9065            .expect("C++ indexed enclosing-owner scope cache poisoned")
9066            .insert(key, resolved.clone());
9067        resolved
9068    }
9069
9070    fn cached_precise_parent_of(
9071        &self,
9072        analyzer: &CppGraphSource<'_>,
9073        code_unit: &CodeUnit,
9074    ) -> Option<CodeUnit> {
9075        if let Some(cached) = self
9076            .precise_parent_cache
9077            .lock()
9078            .expect("C++ precise-parent cache poisoned")
9079            .get(code_unit)
9080            .cloned()
9081        {
9082            return cached;
9083        }
9084        let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
9085        self.precise_parent_cache
9086            .lock()
9087            .expect("C++ precise-parent cache poisoned")
9088            .insert(code_unit.clone(), resolved.clone());
9089        resolved
9090    }
9091
9092    pub fn callable_is_constructor_declaration(
9093        &self,
9094        analyzer: &CppGraphSource<'_>,
9095        candidate: &CodeUnit,
9096    ) -> bool {
9097        if !candidate.is_function() {
9098            return false;
9099        }
9100        if analyzer
9101            .declaration_source_properties(candidate)
9102            .is_some_and(|facts| facts.iter().any(|fact| fact.callable_is_constructor))
9103        {
9104            return true;
9105        }
9106        let metadata = analyzer.signature_metadata(candidate);
9107        !metadata.is_empty()
9108            && metadata
9109                .iter()
9110                .all(|signature| signature.return_type_text().is_none())
9111    }
9112
9113    /// Whether a callable declaration is a class-template deduction guide.
9114    ///
9115    /// Tree-sitter represents `Box(T) -> Box<T>;` as a declaration with no
9116    /// type field whose function declarator owns a trailing return type. This
9117    /// structured shape distinguishes a guide from both a constructor (no
9118    /// trailing return) and an ordinary trailing-return function (an `auto`
9119    /// type field).
9120    pub fn callable_is_deduction_guide_declaration(
9121        &self,
9122        analyzer: &CppGraphSource<'_>,
9123        candidate: &CodeUnit,
9124    ) -> bool {
9125        callable_declaration_has_fact(analyzer, candidate, |fact| fact.callable_is_deduction_guide)
9126    }
9127
9128    /// Whether a callable occurrence is directly wrapped by a C++ template
9129    /// declaration. This deliberately inspects declaration syntax instead of
9130    /// inferring template status from the rendered signature.
9131    pub fn callable_is_template_declaration(
9132        &self,
9133        analyzer: &CppGraphSource<'_>,
9134        candidate: &CodeUnit,
9135    ) -> bool {
9136        callable_declaration_has_fact(analyzer, candidate, |fact| fact.callable_is_template)
9137    }
9138
9139    pub fn type_name_candidates<'b>(
9140        &'b self,
9141        file: &ProjectFile,
9142        normalized: &str,
9143    ) -> Vec<&'b CodeUnit> {
9144        self.candidate_units(file, normalized, TargetKind::Type)
9145    }
9146
9147    pub fn visible_members_for_owner_name<'b>(
9148        &'b self,
9149        file: &ProjectFile,
9150        owner: &CodeUnit,
9151        name: &str,
9152    ) -> Vec<&'b CodeUnit> {
9153        self.visible_identifier_candidates(file, name)
9154            .filter(|unit| {
9155                // Structured owner pop on the unit's own `fq()` (shared with
9156                // `CodeUnitIndex::parent_of`), not a re-split of its rendered fqn
9157                // string.
9158                brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
9159                    .is_some_and(|parent| parent == owner.fq_name())
9160            })
9161            .collect()
9162    }
9163
9164    pub fn visible_member_for_owner_name(
9165        &self,
9166        file: &ProjectFile,
9167        owner: &CodeUnit,
9168        name: &str,
9169    ) -> VisibleMemberResolution {
9170        let candidates = self.visible_members_for_owner_name(file, owner, name);
9171        let mut callables = Vec::new();
9172        let mut non_callable = None;
9173        for candidate in candidates {
9174            if candidate.is_function() {
9175                callables.push(candidate.clone());
9176            } else if non_callable.is_none() {
9177                non_callable = Some(candidate.clone());
9178            }
9179        }
9180        match (callables.is_empty(), non_callable) {
9181            (false, None) => VisibleMemberResolution::Callable(callables),
9182            (true, Some(_)) => VisibleMemberResolution::NonCallable,
9183            (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
9184            (true, None) => VisibleMemberResolution::Missing,
9185        }
9186    }
9187
9188    fn field_declared_type_fact(
9189        &self,
9190        analyzer: &CppGraphSource<'_>,
9191        field: &CodeUnit,
9192    ) -> Option<DeclaredFieldTypeFact> {
9193        if let Some(cached) = self
9194            .field_type_facts
9195            .lock()
9196            .expect("C++ field type fact cache poisoned")
9197            .get(field)
9198            .cloned()
9199        {
9200            return cached;
9201        }
9202        let decoded = decode_field_declared_type_fact(analyzer, field);
9203        self.field_type_facts
9204            .lock()
9205            .expect("C++ field type fact cache poisoned")
9206            .insert(field.clone(), decoded.clone());
9207        decoded
9208    }
9209
9210    fn structured_alias_target(
9211        &self,
9212        analyzer: &CppGraphSource<'_>,
9213        unit: &CodeUnit,
9214    ) -> Option<StructuredAliasTarget> {
9215        if let Some(cached) = self
9216            .structured_alias_targets
9217            .lock()
9218            .expect("C++ structured alias target cache poisoned")
9219            .get(unit)
9220            .cloned()
9221        {
9222            return cached;
9223        }
9224        let decoded = decode_structured_alias_target(analyzer, unit);
9225        self.structured_alias_targets
9226            .lock()
9227            .expect("C++ structured alias target cache poisoned")
9228            .insert(unit.clone(), decoded.clone());
9229        decoded
9230    }
9231
9232    pub fn type_candidates<'b>(
9233        &'b self,
9234        file: &ProjectFile,
9235        normalized: &str,
9236    ) -> Vec<&'b CodeUnit> {
9237        let mut candidates = self
9238            .candidate_units(file, normalized, TargetKind::Type)
9239            .into_iter()
9240            .filter(|unit| {
9241                unit.kind() == CodeUnitType::Class || declared_type_alias(&self.cpp_source(), unit)
9242            })
9243            .collect::<Vec<_>>();
9244        dedup_unit_refs(&mut candidates);
9245        candidates
9246    }
9247
9248    pub fn named_candidates_for_normalized<'b>(
9249        &'b self,
9250        file: &ProjectFile,
9251        normalized: &str,
9252        kind: TargetKind,
9253    ) -> Vec<&'b CodeUnit> {
9254        let mut candidates = self
9255            .candidate_units(file, normalized, kind)
9256            .into_iter()
9257            .filter(|unit| {
9258                matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
9259            })
9260            .collect::<Vec<_>>();
9261        dedup_unit_refs(&mut candidates);
9262        candidates
9263    }
9264
9265    pub fn candidate_units<'b>(
9266        &'b self,
9267        file: &ProjectFile,
9268        normalized: &str,
9269        kind: TargetKind,
9270    ) -> Vec<&'b CodeUnit> {
9271        if normalized.contains("::") {
9272            // `normalized` comes from `normalize_cpp_reference_text`, which
9273            // truncates at the first `(`/`{`/`<`, leaving a plain `::`-joined
9274            // qualified-id with no embedded `.`/`/`/`\` and operator tokens
9275            // kept intact by the shared splitter's operator merge — the same
9276            // domain `cpp_reference_fqn_candidates` below already parses with
9277            // the shared splitter. Re-tokenizing and taking the last segment
9278            // reproduces `rsplit("::").find(non-empty)`'s terminal-component
9279            // scan exactly.
9280            let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9281                brokk_bifrost_core::analyzer::Language::Cpp,
9282                normalized,
9283            );
9284            let Some(identifier) = parts.last() else {
9285                return Vec::new();
9286            };
9287            let fqns = cpp_reference_fqn_candidates_from_parts(&parts, kind);
9288            return self
9289                .visible_identifier_candidates(file, identifier)
9290                .filter(|unit| {
9291                    #[cfg(any(test, feature = "test-support"))]
9292                    self.qualified_candidate_inspections
9293                        .fetch_add(1, Ordering::Relaxed);
9294                    fqns.iter().any(|fqn| unit.fq_name() == *fqn)
9295                        || canonical_cpp_name_matches(unit, normalized)
9296                })
9297                .collect();
9298        }
9299        self.visible_identifier_candidates(file, normalized)
9300            .collect()
9301    }
9302
9303    #[cfg(any(test, feature = "test-support"))]
9304    pub fn reset_qualified_candidate_inspections(&self) {
9305        self.qualified_candidate_inspections
9306            .store(0, Ordering::Relaxed);
9307    }
9308
9309    #[cfg(any(test, feature = "test-support"))]
9310    pub fn qualified_candidate_inspections(&self) -> usize {
9311        self.qualified_candidate_inspections.load(Ordering::Relaxed)
9312    }
9313
9314    #[cfg(any(test, feature = "test-support"))]
9315    pub fn visibility_identifier_lookup_count(&self) -> usize {
9316        self.visibility_identifier_lookup_count
9317    }
9318
9319    #[cfg(any(test, feature = "test-support"))]
9320    pub fn visibility_identifier_batch_count(&self) -> usize {
9321        self.visibility_identifier_batch_count
9322    }
9323
9324    #[cfg(any(test, feature = "test-support"))]
9325    pub fn reset_target_preserving_type_resolution_count(&self) {
9326        self.target_preserving_type_resolution_count
9327            .store(0, Ordering::Relaxed);
9328    }
9329
9330    #[cfg(any(test, feature = "test-support"))]
9331    pub fn target_preserving_type_resolution_count(&self) -> usize {
9332        self.target_preserving_type_resolution_count
9333            .load(Ordering::Relaxed)
9334    }
9335
9336    #[cfg(any(test, feature = "test-support"))]
9337    pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
9338        self.visible_parser_alias_name_set_build_count
9339            .load(Ordering::Relaxed)
9340    }
9341}
9342
9343fn callable_declaration_has_fact(
9344    analyzer: &CppGraphSource<'_>,
9345    candidate: &CodeUnit,
9346    matches: impl Fn(&brokk_bifrost_core::analyzer::cpp_facts::CppDeclarationSourceFact) -> bool,
9347) -> bool {
9348    candidate.is_function()
9349        && analyzer
9350            .declaration_source_properties(candidate)
9351            .is_some_and(|facts| facts.iter().any(matches))
9352}
9353
9354#[derive(Default)]
9355struct IncludeGraph {
9356    targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
9357}
9358
9359impl IncludeGraph {
9360    fn extend_with<F>(
9361        &mut self,
9362        root: &ProjectFile,
9363        cancellation: Option<&CancellationToken>,
9364        targets_for: &mut F,
9365    ) where
9366        F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9367    {
9368        let mut stack = vec![root.clone()];
9369        while let Some(file) = stack.pop() {
9370            if cancellation.is_some_and(CancellationToken::is_cancelled) {
9371                break;
9372            }
9373            if self.targets_by_file.contains_key(&file) {
9374                continue;
9375            }
9376            let targets = targets_for(&file);
9377            stack.extend(targets.iter().cloned());
9378            self.targets_by_file.insert(file, targets);
9379        }
9380    }
9381
9382    fn files(&self) -> impl Iterator<Item = &ProjectFile> {
9383        self.targets_by_file.keys()
9384    }
9385
9386    fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
9387        self.targets_by_file
9388            .get(file)
9389            .map(Vec::as_slice)
9390            .unwrap_or_default()
9391    }
9392
9393    fn reachable_files(
9394        &self,
9395        root: &ProjectFile,
9396        cancellation: Option<&CancellationToken>,
9397    ) -> HashSet<ProjectFile> {
9398        let mut pending = vec![root.clone()];
9399        let mut visited = HashSet::default();
9400        while let Some(file) = pending.pop() {
9401            if cancellation.is_some_and(CancellationToken::is_cancelled) {
9402                break;
9403            }
9404            if visited.insert(file.clone()) {
9405                pending.extend(self.targets(&file).iter().cloned());
9406            }
9407        }
9408        visited
9409    }
9410}
9411
9412fn build_bounded_visible_declarations(
9413    cpp: &dyn CppSource,
9414    token: QueryToken<'_>,
9415    analyzer: &CppGraphSource<'_>,
9416    roots: &HashSet<ProjectFile>,
9417    visible_sources: &HashMap<ProjectFile, HashSet<ProjectFile>>,
9418    cancellation: Option<&CancellationToken>,
9419    stats: &mut BoundedVisibilityStats,
9420) -> HashMap<ProjectFile, HashSet<CodeUnit>> {
9421    let mut candidates_by_identifier = HashMap::default();
9422    let mut declarations_by_source_and_reading: HashMap<
9423        (ProjectFile, bool),
9424        BoundedVisibilityDeclarations,
9425    > = HashMap::default();
9426    let mut dependency_names_by_unit: HashMap<CodeUnit, HashSet<String>> = HashMap::default();
9427    roots
9428        .iter()
9429        .map(|root| {
9430            let reading_is_c = analyzer.reference_uses_c_semantics(root);
9431            let root_declarations = declarations_by_source_and_reading
9432                .entry((root.clone(), reading_is_c))
9433                .or_insert_with(|| {
9434                    bounded_visibility_declarations(cpp, analyzer, root, reading_is_c, stats)
9435                });
9436            let mut visible = root_declarations
9437                .all
9438                .iter()
9439                .cloned()
9440                .collect::<HashSet<_>>();
9441            let mut pending_names = HashSet::default();
9442            if let Some(prepared) = cpp.prepared_syntax(token, root) {
9443                // One cursor for the whole file walk: `named_child(index)`
9444                // re-steps the sibling list from the first child on every
9445                // access, which made this whole-file scan quadratic in the
9446                // fan-out of each node (#3097).
9447                let mut cursor = prepared.tree().walk();
9448                let mut pending_nodes = vec![prepared.tree().root_node()];
9449                while let Some(node) = pending_nodes.pop() {
9450                    if matches!(
9451                        node.kind(),
9452                        "identifier"
9453                            | "type_identifier"
9454                            | "field_identifier"
9455                            | "namespace_identifier"
9456                    ) {
9457                        pending_names.insert(node_text(node, prepared.source()).to_string());
9458                    }
9459                    if node.kind() == "preproc_arg" {
9460                        for reference in
9461                            object_macro_replacement_type_references(node, prepared.source())
9462                        {
9463                            pending_names.extend(reference.components);
9464                        }
9465                    }
9466                    pending_nodes.extend(node.named_children(&mut cursor));
9467                }
9468            }
9469            stats.root_names += pending_names.len();
9470            let mut completed_names = HashSet::default();
9471            while !pending_names.is_empty() {
9472                stats.rounds += 1;
9473                let round_names = std::mem::take(&mut pending_names);
9474                let mut requested_names_by_source: HashMap<ProjectFile, HashSet<String>> =
9475                    HashMap::default();
9476                let mut identifiers = Vec::new();
9477                for identifier in round_names {
9478                    if !completed_names.insert(identifier.clone())
9479                        || cancellation.is_some_and(CancellationToken::is_cancelled)
9480                    {
9481                        continue;
9482                    }
9483                    identifiers.push(identifier);
9484                }
9485                let missing_identifiers = identifiers
9486                    .iter()
9487                    .filter(|identifier| !candidates_by_identifier.contains_key(*identifier))
9488                    .cloned()
9489                    .collect::<HashSet<_>>();
9490                if !missing_identifiers.is_empty() {
9491                    let lookup_started = Instant::now();
9492                    let mut candidates = cpp
9493                        .visibility_identifier_candidates_batch(&missing_identifiers, cancellation);
9494                    stats.lookup_elapsed += lookup_started.elapsed();
9495                    stats.identifier_lookups += missing_identifiers.len();
9496                    stats.identifier_batches += 1;
9497                    if cancellation.is_some_and(CancellationToken::is_cancelled) {
9498                        break;
9499                    }
9500                    for identifier in missing_identifiers {
9501                        let units = candidates.remove(&identifier).unwrap_or_default();
9502                        let candidate_count = units.len();
9503                        let candidate_sources = units
9504                            .into_iter()
9505                            .map(|unit| unit.source().clone())
9506                            .collect::<HashSet<_>>();
9507                        candidates_by_identifier
9508                            .insert(identifier, (candidate_sources, candidate_count));
9509                    }
9510                }
9511                for identifier in identifiers {
9512                    let (candidate_sources, candidate_count) = candidates_by_identifier
9513                        .get(&identifier)
9514                        .expect("every missing identifier was inserted after the batch lookup");
9515                    stats.candidate_units += *candidate_count;
9516                    for source in candidate_sources.iter().cloned() {
9517                        if source != *root
9518                            && visible_sources
9519                                .get(root)
9520                                .is_some_and(|files| files.contains(&source))
9521                        {
9522                            requested_names_by_source
9523                                .entry(source)
9524                                .or_default()
9525                                .insert(identifier.clone());
9526                        }
9527                    }
9528                }
9529                stats.candidate_sources += requested_names_by_source.len();
9530                for (source, requested_names) in requested_names_by_source {
9531                    let declarations = declarations_by_source_and_reading
9532                        .entry((source.clone(), reading_is_c))
9533                        .or_insert_with(|| {
9534                            bounded_visibility_declarations(
9535                                cpp,
9536                                analyzer,
9537                                &source,
9538                                reading_is_c,
9539                                stats,
9540                            )
9541                        });
9542                    let mut selected = HashSet::default();
9543                    for name in requested_names {
9544                        if let Some(units) = declarations.by_identifier.get(&name) {
9545                            selected.extend(units.iter().cloned());
9546                        }
9547                    }
9548                    for unit in selected {
9549                        stats.selected_units += 1;
9550                        let dependency_names = dependency_names_by_unit
9551                            .entry(unit.clone())
9552                            .or_insert_with(|| {
9553                                let facts_started = Instant::now();
9554                                let mut dependency_names = HashSet::default();
9555                                if let Some(facts) = analyzer.declaration_source_properties(&unit) {
9556                                    for fact in facts {
9557                                        for name in fact.dependency_type_names {
9558                                            stats.dependency_source_names += 1;
9559                                            #[cfg(any(test, feature = "test-support"))]
9560                                            BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT
9561                                                .with(|count| count.set(count.get() + 1));
9562                                            dependency_names.insert(name);
9563                                        }
9564                                    }
9565                                    stats.dependency_source_elapsed += facts_started.elapsed();
9566                                }
9567                                dependency_names
9568                            });
9569                        for name in dependency_names.iter() {
9570                            if !completed_names.contains(name) && pending_names.insert(name.clone())
9571                            {
9572                                stats.dependency_names += 1;
9573                            }
9574                        }
9575                        visible.insert(unit);
9576                    }
9577                }
9578            }
9579            (root.clone(), visible)
9580        })
9581        .collect()
9582}
9583
9584struct BoundedVisibilityDeclarations {
9585    all: Vec<CodeUnit>,
9586    by_identifier: HashMap<String, Vec<CodeUnit>>,
9587}
9588
9589fn bounded_visibility_declarations(
9590    cpp: &dyn CppSource,
9591    analyzer: &CppGraphSource<'_>,
9592    file: &ProjectFile,
9593    c_semantics: bool,
9594    stats: &mut BoundedVisibilityStats,
9595) -> BoundedVisibilityDeclarations {
9596    let declarations_started = Instant::now();
9597    let all = bounded_visibility_declarations_in_reading(analyzer, file, c_semantics)
9598        .into_iter()
9599        .collect::<Vec<_>>();
9600    stats.declaration_elapsed += declarations_started.elapsed();
9601    stats.declaration_reads += 1;
9602    stats.declaration_units += all.len();
9603
9604    let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
9605    for unit in &all {
9606        by_identifier
9607            .entry(unit.identifier().to_string())
9608            .or_default()
9609            .push(unit.clone());
9610        if unit.is_class()
9611            && let Some(metadata) = cpp.template_metadata(unit)
9612            && metadata.primary_name != unit.identifier()
9613        {
9614            by_identifier
9615                .entry(metadata.primary_name)
9616                .or_default()
9617                .push(unit.clone());
9618        }
9619    }
9620    BoundedVisibilityDeclarations { all, by_identifier }
9621}
9622
9623#[derive(Default)]
9624struct BoundedVisibilityStats {
9625    rounds: usize,
9626    root_names: usize,
9627    identifier_lookups: usize,
9628    identifier_batches: usize,
9629    candidate_units: usize,
9630    candidate_sources: usize,
9631    declaration_reads: usize,
9632    declaration_units: usize,
9633    selected_units: usize,
9634    dependency_source_names: usize,
9635    dependency_names: usize,
9636    lookup_elapsed: Duration,
9637    declaration_elapsed: Duration,
9638    dependency_source_elapsed: Duration,
9639}
9640
9641fn bounded_visibility_declarations_in_reading(
9642    analyzer: &CppGraphSource<'_>,
9643    file: &ProjectFile,
9644    c_semantics: bool,
9645) -> BTreeSet<CodeUnit> {
9646    #[cfg(any(test, feature = "test-support"))]
9647    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(count.get() + 1));
9648    analyzer.declarations_in_reading(file, c_semantics)
9649}
9650
9651#[cfg(any(test, feature = "test-support"))]
9652pub fn reset_bounded_visibility_declaration_read_count_for_test() {
9653    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(0));
9654}
9655
9656#[cfg(any(test, feature = "test-support"))]
9657pub fn bounded_visibility_declaration_read_count_for_test() -> usize {
9658    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(Cell::get)
9659}
9660
9661#[cfg(any(test, feature = "test-support"))]
9662pub fn reset_bounded_visibility_dependency_ast_node_count_for_test() {
9663    BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(|count| count.set(0));
9664}
9665
9666#[cfg(any(test, feature = "test-support"))]
9667pub fn bounded_visibility_dependency_ast_node_count_for_test() -> usize {
9668    BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(Cell::get)
9669}
9670
9671pub struct VisibilityData {
9672    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
9673    pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
9674}
9675
9676/// Build the per-root include closure and the declarations each root can see
9677/// through it.
9678///
9679/// `declarations_for` takes the reading to answer in (issue #1970): a root
9680/// compiled as C sees the C reading of every file in its closure, a root
9681/// compiled as C++ sees the C++ reading, and `reading_is_c_for` decides which
9682/// per root. The two readings agree for all but a handful of headers, so the
9683/// C map is built only when some root actually asks for it, and only over the
9684/// files that root reaches.
9685pub fn build_visibility_data<F, R, D>(
9686    roots: &HashSet<ProjectFile>,
9687    cancellation: Option<&CancellationToken>,
9688    mut targets_for: F,
9689    mut reading_is_c_for: R,
9690    mut declarations_for: D,
9691) -> VisibilityData
9692where
9693    F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9694    R: FnMut(&ProjectFile) -> bool,
9695    D: FnMut(&ProjectFile, bool) -> BTreeSet<CodeUnit>,
9696{
9697    let mut include_graph = IncludeGraph::default();
9698    for file in roots {
9699        if cancellation.is_some_and(CancellationToken::is_cancelled) {
9700            break;
9701        }
9702        include_graph.extend_with(file, cancellation, &mut targets_for);
9703    }
9704    let cpp_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
9705        .files()
9706        .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
9707        .map(|file| (file.clone(), declarations_for(file, false)))
9708        .collect();
9709    let mut c_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = HashMap::default();
9710    let mut visible_by_file = HashMap::default();
9711    let mut visible_source_files_by_root = HashMap::default();
9712    for file in roots {
9713        if cancellation.is_some_and(CancellationToken::is_cancelled) {
9714            break;
9715        }
9716        let mut visited = HashSet::default();
9717        let mut visible = HashSet::default();
9718        let declarations_by_file = if reading_is_c_for(file) {
9719            for reached in cpp_declarations_by_file.keys() {
9720                if !c_declarations_by_file.contains_key(reached) {
9721                    let declarations = declarations_for(reached, true);
9722                    c_declarations_by_file.insert(reached.clone(), declarations);
9723                }
9724            }
9725            &c_declarations_by_file
9726        } else {
9727            &cpp_declarations_by_file
9728        };
9729        collect_visible_declarations(
9730            &include_graph,
9731            declarations_by_file,
9732            file,
9733            &mut visited,
9734            &mut visible,
9735            cancellation,
9736        );
9737        visible_by_file.insert(file.clone(), visible);
9738        visible_source_files_by_root.insert(file.clone(), visited);
9739    }
9740    VisibilityData {
9741        visible_by_file,
9742        visible_source_files_by_root,
9743    }
9744}
9745
9746/// Admit the class that an out-of-line definition proves is in scope.
9747///
9748/// `Owner::member(...) { ... }` in a file is structured proof that `Owner`
9749/// names a class-like entity in that file's scope: a member declaration can
9750/// live in a file other than its class's only when it is written out of line.
9751/// A file a build concatenates rather than compiles carries no `#include` edge
9752/// to the header declaring `Owner` -- google/wuffs
9753/// `internal/cgen/auxiliary/image.cc` defines
9754/// `DecodeImageResult::DecodeImageResult` and never includes `image.hh` -- so
9755/// every unqualified member and constructor reference in it had no candidate at
9756/// all (#1832).
9757///
9758/// The evidence is the indexed declaration's own owner name, taken from its
9759/// `FqName`, so this stays a structured answer rather than a text fallback.
9760/// Only an owner the file cannot already see is admitted: that is what keeps a
9761/// header declaring its own class from additionally seeing every same-named
9762/// class in the workspace, and it makes the pass free for the ordinary file
9763/// whose owners are all visible.
9764#[derive(Default)]
9765struct OutOfLineOwnerBindingStats {
9766    unseen_owners: usize,
9767    definition_lookups: usize,
9768    admitted: usize,
9769}
9770
9771fn extend_with_out_of_line_owner_bindings(
9772    cpp: &dyn CppSource,
9773    visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
9774) -> OutOfLineOwnerBindingStats {
9775    let mut stats = OutOfLineOwnerBindingStats::default();
9776    for (file, visible) in visible_by_file.iter_mut() {
9777        // The include-closure walk seeds every root with its own declarations,
9778        // so the file's members are already here; re-reading them from the
9779        // analyzer would pay for the same declaration set twice.
9780        let mut unseen_owners: HashSet<String> = visible
9781            .iter()
9782            .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
9783            .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
9784            .collect();
9785        if unseen_owners.is_empty() {
9786            continue;
9787        }
9788        for unit in visible.iter().filter(|unit| unit.is_class()) {
9789            unseen_owners.remove(&unit.fq_name());
9790        }
9791        stats.unseen_owners += unseen_owners.len();
9792        stats.definition_lookups += unseen_owners.len();
9793        let admitted = unseen_owners
9794            .iter()
9795            .flat_map(|owner| cpp.definitions(owner))
9796            .filter(CodeUnit::is_class)
9797            .collect::<Vec<_>>();
9798        stats.admitted += admitted.len();
9799        visible.extend(admitted);
9800    }
9801    stats
9802}
9803
9804pub enum VisibleMemberResolution {
9805    Callable(Vec<CodeUnit>),
9806    NonCallable,
9807    AmbiguousKind,
9808    Missing,
9809}
9810
9811#[derive(Clone)]
9812pub enum EnclosingMemberOwnerResolution {
9813    Owner(CodeUnit),
9814    Ambiguous,
9815    Missing,
9816}
9817
9818pub fn resolve_declaring_member_owner(
9819    analyzer: &CppGraphSource<'_>,
9820    visibility: &VisibilityIndex<'_>,
9821    file: &ProjectFile,
9822    receiver_owner: &CodeUnit,
9823    member_name: &str,
9824) -> EnclosingMemberOwnerResolution {
9825    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
9826        return EnclosingMemberOwnerResolution::Missing;
9827    };
9828    let Some(receiver_owner) =
9829        visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
9830    else {
9831        return EnclosingMemberOwnerResolution::Ambiguous;
9832    };
9833    let resolve_level = |frontier: &[CodeUnit]| {
9834        let mut member_owners = Vec::new();
9835        for raw_owner in frontier {
9836            let Some(owner) =
9837                visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
9838            else {
9839                return EnclosingMemberOwnerResolution::Ambiguous;
9840            };
9841            for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
9842                // A type nested in an owner shares the owner's member-name
9843                // index, but it cannot be the value receiver of `owner.name`.
9844                // Keep value members here so an anonymous aggregate field
9845                // does not become ambiguous with its promoted receiver type.
9846                if !member.is_field() && !member.is_function() {
9847                    continue;
9848                }
9849                let Some(member_owner) = type_owner_of(analyzer, member) else {
9850                    return EnclosingMemberOwnerResolution::Ambiguous;
9851                };
9852                if !member_owners
9853                    .iter()
9854                    .any(|existing| same_visible_symbol(existing, &member_owner))
9855                {
9856                    member_owners.push(member_owner);
9857                }
9858            }
9859        }
9860        match member_owners.len() {
9861            0 => EnclosingMemberOwnerResolution::Missing,
9862            1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
9863            _ => EnclosingMemberOwnerResolution::Ambiguous,
9864        }
9865    };
9866    // The first declaration on each structured base path hides deeper names,
9867    // regardless of whether its callable overload is applicable at a particular
9868    // call site. Applicability is checked only after this owner is established.
9869    let direct = resolve_level(std::slice::from_ref(&receiver_owner));
9870    if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
9871        return direct;
9872    }
9873    let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
9874    let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
9875    let mut path_matches = Vec::new();
9876    while let Some(raw_owner) = stack.pop() {
9877        let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
9878        else {
9879            return EnclosingMemberOwnerResolution::Ambiguous;
9880        };
9881        // Persisted hierarchy edges do not encode virtual-base or base-subobject paths.
9882        // Propagate at most two occurrences of each owner: that preserves the distinction
9883        // between one and multiple resolving base paths without exponential diamond walks.
9884        let propagated = propagated_counts.entry(owner.clone()).or_default();
9885        if *propagated == 2 {
9886            continue;
9887        }
9888        *propagated += 1;
9889        match resolve_level(std::slice::from_ref(&owner)) {
9890            EnclosingMemberOwnerResolution::Owner(owner) => {
9891                path_matches.push(owner);
9892                if path_matches.len() == 2 {
9893                    return EnclosingMemberOwnerResolution::Ambiguous;
9894                }
9895            }
9896            EnclosingMemberOwnerResolution::Ambiguous => {
9897                return EnclosingMemberOwnerResolution::Ambiguous;
9898            }
9899            EnclosingMemberOwnerResolution::Missing => {
9900                stack.extend(hierarchy.get_direct_ancestors(&owner));
9901            }
9902        }
9903    }
9904    match path_matches.len() {
9905        0 => EnclosingMemberOwnerResolution::Missing,
9906        1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
9907        _ => unreachable!("base-path matches are capped at one before returning"),
9908    }
9909}
9910
9911/// Resolve the declaring owner of a callable after applying a member
9912/// `using <Base>::<member>;` declaration to one exact call arity.
9913///
9914/// Ordinary member lookup is intentionally name-based: the first class that
9915/// declares a name hides the same name on deeper bases. A member
9916/// using-declaration is the one exception. When none of the declarations on
9917/// that first owner accepts the call arity, it can reintroduce an applicable
9918/// overload from the named base. If a declaration on the first owner does
9919/// accept the arity, argument types would be needed to choose between it and
9920/// a same-arity introduced overload, so this resolver conservatively keeps the
9921/// ordinary owner (#1835/#1843).
9922///
9923/// The caller supplies ordinary name-based owner resolution so a file scan can
9924/// reuse its existing owner cache before applying this callable-only exception.
9925pub fn resolve_declaring_callable_owner(
9926    analyzer: &CppGraphSource<'_>,
9927    visibility: &VisibilityIndex<'_>,
9928    file: &ProjectFile,
9929    ordinary: EnclosingMemberOwnerResolution,
9930    member_name: &str,
9931    call_arity: usize,
9932) -> EnclosingMemberOwnerResolution {
9933    let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
9934        return ordinary;
9935    };
9936    if visibility
9937        .visible_members_for_owner_name(file, ordinary_owner, member_name)
9938        .into_iter()
9939        .any(|unit| {
9940            unit.is_function()
9941                && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(call_arity))
9942        })
9943    {
9944        return ordinary;
9945    }
9946
9947    let mut pending = match member_using_declaration_bases(
9948        analyzer,
9949        visibility,
9950        file,
9951        ordinary_owner,
9952        member_name,
9953    ) {
9954        Ok(bases) => bases,
9955        Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
9956    };
9957    let mut visited = HashSet::default();
9958    let mut introduced_owners = Vec::new();
9959    while let Some(owner) = pending.pop() {
9960        if !visited.insert(owner.clone()) {
9961            continue;
9962        }
9963        let accepts_arity = visibility
9964            .visible_members_for_owner_name(file, &owner, member_name)
9965            .into_iter()
9966            .any(|unit| {
9967                unit.is_function()
9968                    && cpp_callable_arity(analyzer, unit)
9969                        .is_none_or(|known| known.accepts(call_arity))
9970            });
9971        if accepts_arity {
9972            if !introduced_owners
9973                .iter()
9974                .any(|existing| same_visible_symbol(existing, &owner))
9975            {
9976                introduced_owners.push(owner);
9977            }
9978            continue;
9979        }
9980        match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
9981            Ok(bases) => pending.extend(bases),
9982            Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
9983        }
9984    }
9985    match introduced_owners.as_slice() {
9986        [] => ordinary,
9987        [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
9988        _ => EnclosingMemberOwnerResolution::Ambiguous,
9989    }
9990}
9991
9992fn member_using_declaration_bases(
9993    analyzer: &CppGraphSource<'_>,
9994    visibility: &VisibilityIndex<'_>,
9995    file: &ProjectFile,
9996    owner: &CodeUnit,
9997    member_name: &str,
9998) -> Result<Vec<CodeUnit>, ()> {
9999    let Some(facts) = analyzer.declaration_source_properties(owner) else {
10000        return Err(());
10001    };
10002    let scopes: Vec<_> = facts
10003        .iter()
10004        .flat_map(|fact| &fact.member_usings)
10005        .filter(|using| using.member == member_name)
10006        .map(|using| using.scope.join("::"))
10007        .collect();
10008    if scopes.is_empty() {
10009        return Ok(Vec::new());
10010    }
10011    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
10012        return Ok(Vec::new());
10013    };
10014    let mut bases = Vec::new();
10015    for raw_ancestor in hierarchy.get_ancestors(owner) {
10016        let Some(ancestor) =
10017            visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
10018        else {
10019            return Err(());
10020        };
10021        let qualified = cpp_name_for(&ancestor);
10022        if scopes
10023            .iter()
10024            .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
10025            && !bases
10026                .iter()
10027                .any(|existing| same_visible_symbol(existing, &ancestor))
10028        {
10029            bases.push(ancestor);
10030        }
10031    }
10032    Ok(bases)
10033}
10034
10035pub fn lexical_component_tiers<'a>(
10036    components: &'a [String],
10037    global: bool,
10038    lexical_scope: &'a [String],
10039) -> impl Iterator<Item = Vec<String>> + 'a {
10040    let first_prefix_len = if global { 0 } else { lexical_scope.len() };
10041    (0..=first_prefix_len).rev().map(move |prefix_len| {
10042        let mut qualified = Vec::with_capacity(prefix_len + components.len());
10043        qualified.extend_from_slice(&lexical_scope[..prefix_len]);
10044        qualified.extend_from_slice(components);
10045        qualified
10046    })
10047}
10048
10049pub fn build_visible_identifier_index(
10050    analyzer: &CppGraphSource<'_>,
10051    visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
10052    visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
10053    global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
10054) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
10055    let mut out = HashMap::default();
10056    for (file, visible) in visible_by_file {
10057        let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
10058        for unit in visible {
10059            if unit.is_field()
10060                && !visible_source_files_by_root
10061                    .get(file)
10062                    .is_some_and(|sources| sources.contains(unit.source()))
10063                && cpp_global_field_has_internal_linkage_cached(
10064                    analyzer,
10065                    global_field_internal_linkage,
10066                    unit,
10067                )
10068            {
10069                continue;
10070            }
10071            by_identifier
10072                .entry(unit.identifier().to_string())
10073                .or_default()
10074                .push(unit.clone());
10075        }
10076        for units in by_identifier.values_mut() {
10077            sort_lookup_units(units);
10078            units.dedup();
10079        }
10080        out.insert(file.clone(), by_identifier);
10081    }
10082    out
10083}
10084
10085fn sort_lookup_units(units: &mut [CodeUnit]) {
10086    units.sort_by(|left, right| {
10087        left.fq_name()
10088            .cmp(&right.fq_name())
10089            .then_with(|| left.signature().cmp(&right.signature()))
10090            .then_with(|| left.source().cmp(right.source()))
10091            .then_with(|| left.kind().cmp(&right.kind()))
10092            .then_with(|| {
10093                left.package_segment_count()
10094                    .cmp(&right.package_segment_count())
10095            })
10096            .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
10097            .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
10098    });
10099}
10100
10101fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
10102    let interner = segment_interner();
10103    for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
10104        let (left_text, left_kind) = interner.resolve(left_id);
10105        let (right_text, right_kind) = interner.resolve(right_id);
10106        let order = left_text
10107            .cmp(right_text)
10108            .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
10109        if order != CmpOrdering::Equal {
10110            return order;
10111        }
10112    }
10113    left.len().cmp(&right.len())
10114}
10115
10116const fn segment_kind_order(kind: SegmentKind) -> u8 {
10117    match kind {
10118        SegmentKind::Path => 0,
10119        SegmentKind::Package => 1,
10120        SegmentKind::Type => 2,
10121        SegmentKind::Companion => 3,
10122        SegmentKind::Nested => 4,
10123        SegmentKind::Member => 5,
10124        SegmentKind::Unknown => 6,
10125    }
10126}
10127
10128fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
10129    *units = brokk_bifrost_core::analyzer::usages::inverted_edges::dedup_preserving_order(
10130        std::mem::take(units),
10131    );
10132}
10133
10134pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
10135    // Same domain as `candidate_units` above: `reference` is a plain
10136    // `::`-joined qualified-id with operator tokens kept intact by the shared
10137    // splitter's operator merge.
10138    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10139        brokk_bifrost_core::analyzer::Language::Cpp,
10140        reference,
10141    );
10142    cpp_reference_fqn_candidates_from_parts(&parts, kind)
10143}
10144
10145fn cpp_reference_fqn_candidates_from_parts(parts: &[String], kind: TargetKind) -> Vec<String> {
10146    if parts.is_empty() {
10147        return Vec::new();
10148    }
10149
10150    let mut candidates = Vec::new();
10151    for package_len in 0..parts.len() {
10152        let package = parts[..package_len].join("::");
10153        let rest = &parts[package_len..];
10154        if rest.is_empty() {
10155            continue;
10156        }
10157        match kind {
10158            TargetKind::Type | TargetKind::Constructor => {
10159                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
10160                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10161            }
10162            TargetKind::FreeFunction
10163            | TargetKind::Method
10164            | TargetKind::GlobalField
10165            | TargetKind::MemberField
10166            | TargetKind::Macro => {
10167                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10168                if rest.len() > 1 {
10169                    let owner = rest[..rest.len() - 1].join("$");
10170                    let short = format!("{}.{}", owner, rest[rest.len() - 1]);
10171                    push_cpp_fqn_candidate(&mut candidates, &package, &short);
10172                }
10173            }
10174        }
10175    }
10176    candidates
10177}
10178
10179fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
10180    let fqn = if package.is_empty() {
10181        short.to_string()
10182    } else {
10183        format!("{package}.{short}")
10184    };
10185    if !out.contains(&fqn) {
10186        out.push(fqn);
10187    }
10188}
10189
10190pub fn infer_cpp_initializer_type(
10191    analyzer: &CppGraphSource<'_>,
10192    visibility: &VisibilityIndex<'_>,
10193    file: &ProjectFile,
10194    source: &str,
10195    node: Node<'_>,
10196) -> Option<CodeUnit> {
10197    infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
10198        .and_then(|binding| binding.unit)
10199}
10200
10201pub fn infer_cpp_initializer_binding(
10202    analyzer: &CppGraphSource<'_>,
10203    visibility: &VisibilityIndex<'_>,
10204    file: &ProjectFile,
10205    source: &str,
10206    node: Node<'_>,
10207    receiver_resolver: Option<&ReceiverResolver<'_>>,
10208) -> Option<CppScanBinding> {
10209    match node.kind() {
10210        "new_expression" => {
10211            let type_node = cpp_new_expression_type_node(node)?;
10212            let type_text = node_text(type_node, source);
10213            let name = normalize_cpp_type_name(type_text);
10214            Some(CppScanBinding::from_type_name(
10215                name.clone(),
10216                visibility.resolve_type(file, &name),
10217                1,
10218            ))
10219        }
10220        "call_expression" => node.child_by_field_name("function").and_then(|function| {
10221            // `a().b()` and `p->b()` invoke a member on a receiver *value*. The
10222            // callee's source text is an expression, not a name, and every name
10223            // lookup below normalizes a reference by truncating at the first
10224            // `(`: `first().second` would read as `first`, so the chained call
10225            // would take the type of `first()` instead of the type of
10226            // `first().second()` (#2178). Only the member path can answer for
10227            // this shape, so route to it from the callee's node kind.
10228            if function.kind() == "field_expression" {
10229                let arity = visibility.call_arity_evidence(file, node, source).exact()?;
10230                return resolve_field_method_call_return_binding(
10231                    analyzer,
10232                    visibility,
10233                    file,
10234                    source,
10235                    function,
10236                    arity,
10237                    receiver_resolver,
10238                );
10239            }
10240            let function_text = node_text(function, source);
10241            let direct_type_binding = visibility
10242                .resolve_type(file, function_text)
10243                .map(|unit| CppScanBinding::from_unit(unit, 0));
10244            if function.kind() == "template_function" && direct_type_binding.is_some() {
10245                let lexical_namespace = enclosing_namespace_context(node, source);
10246                let arity = visibility.call_arity_evidence(file, node, source).exact();
10247                if let Some(arity) = arity
10248                    && let Some(binding) = visibility.resolve_call_return_binding(
10249                        analyzer,
10250                        file,
10251                        function_text,
10252                        arity,
10253                        lexical_namespace.as_deref(),
10254                        direct_type_binding
10255                            .as_ref()
10256                            .and_then(|binding| binding.unit.as_ref()),
10257                    )
10258                {
10259                    return Some(binding);
10260                }
10261                let (has_callable, callable_binding) = visibility
10262                    .resolve_call_return_binding_without_arity(
10263                        analyzer,
10264                        file,
10265                        function_text,
10266                        lexical_namespace.as_deref(),
10267                        direct_type_binding
10268                            .as_ref()
10269                            .and_then(|binding| binding.unit.as_ref()),
10270                    );
10271                if let Some(binding) = callable_binding {
10272                    return Some(binding);
10273                }
10274                if has_callable {
10275                    return None;
10276                }
10277                return direct_type_binding;
10278            }
10279            // Only the return-typed branches need the argument count. An
10280            // unknown arity leaves them out, exactly as in the template arm
10281            // above, and still constructs the direct type: `File(getPath())`
10282            // names `File` whether or not `getPath()`'s expansion is provable.
10283            let arity = visibility.call_arity_evidence(file, node, source).exact();
10284            if let Some(arity) = arity {
10285                let direct_type_binding_for_call = direct_type_binding.clone();
10286                if let Some(binding) = resolve_static_method_call_return_binding(
10287                    analyzer, visibility, file, source, function, arity,
10288                )
10289                .or_else(|| {
10290                    // An applicable free function supplies the receiver value
10291                    // before an unrelated visible type with the same terminal
10292                    // name. The direct type still excludes its own constructor
10293                    // declaration below and remains the construction fallback.
10294                    visibility.resolve_call_return_binding(
10295                        analyzer,
10296                        file,
10297                        function_text,
10298                        arity,
10299                        enclosing_namespace_context(node, source).as_deref(),
10300                        direct_type_binding_for_call
10301                            .as_ref()
10302                            .and_then(|binding| binding.unit.as_ref()),
10303                    )
10304                }) {
10305                    return Some(binding);
10306                }
10307            }
10308            direct_type_binding
10309        }),
10310        _ => None,
10311    }
10312}
10313
10314/// Return the type specifier carried by a C++ `new_expression`.
10315///
10316/// The grammar names this child with the `type` field. If recovery omits that
10317/// field, recover only from type-specifier nodes retained in the parse tree;
10318/// skip placement arguments, the array declarator, and initializer arguments.
10319pub fn cpp_new_expression_type_node(expression: Node<'_>) -> Option<Node<'_>> {
10320    debug_assert_eq!(expression.kind(), "new_expression");
10321    if let Some(type_node) = expression.child_by_field_name("type") {
10322        return Some(type_node);
10323    }
10324
10325    let mut pending = Vec::new();
10326    let mut cursor = expression.walk();
10327    pending.extend(
10328        expression
10329            .named_children(&mut cursor)
10330            .filter(|child| {
10331                !matches!(
10332                    child.kind(),
10333                    "argument_list" | "initializer_list" | "new_declarator"
10334                )
10335            })
10336            .collect::<Vec<_>>()
10337            .into_iter()
10338            .rev(),
10339    );
10340    while let Some(node) = pending.pop() {
10341        if is_cpp_type_specifier_node(node) {
10342            return Some(node);
10343        }
10344        if node.kind() == "ERROR" {
10345            let mut cursor = node.walk();
10346            pending.extend(
10347                node.named_children(&mut cursor)
10348                    .collect::<Vec<_>>()
10349                    .into_iter()
10350                    .rev(),
10351            );
10352        }
10353    }
10354    None
10355}
10356
10357fn is_cpp_type_specifier_node(node: Node<'_>) -> bool {
10358    matches!(
10359        node.kind(),
10360        "class_specifier"
10361            | "decltype"
10362            | "dependent_type"
10363            | "enum_specifier"
10364            | "placeholder_type_specifier"
10365            | "primitive_type"
10366            | "qualified_identifier"
10367            | "sized_type_specifier"
10368            | "struct_specifier"
10369            | "template_type"
10370            | "type_identifier"
10371            | "union_specifier"
10372    )
10373}
10374
10375fn resolve_static_method_call_return_binding(
10376    analyzer: &CppGraphSource<'_>,
10377    visibility: &VisibilityIndex<'_>,
10378    file: &ProjectFile,
10379    source: &str,
10380    function: Node<'_>,
10381    arity: usize,
10382) -> Option<CppScanBinding> {
10383    if function.kind() != "qualified_identifier" {
10384        return None;
10385    }
10386    let qualified = normalize_cpp_reference_text(node_text(function, source));
10387    // A C++ qualified-id is `::`-joined with no embedded delimiters in any
10388    // single component (the shared splitter's operator-token merge keeps
10389    // `operator+`-style names intact), so re-tokenizing with the shared
10390    // structured splitter and peeling the terminal segment reproduces
10391    // `rsplit_once("::")`'s (owner, member) split exactly — same shape as
10392    // `cpp_out_of_line_function_owner`'s `qualified` split above.
10393    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10394        brokk_bifrost_core::analyzer::Language::Cpp,
10395        &qualified,
10396    );
10397    let (owner_text, member_name) = match parts.split_last() {
10398        Some((member, owner_parts)) if !owner_parts.is_empty() => {
10399            (owner_parts.join("::"), member.clone())
10400        }
10401        _ => {
10402            let scope = function.child_by_field_name("scope")?;
10403            let name = function.child_by_field_name("name")?;
10404            (
10405                node_text(scope, source).to_string(),
10406                node_text(name, source).to_string(),
10407            )
10408        }
10409    };
10410    let owner = visibility.resolve_type(file, &owner_text)?;
10411    let candidates =
10412        callable_members_accepting_arity(analyzer, visibility, file, &owner, &member_name, arity);
10413    unanimous_return_binding(analyzer, visibility, file, &candidates)
10414}
10415
10416fn resolve_field_method_call_return_binding(
10417    analyzer: &CppGraphSource<'_>,
10418    visibility: &VisibilityIndex<'_>,
10419    file: &ProjectFile,
10420    source: &str,
10421    function: Node<'_>,
10422    arity: usize,
10423    receiver_resolver: Option<&ReceiverResolver<'_>>,
10424) -> Option<CppScanBinding> {
10425    debug_assert_eq!(
10426        function.kind(),
10427        "field_expression",
10428        "the member-call return binding answers only for a field-expression callee"
10429    );
10430    let receiver_resolver = receiver_resolver?;
10431    let field = function.child_by_field_name("field")?;
10432    let member_name = node_text(function_terminal_node(field), source);
10433    let receiver = function
10434        .child_by_field_name("argument")
10435        .or_else(|| function.named_child(0))?;
10436    let owners = receiver_resolver(receiver, source);
10437    let mut candidates = Vec::new();
10438    for owner in owners {
10439        let declaring_owner =
10440            match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
10441                EnclosingMemberOwnerResolution::Owner(owner) => owner,
10442                EnclosingMemberOwnerResolution::Missing => continue,
10443                EnclosingMemberOwnerResolution::Ambiguous => return None,
10444            };
10445        candidates.extend(callable_members_accepting_arity(
10446            analyzer,
10447            visibility,
10448            file,
10449            &declaring_owner,
10450            member_name,
10451            arity,
10452        ));
10453    }
10454    unanimous_return_binding(analyzer, visibility, file, &candidates)
10455}
10456
10457fn callable_members_accepting_arity(
10458    analyzer: &CppGraphSource<'_>,
10459    visibility: &VisibilityIndex<'_>,
10460    file: &ProjectFile,
10461    owner: &CodeUnit,
10462    member_name: &str,
10463    arity: usize,
10464) -> Vec<CodeUnit> {
10465    visibility
10466        .visible_members_for_owner_name(file, owner, member_name)
10467        .into_iter()
10468        .filter(|unit| {
10469            unit.is_function()
10470                && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(arity))
10471        })
10472        .cloned()
10473        .collect()
10474}
10475
10476fn unanimous_return_binding(
10477    analyzer: &CppGraphSource<'_>,
10478    visibility: &VisibilityIndex<'_>,
10479    file: &ProjectFile,
10480    candidates: &[CodeUnit],
10481) -> Option<CppScanBinding> {
10482    let mut resolved_return: Option<CppScanBinding> = None;
10483    for function in candidates {
10484        let metadata = analyzer.signature_metadata(function);
10485        let return_types = if metadata.is_empty() {
10486            vec![cpp_function_return_type_text(analyzer, function)?]
10487        } else {
10488            metadata
10489                .iter()
10490                .map(|metadata| metadata.return_type_text().map(str::to_string))
10491                .collect::<Option<Vec<_>>>()?
10492        };
10493        for return_text in return_types {
10494            let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
10495            let name = normalize_cpp_type_name(&return_text);
10496            let binding = CppScanBinding::from_type_name(
10497                name.clone(),
10498                visibility
10499                    .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
10500                indirection,
10501            );
10502            if let Some(existing) = resolved_return.as_ref()
10503                && (existing.indirection != binding.indirection
10504                    || match (&existing.unit, &binding.unit) {
10505                        (Some(left), Some(right)) => !same_visible_symbol(left, right),
10506                        (None, None) => existing.type_name != binding.type_name,
10507                        (Some(_), None) | (None, Some(_)) => true,
10508                    })
10509            {
10510                return None;
10511            }
10512            resolved_return = Some(binding);
10513        }
10514    }
10515    resolved_return
10516}
10517
10518fn aliases_from_canonical_source(
10519    cpp: &dyn CppSource,
10520    token: QueryToken<'_>,
10521    file: &ProjectFile,
10522) -> Option<Vec<CppAlias>> {
10523    let source = cpp.declaration_source_facts(token, file)?;
10524    Some(
10525        source
10526            .facts
10527            .declarations
10528            .iter()
10529            .filter_map(|fact| {
10530                let alias = fact.file_scope_alias.as_ref()?;
10531                Some(CppAlias {
10532                    name: alias.name.clone(),
10533                    target: alias.target.clone(),
10534                    namespace: alias.namespace.clone(),
10535                })
10536            })
10537            .collect(),
10538    )
10539}
10540
10541pub(crate) fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
10542    let mut current = node.parent();
10543    while let Some(parent) = current {
10544        match parent.kind() {
10545            "translation_unit"
10546            | "namespace_definition"
10547            | "declaration_list"
10548            | "linkage_specification" => current = parent.parent(),
10549            "template_declaration" => current = parent.parent(),
10550            _ => return false,
10551        }
10552    }
10553    true
10554}
10555
10556pub fn collect_include_closure(
10557    analyzer: &CppGraphSource<'_>,
10558    include_targets: &IncludeTargetIndex,
10559    file: &ProjectFile,
10560    out: &mut HashSet<ProjectFile>,
10561    cancellation: Option<&CancellationToken>,
10562) {
10563    let mut stack = vec![file.clone()];
10564    while let Some(file) = stack.pop() {
10565        if cancellation.is_some_and(CancellationToken::is_cancelled) {
10566            break;
10567        }
10568        if !out.insert(file.clone()) {
10569            continue;
10570        }
10571        for include in analyzer.canonical_include_paths(&file).unwrap_or_default() {
10572            for target in resolve_include_targets_with_index(&file, &include, include_targets) {
10573                stack.push(target);
10574            }
10575        }
10576    }
10577}
10578
10579fn collect_visible_declarations(
10580    include_graph: &IncludeGraph,
10581    declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
10582    file: &ProjectFile,
10583    visited: &mut HashSet<ProjectFile>,
10584    out: &mut HashSet<CodeUnit>,
10585    cancellation: Option<&CancellationToken>,
10586) {
10587    let mut stack = vec![file.clone()];
10588    while let Some(file) = stack.pop() {
10589        if cancellation.is_some_and(CancellationToken::is_cancelled) {
10590            break;
10591        }
10592        if !visited.insert(file.clone()) {
10593            continue;
10594        }
10595        if let Some(declarations) = declarations_by_file.get(&file) {
10596            out.extend(declarations.iter().cloned());
10597        }
10598        stack.extend(include_graph.targets(&file).iter().cloned());
10599    }
10600}
10601
10602fn parse_macro_parameter_list<T>(
10603    replacement: &str,
10604    read: impl FnOnce(Node<'_>, &str) -> T,
10605) -> Option<T> {
10606    let source = format!("void __bifrost_macro_parameters({replacement});");
10607    let mut parser = Parser::new();
10608    parser
10609        .set_language(&tree_sitter_cpp::LANGUAGE.into())
10610        .ok()?;
10611    let tree = parser.parse(&source, None)?;
10612    let root = tree.root_node();
10613    if root.has_error() {
10614        return None;
10615    }
10616    let declaration = root.named_child(0)?;
10617    let declarator = declaration.child_by_field_name("declarator")?;
10618    let parameters = declarator.child_by_field_name("parameters")?;
10619    Some(read(parameters, &source))
10620}
10621
10622fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
10623    parse_macro_parameter_list(replacement, crate::declarations::cpp_callable_arity)
10624}
10625
10626/// Interpret opaque macro replacement parameters with the primary producer's
10627/// canonical type spelling, preserving cv/ref qualifiers for overload hiding.
10628pub fn cpp_macro_parameter_types(replacement: &str) -> Option<Vec<String>> {
10629    parse_macro_parameter_list(
10630        replacement,
10631        crate::declarations::cpp_callable_parameter_types,
10632    )
10633}
10634
10635pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> Option<CallableArity> {
10636    brokk_bifrost_core::analyzer::model::first_callable_arity(analyzer.signature_metadata(unit))
10637}
10638
10639pub fn cpp_callable_parameter_types(
10640    analyzer: &CppGraphSource<'_>,
10641    unit: &CodeUnit,
10642) -> Option<Vec<String>> {
10643    brokk_bifrost_core::analyzer::model::first_callable_parameter_types(
10644        analyzer.signature_metadata(unit),
10645    )
10646}
10647
10648fn merge_compatible_callable_arities(
10649    left: CallableArity,
10650    right: CallableArity,
10651) -> Option<CallableArity> {
10652    let total = left.total();
10653    let left_repeated = left.accepts(total.saturating_add(1));
10654    let right_repeated = right.accepts(right.total().saturating_add(1));
10655    if total != right.total() || left_repeated != right_repeated {
10656        return None;
10657    }
10658    let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
10659    Some(CallableArity::new(required, total, left_repeated))
10660}
10661
10662fn collect_macro_events(
10663    cpp: &dyn CppSource,
10664    token: QueryToken<'_>,
10665    file: &ProjectFile,
10666) -> Vec<MacroEvent> {
10667    let Some(prepared) = cpp.prepared_syntax(token, file) else {
10668        return Vec::new();
10669    };
10670    let source = prepared.source();
10671    let mut events = Vec::new();
10672    let root = prepared.tree().root_node();
10673    let mut stack = vec![root];
10674    while let Some(node) = stack.pop() {
10675        match node.kind() {
10676            "preproc_def" | "preproc_function_def" => {
10677                let Some(name) = node.child_by_field_name("name") else {
10678                    continue;
10679                };
10680                let name = node_text(name, source).to_string();
10681                events.push(MacroEvent::Define {
10682                    name,
10683                    binding: MacroBinding {
10684                        source: file.clone(),
10685                        declaration_byte: node.start_byte(),
10686                        definition: VisibilityIndex::decode_macro_definition(node, source),
10687                        exact: true,
10688                    },
10689                    byte: node.start_byte(),
10690                    conditionals: owning_preprocessor_conditionals(root, node, source),
10691                });
10692                continue;
10693            }
10694            "preproc_include" => {
10695                let Some(path) = node.child_by_field_name("path") else {
10696                    events.push(MacroEvent::Include {
10697                        targets: Vec::new(),
10698                        byte: node.start_byte(),
10699                        conditionals: owning_preprocessor_conditionals(root, node, source),
10700                    });
10701                    continue;
10702                };
10703                let include = crate::imports::include_path_literal_value(path, source);
10704                let targets = include.map_or_else(Vec::new, |include| {
10705                    resolve_include_targets_with_index(file, include, cpp.include_target_index())
10706                });
10707                // An include no indexed file can answer -- its final path component names
10708                // no analyzable workspace file -- crosses into sources this index does not
10709                // hold, so it cannot hide indexed macro state and must not poison every
10710                // later local macro binding. The include's spelling does not decide that:
10711                // a quoted include falls back to the same search path an angle include
10712                // uses, and esphome's `core/log.h` reaches every component with
10713                // `#include "WString.h"`, an Arduino header absent from the tree (#3057).
10714                // A computed include, and a spelling some indexed file name does match
10715                // without resolving to one target, may still hide indexed macro state and
10716                // therefore fail closed.
10717                if targets.is_empty()
10718                    && include.is_some_and(|include| {
10719                        !cpp.include_target_index().names_indexed_file(include)
10720                    })
10721                {
10722                    continue;
10723                }
10724                events.push(MacroEvent::Include {
10725                    targets,
10726                    byte: node.start_byte(),
10727                    conditionals: owning_preprocessor_conditionals(root, node, source),
10728                });
10729                continue;
10730            }
10731            "preproc_call" => {
10732                let Some(directive) = node.child_by_field_name("directive") else {
10733                    continue;
10734                };
10735                if node_text(directive, source) != "#undef" {
10736                    continue;
10737                }
10738                let name = node
10739                    .child_by_field_name("argument")
10740                    .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
10741                if let Some(name) = name {
10742                    events.push(MacroEvent::Undef {
10743                        name,
10744                        byte: node.start_byte(),
10745                        conditionals: owning_preprocessor_conditionals(root, node, source),
10746                    });
10747                } else {
10748                    events.push(MacroEvent::Invalidate {
10749                        byte: node.start_byte(),
10750                    });
10751                }
10752                continue;
10753            }
10754            _ => {}
10755        }
10756        push_named_children_reversed(node, &mut stack);
10757    }
10758    events.sort_by_key(MacroEvent::byte);
10759    events
10760}
10761
10762fn find_include_activation(
10763    cpp: &dyn CppSource,
10764    token: QueryToken<'_>,
10765    file: &ProjectFile,
10766    prepared: &PreparedSyntaxTree,
10767    donor_source: &ProjectFile,
10768    language_guard_is_stable: &dyn Fn() -> bool,
10769) -> Option<usize> {
10770    let include_targets = cpp.include_target_index();
10771    let reference_is_c = file
10772        .rel_path()
10773        .extension()
10774        .and_then(|extension| extension.to_str())
10775        == Some("c");
10776    let mut direct_includes = Vec::new();
10777    let mut nodes = vec![prepared.tree().root_node()];
10778    // An include activates for the whole file, so only an unconditional
10779    // directive counts here.
10780    while let Some(node) = nodes.pop() {
10781        if node.kind() == "preproc_include" {
10782            let Some(required_guards) = include_directive_guard_requirements(
10783                node,
10784                prepared.source(),
10785                reference_is_c,
10786                language_guard_is_stable,
10787            ) else {
10788                continue;
10789            };
10790            if required_guards.all.is_empty() && required_guards.partial.is_empty() {
10791                for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
10792                    if let Some(target) = unique_include_target(resolve_include_targets_with_index(
10793                        file,
10794                        include,
10795                        include_targets,
10796                    )) {
10797                        direct_includes.push((node.end_byte(), target));
10798                    }
10799                }
10800            }
10801            continue;
10802        }
10803        push_named_children_reversed(node, &mut nodes);
10804    }
10805    direct_includes.sort_by_key(|(activation, _)| *activation);
10806    let mut known_missing = HashSet::default();
10807    direct_includes
10808        .into_iter()
10809        .find(|(_, direct)| {
10810            unconditional_include_reaches(
10811                cpp,
10812                token,
10813                direct,
10814                donor_source,
10815                file,
10816                &mut known_missing,
10817                language_guard_is_stable,
10818            )
10819        })
10820        .map(|(activation, _)| activation)
10821}
10822
10823fn find_conditional_include_projection_index(
10824    cpp: &dyn CppSource,
10825    token: QueryToken<'_>,
10826    file: &ProjectFile,
10827    prepared: &PreparedSyntaxTree,
10828    language_guard_is_stable: &dyn Fn() -> bool,
10829    on_state: &dyn Fn(),
10830) -> ConditionalIncludeProjectionIndex {
10831    let reference_is_c = reference_uses_c_semantics(cpp, file);
10832    let include_targets = cpp.include_target_index();
10833    let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
10834        HashMap::default();
10835    let mut pending = Vec::new();
10836    let mut nodes = vec![prepared.tree().root_node()];
10837    while let Some(node) = nodes.pop() {
10838        if node.kind() == "preproc_include" {
10839            let Some(required_guards) = include_directive_guard_requirements(
10840                node,
10841                prepared.source(),
10842                reference_is_c,
10843                language_guard_is_stable,
10844            ) else {
10845                continue;
10846            };
10847            for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
10848                let Some(target) = unique_include_target(resolve_include_targets_with_index(
10849                    file,
10850                    include,
10851                    include_targets,
10852                )) else {
10853                    continue;
10854                };
10855                pending.push((target, node.end_byte(), required_guards.clone()));
10856            }
10857            continue;
10858        }
10859        push_named_children_reversed(node, &mut nodes);
10860    }
10861
10862    // One reached file can have several distinct compatible guard paths. Each
10863    // (file, activation byte) key keeps only the inclusion-minimal guard sets:
10864    // the consumers ask existence questions whose answers are monotone in the
10865    // guard set -- a path whose requirements hold, stay stable, and stay
10866    // compatible under one environment does so under every subset as well --
10867    // so a state subsumed by an existing subset cannot witness anything its
10868    // subset does not, and inserting a smaller set evicts the supersets it
10869    // subsumes. Exact-set dedup still terminated cycles, but dense `#ifdef`
10870    // lattices (QMK's per-keyboard feature guards) enumerated the powerset of
10871    // path-union guard sets through it: the state space, the per-key linear
10872    // scans, and resident memory all grew without bound (#2365).
10873    let mut expanded: HashMap<(ProjectFile, usize), Vec<HashSet<PreprocessorGuard>>> =
10874        HashMap::default();
10875    while let Some((current_file, activation_byte, path)) = pending.pop() {
10876        let guard_sets = expanded
10877            .entry((current_file.clone(), activation_byte))
10878            .or_default();
10879        // Minimality is keyed on the full guard set alone: a path's undecided
10880        // guards are the per-conditional subset of that set, so a subset path
10881        // carries a subset of them too.
10882        if guard_sets
10883            .iter()
10884            .any(|existing| existing.is_subset(&path.all))
10885        {
10886            continue;
10887        }
10888        let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
10889            .drain(..)
10890            .partition(|existing| path.all.is_subset(existing));
10891        *guard_sets = kept;
10892        guard_sets.push(path.all.clone());
10893        if !evicted.is_empty()
10894            && let Some(projections) = projections_by_source.get_mut(&current_file)
10895        {
10896            projections.retain(|projection| {
10897                projection.activation_byte != activation_byte
10898                    || !evicted.contains(&projection.required_guards)
10899            });
10900        }
10901        on_state();
10902
10903        // A fresh minimal set has no equal in the store: equality would have
10904        // been caught by the subset check above.
10905        projections_by_source
10906            .entry(current_file.clone())
10907            .or_default()
10908            .push(ConditionalIncludeProjection {
10909                activation_byte,
10910                required_guards: path.all.clone(),
10911                partial_guards: path.partial.clone(),
10912            });
10913
10914        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
10915            continue;
10916        };
10917        let mut nodes = vec![current_prepared.tree().root_node()];
10918        while let Some(node) = nodes.pop() {
10919            if node.kind() == "preproc_include" {
10920                let Some(include_guards) = include_directive_guard_requirements(
10921                    node,
10922                    current_prepared.source(),
10923                    reference_is_c,
10924                    language_guard_is_stable,
10925                ) else {
10926                    continue;
10927                };
10928                let Some(reached) = path.merged(&include_guards) else {
10929                    continue;
10930                };
10931                for (include, _) in
10932                    include_path_from_node(node, current_prepared.source()).into_iter()
10933                {
10934                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
10935                        &current_file,
10936                        include,
10937                        include_targets,
10938                    )) else {
10939                        continue;
10940                    };
10941                    pending.push((target, activation_byte, reached.clone()));
10942                }
10943                continue;
10944            }
10945            push_named_children_reversed(node, &mut nodes);
10946        }
10947    }
10948
10949    projections_by_source
10950        .into_iter()
10951        .map(|(source, mut projections)| {
10952            projections.sort_by_key(|projection| projection.activation_byte);
10953            (source, Arc::from(projections))
10954        })
10955        .collect()
10956}
10957
10958/// Decide one conditional include target without materializing every source
10959/// reached by every guard combination. A path `admission` already rejects
10960/// cannot become admissible after adding nested include guards -- both rules
10961/// only ever add requirements -- so discard it before expanding the next
10962/// header.
10963#[allow(clippy::too_many_arguments)]
10964fn find_conditional_include_projection_for_source(
10965    cpp: &dyn CppSource,
10966    token: QueryToken<'_>,
10967    file: &ProjectFile,
10968    prepared: &PreparedSyntaxTree,
10969    donor_source: &ProjectFile,
10970    admission: IncludePathAdmission,
10971    reference_guards: Option<&HashSet<PreprocessorGuard>>,
10972    reference_byte: usize,
10973    language_guard_is_stable: &dyn Fn() -> bool,
10974    on_state: &dyn Fn(),
10975) -> bool {
10976    let Some(reference_guards) = reference_guards else {
10977        return false;
10978    };
10979    let reference_is_c = reference_uses_c_semantics(cpp, file);
10980    let include_targets = cpp.include_target_index();
10981    let mut pending = Vec::new();
10982    let mut nodes = vec![prepared.tree().root_node()];
10983    while let Some(node) = nodes.pop() {
10984        if node.kind() == "preproc_include" {
10985            let Some(required_guards) = include_directive_guard_requirements(
10986                node,
10987                prepared.source(),
10988                reference_is_c,
10989                language_guard_is_stable,
10990            ) else {
10991                continue;
10992            };
10993            if node.end_byte() > reference_byte
10994                || !admission.admits(
10995                    &required_guards.all,
10996                    &required_guards.partial,
10997                    Some(reference_guards),
10998                )
10999            {
11000                continue;
11001            }
11002            for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
11003                let Some(target) = unique_include_target(resolve_include_targets_with_index(
11004                    file,
11005                    include,
11006                    include_targets,
11007                )) else {
11008                    continue;
11009                };
11010                if &target == donor_source {
11011                    return true;
11012                }
11013                pending.push((target, required_guards.clone()));
11014            }
11015            continue;
11016        }
11017        push_named_children_reversed(node, &mut nodes);
11018    }
11019
11020    let mut expanded: HashMap<ProjectFile, Vec<HashSet<PreprocessorGuard>>> = HashMap::default();
11021    while let Some((current_file, path)) = pending.pop() {
11022        let guard_sets = expanded.entry(current_file.clone()).or_default();
11023        if guard_sets.contains(&path.all) {
11024            continue;
11025        }
11026        guard_sets.push(path.all.clone());
11027        on_state();
11028
11029        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
11030            continue;
11031        };
11032        let mut nodes = vec![current_prepared.tree().root_node()];
11033        while let Some(node) = nodes.pop() {
11034            if node.kind() == "preproc_include" {
11035                let Some(include_guards) = include_directive_guard_requirements(
11036                    node,
11037                    current_prepared.source(),
11038                    reference_is_c,
11039                    language_guard_is_stable,
11040                ) else {
11041                    continue;
11042                };
11043                let Some(reached) = path.merged(&include_guards) else {
11044                    continue;
11045                };
11046                if !admission.admits(&reached.all, &reached.partial, Some(reference_guards)) {
11047                    continue;
11048                }
11049                for (include, _) in
11050                    include_path_from_node(node, current_prepared.source()).into_iter()
11051                {
11052                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
11053                        &current_file,
11054                        include,
11055                        include_targets,
11056                    )) else {
11057                        continue;
11058                    };
11059                    if &target == donor_source {
11060                        return true;
11061                    }
11062                    pending.push((target, reached.clone()));
11063                }
11064                continue;
11065            }
11066            push_named_children_reversed(node, &mut nodes);
11067        }
11068    }
11069    false
11070}
11071
11072/// Whether `translation_unit`'s unconditional `#include` closure reaches
11073/// `header`, directly or through any chain of headers.
11074///
11075/// The include-closure question asked on its own, for
11076/// [`crate::identity::cpp_header_body_files_are_related`]. The walk resolves
11077/// each include the way visibility does -- to a unique target or to nothing --
11078/// so a duplicated basename relates nothing, and it is memoized per file pair
11079/// and language-proof state on the analyzer.
11080///
11081/// The reference position is `translation_unit` itself: the question is
11082/// whether that unit compiles the header, so that unit's own dialect and
11083/// preprocessor context govern the walk.
11084pub fn cpp_include_closure_reaches(
11085    cpp: &dyn CppSource,
11086    token: QueryToken<'_>,
11087    translation_unit: &ProjectFile,
11088    header: &ProjectFile,
11089) -> bool {
11090    let language_guard_stable = OnceCell::new();
11091    unconditional_include_reaches(
11092        cpp,
11093        token,
11094        translation_unit,
11095        header,
11096        translation_unit,
11097        &mut HashSet::default(),
11098        &|| {
11099            *language_guard_stable.get_or_init(|| {
11100                let mut macro_events =
11101                    |file: &ProjectFile| collect_macro_events(cpp, token, file).into_boxed_slice();
11102                include_language_guard_is_stable_with(
11103                    cpp,
11104                    token,
11105                    translation_unit,
11106                    &mut macro_events,
11107                    &mut |_: &ProjectFile, prepared: &PreparedSyntaxTree| {
11108                        collect_structured_include_facts(prepared)
11109                    },
11110                )
11111            })
11112        },
11113    )
11114}
11115
11116fn unconditional_include_reaches(
11117    cpp: &dyn CppSource,
11118    token: QueryToken<'_>,
11119    first: &ProjectFile,
11120    donor_source: &ProjectFile,
11121    reference_file: &ProjectFile,
11122    known_missing: &mut HashSet<ProjectFile>,
11123    language_guard_is_stable: &dyn Fn() -> bool,
11124) -> bool {
11125    if first == donor_source {
11126        return true;
11127    }
11128    if known_missing.contains(first) {
11129        return false;
11130    }
11131    let reference_is_c = reference_file
11132        .rel_path()
11133        .extension()
11134        .and_then(|extension| extension.to_str())
11135        == Some("c");
11136    if let Some(reaches) =
11137        cpp.cached_unconditional_include_reachability(first, donor_source, reference_file)
11138    {
11139        return reaches;
11140    }
11141    let mut visited = HashSet::default();
11142    let mut files = vec![first.clone()];
11143    let include_targets = cpp.include_target_index();
11144    while let Some(file) = files.pop() {
11145        if file == *donor_source {
11146            cpp.cache_unconditional_include_reachability(first, donor_source, reference_file, true);
11147            return true;
11148        }
11149        if known_missing.contains(&file) || !visited.insert(file.clone()) {
11150            continue;
11151        }
11152        let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11153            continue;
11154        };
11155        // One cursor for the whole-file walk; see `named_children_iter` (#3097).
11156        let mut cursor = prepared.tree().walk();
11157        let mut nodes = vec![prepared.tree().root_node()];
11158        while let Some(node) = nodes.pop() {
11159            if node.kind() == "preproc_include" {
11160                let Some(required_guards) = include_directive_guard_requirements(
11161                    node,
11162                    prepared.source(),
11163                    reference_is_c,
11164                    language_guard_is_stable,
11165                ) else {
11166                    continue;
11167                };
11168                if required_guards.all.is_empty() && required_guards.partial.is_empty() {
11169                    for (include, _) in include_path_from_node(node, prepared.source()).into_iter()
11170                    {
11171                        if let Some(target) = unique_include_target(
11172                            resolve_include_targets_with_index(&file, include, include_targets),
11173                        ) {
11174                            files.push(target);
11175                        }
11176                    }
11177                }
11178                continue;
11179            }
11180            let first_pushed = nodes.len();
11181            nodes.extend(node.named_children(&mut cursor));
11182            nodes[first_pushed..].reverse();
11183        }
11184    }
11185    known_missing.extend(visited);
11186    cpp.cache_unconditional_include_reachability(first, donor_source, reference_file, false);
11187    false
11188}
11189
11190fn include_language_guard_is_stable_with<MacroEvents, IncludeFacts>(
11191    cpp: &dyn CppSource,
11192    token: QueryToken<'_>,
11193    file: &ProjectFile,
11194    mut macro_events: MacroEvents,
11195    mut include_facts: IncludeFacts,
11196) -> bool
11197where
11198    MacroEvents: FnMut(&ProjectFile) -> Box<[MacroEvent]>,
11199    IncludeFacts: FnMut(&ProjectFile, &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]>,
11200{
11201    // A header has no intrinsic dialect. An opaque incoming include can hide
11202    // its compiler context from reverse include attribution. Only a
11203    // translation unit can supply the source-only language fact.
11204    if !crate::imports::is_cpp_translation_unit(file) {
11205        return false;
11206    }
11207    // When compiler data exists, keep its existing authoritative guard proof.
11208    // Source-only dialect facts cannot reconstruct -D/-U ordering.
11209    if !cpp.compile_contexts_for(file).is_empty()
11210        || cpp
11211            .reaching_translation_units(file)
11212            .iter()
11213            .any(|tu| !cpp.compile_contexts_for(tu).is_empty())
11214    {
11215        return false;
11216    }
11217    let mut pending = vec![file.clone()];
11218    let mut visited = HashSet::default();
11219    while let Some(file) = pending.pop() {
11220        if !visited.insert(file.clone()) {
11221            continue;
11222        }
11223        let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11224            return false;
11225        };
11226        if macro_events(&file).iter().any(|event| match event {
11227            MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
11228                name == "__cplusplus"
11229            }
11230            MacroEvent::Invalidate { .. } => true,
11231            MacroEvent::Include { targets, .. } => targets.is_empty(),
11232        }) {
11233            return false;
11234        }
11235        // Unlike indexed macro-binding replay, intrinsic-macro proof also
11236        // needs every include source: an opaque header may change it.
11237        for fact in include_facts(&file, prepared.as_ref()).iter() {
11238            let targets =
11239                resolve_include_targets_with_index(&file, &fact.path, cpp.include_target_index());
11240            if targets.is_empty() {
11241                return false;
11242            }
11243            pending.extend(targets);
11244        }
11245    }
11246    true
11247}
11248
11249fn declaration_guard_requirements(
11250    analyzer: &CppGraphSource<'_>,
11251    cpp: &dyn CppSource,
11252    candidate: &CodeUnit,
11253) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
11254    declaration_guard_sites(analyzer, cpp, candidate, None)
11255        .into_iter()
11256        .map(|site| (site.byte, site.guards))
11257        .collect()
11258}
11259
11260/// One declaration range's preprocessor facts, as a visible-at-a-reference
11261/// question reads them.
11262struct DeclarationGuardSite {
11263    /// The byte at which the range introduces the name.
11264    byte: usize,
11265    /// The preprocessor requirements the range's declaration stands under.
11266    guards: HashSet<PreprocessorGuard>,
11267    /// Whether the range is one branch of completed `#if`/`#else` families
11268    /// that all close before the reference. Always false without a reference
11269    /// position.
11270    completed_family_before_reference: bool,
11271}
11272
11273/// The per-range facts behind [`declaration_guard_requirements`], plus the
11274/// reference-relative fact the completed-family rule asks for.
11275fn declaration_guard_sites(
11276    analyzer: &CppGraphSource<'_>,
11277    cpp: &dyn CppSource,
11278    candidate: &CodeUnit,
11279    reference_byte: Option<usize>,
11280) -> Vec<DeclarationGuardSite> {
11281    let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
11282        return Vec::new();
11283    };
11284    let root = prepared.tree().root_node();
11285    analyzer
11286        .declaration_source_occurrences(candidate)
11287        .into_iter()
11288        .flatten()
11289        .filter_map(|occurrence| {
11290            let guards = cpp_guard_set_to_runtime(occurrence.fact.guard_requirements.as_ref()?)?;
11291            Some(DeclarationGuardSite {
11292                byte: occurrence.range.start_byte,
11293                guards,
11294                completed_family_before_reference: reference_byte.is_some_and(|reference_byte| {
11295                    root.descendant_for_byte_range(
11296                        occurrence.range.start_byte,
11297                        occurrence.range.end_byte,
11298                    )
11299                    .is_some_and(|node| {
11300                        declaration_follows_completed_family_before_reference(
11301                            node,
11302                            prepared.source(),
11303                            reference_byte,
11304                        )
11305                    })
11306                }),
11307            })
11308        })
11309        .collect()
11310}
11311
11312/// Whether every conditional enclosing `node` is a completed
11313/// `#if`/`#elif`/`#else` family that closes before `reference_byte`.
11314///
11315/// A completed family is an exhaustive case analysis: every configuration
11316/// takes exactly one of its branches. A reference sited below the whole family
11317/// is compiled in a configuration that either takes the declaration's branch,
11318/// in which case the declaration is what the name resolves to there, or takes
11319/// another branch, in which case the declaration is simply not part of that
11320/// configuration. Nothing in the reference's own position settles the family,
11321/// so the declaration is one resolution the source presents at the reference
11322/// rather than a contradiction of it (#3297).
11323///
11324/// A lone `#if` is not a case analysis: nothing in the source says any
11325/// configuration defines its macro, so a declaration under one stays
11326/// unprovable and the implication rule keeps it out. Neither does a family
11327/// whose end is not behind the reference: a reference inside the other branch
11328/// contradicts the declaration, and a reference inside the declaration's own
11329/// branch is already covered by implication.
11330fn declaration_follows_completed_family_before_reference(
11331    node: Node<'_>,
11332    source: &str,
11333    reference_byte: usize,
11334) -> bool {
11335    callable_guard_completion_byte(node, source).is_some_and(|end| end > 0 && end <= reference_byte)
11336}
11337
11338/// Producer-owned completion boundary for all enclosing conditional families.
11339/// Zero is the neutral boundary for a declaration outside those families.
11340pub(crate) fn callable_guard_completion_byte(node: Node<'_>, source: &str) -> Option<usize> {
11341    let mut cache = PreprocessorContextCache::default();
11342    callable_guard_completion_byte_with_cache(node, source, &mut cache)
11343}
11344
11345pub(crate) fn callable_guard_completion_byte_with_cache(
11346    node: Node<'_>,
11347    source: &str,
11348    cache: &mut PreprocessorContextCache,
11349) -> Option<usize> {
11350    let mut completion_byte = 0;
11351    let mut ancestor = node.parent();
11352    while let Some(conditional) = ancestor {
11353        if matches!(
11354            conditional.kind(),
11355            "preproc_if" | "preproc_ifdef" | "preproc_elif"
11356        ) && !is_file_covering_include_guard(conditional, source)
11357            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11358            && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
11359        {
11360            let family = preprocessor_conditional_family_root(conditional);
11361            if !preprocessor_conditional_family_has_terminal_else(family) {
11362                return None;
11363            }
11364            completion_byte = completion_byte.max(family.end_byte());
11365        }
11366        ancestor = conditional.parent();
11367    }
11368    Some(completion_byte)
11369}
11370
11371fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
11372    analyzer
11373        .ranges(candidate)
11374        .into_iter()
11375        .map(|range| range.start_byte)
11376        .min()
11377}
11378
11379/// The macro names every configuration in `contexts` defines -- the fact set
11380/// one file's compile-database coverage proves (#2011). `None` when the
11381/// database has no entry for the file, which is different from an empty
11382/// intersection: no entry means no coverage, while an empty intersection is
11383/// covered-and-proves-nothing.
11384fn context_fact_names(contexts: &[CppCompileContext]) -> Option<HashSet<String>> {
11385    let (first, rest) = contexts.split_first()?;
11386    Some(
11387        first
11388            .defined_macros
11389            .iter()
11390            .filter(|name| {
11391                rest.iter()
11392                    .all(|context| context.defined_macros.contains(*name))
11393            })
11394            .cloned()
11395            .collect(),
11396    )
11397}
11398
11399pub fn guard_requirements_hold_at_reference(
11400    required: &HashSet<PreprocessorGuard>,
11401    reference: Option<&HashSet<PreprocessorGuard>>,
11402) -> bool {
11403    reference.is_some_and(|active| {
11404        required
11405            .iter()
11406            .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
11407    })
11408}
11409
11410fn preprocessor_guard_holds_at_reference(
11411    required: &PreprocessorGuard,
11412    active: &HashSet<PreprocessorGuard>,
11413) -> bool {
11414    if active.contains(required) {
11415        return true;
11416    }
11417    let active_expression = BooleanGuardExpression::all(
11418        active
11419            .iter()
11420            .filter_map(PreprocessorGuard::as_boolean_expression),
11421    );
11422    required
11423        .as_boolean_expression()
11424        .is_some_and(|required| active_expression.implies(&required))
11425}
11426
11427/// Cross-file guard rule: two guard sets are compatible when neither one
11428/// contradicts the other. Use this instead of the subset test whenever the
11429/// guards come from a foreign file, which resolves its own conditionals
11430/// independently of the reference.
11431fn guards_compatible_at_reference(
11432    declaration: &HashSet<PreprocessorGuard>,
11433    reference: Option<&HashSet<PreprocessorGuard>>,
11434) -> bool {
11435    reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
11436}
11437
11438/// The byte range of the `#if`/`#elif`/`#else` chain that encloses the smallest
11439/// node covering `[start_byte, end_byte)`, or `None` when nothing there is
11440/// conditional.
11441///
11442/// Two declarations of one name that report the same chain stand in different
11443/// branches of it, so at most one of them is compiled in any configuration.
11444/// They are alternate spellings of a single declaration, not competing
11445/// declarations, and navigation must not present them as an ambiguity.
11446pub fn preprocessor_conditional_family_range(
11447    root: Node<'_>,
11448    start_byte: usize,
11449    end_byte: usize,
11450) -> Option<(usize, usize)> {
11451    let node = root.descendant_for_byte_range(start_byte, end_byte)?;
11452    preprocessor_conditional_family_node(node)
11453        .map(|family| (family.start_byte(), family.end_byte()))
11454}
11455
11456pub(crate) fn preprocessor_conditional_family_node(node: Node<'_>) -> Option<Node<'_>> {
11457    let mut ancestor = Some(node);
11458    while let Some(current) = ancestor {
11459        if is_cpp_preprocessor_conditional(current)
11460            && preprocessor_conditional_contains_descendant(current, node)
11461        {
11462            return Some(preprocessor_conditional_family_root(current));
11463        }
11464        ancestor = current.parent();
11465    }
11466    None
11467}
11468
11469pub(crate) fn preprocessor_conditional_family_for_declaration_with_cache<'tree>(
11470    node: Node<'tree>,
11471    cache: &mut PreprocessorContextCache,
11472) -> Option<Node<'tree>> {
11473    let mut ancestor = node.parent();
11474    while let Some(current) = ancestor {
11475        if is_cpp_preprocessor_conditional(current)
11476            && preprocessor_conditional_contains_descendant_with_cache(current, node, cache)
11477        {
11478            let family = preprocessor_conditional_family_root(current);
11479            if preprocessor_conditional_family_has_terminal_else(family) {
11480                return Some(family);
11481            }
11482        }
11483        ancestor = current.parent();
11484    }
11485    None
11486}
11487
11488fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
11489    while let Some(parent) = conditional.parent() {
11490        let is_alternative = parent
11491            .child_by_field_name("alternative")
11492            .is_some_and(|alternative| {
11493                alternative.start_byte() == conditional.start_byte()
11494                    && alternative.end_byte() == conditional.end_byte()
11495            });
11496        if !is_alternative {
11497            break;
11498        }
11499        conditional = parent;
11500    }
11501    conditional
11502}
11503
11504fn preprocessor_conditional_family_has_terminal_else(mut conditional: Node<'_>) -> bool {
11505    loop {
11506        let Some(alternative) = conditional.child_by_field_name("alternative") else {
11507            return false;
11508        };
11509        match alternative.kind() {
11510            "preproc_else" => return true,
11511            "preproc_elif" => conditional = alternative,
11512            _ => return false,
11513        }
11514    }
11515}
11516
11517/// The undecidable preprocessor guards an `#include` directive stands under,
11518/// or `None` when its conditional context keeps the directive out of a
11519/// translation unit of the reference's language: a false constant, a
11520/// condition this analyzer cannot read, or a `__cplusplus` branch that
11521/// language never takes.
11522///
11523/// This is [`callable_declaration_guard_requirements`]'s rule for the include
11524/// edge, and the language test is what keeps
11525/// [`IncludePathAdmission::Compatible`] honest: an empty active guard set
11526/// never implies `defined(__cplusplus)`, but it never contradicts it either,
11527/// so without this a `.c` file would reach the C++ arm of every
11528/// `#ifdef __cplusplus` / `#else` include pair.
11529fn include_directive_guard_requirements(
11530    node: Node<'_>,
11531    source: &str,
11532    reference_is_c: bool,
11533    language_guard_is_stable: &dyn Fn() -> bool,
11534) -> Option<PreprocessorGuardEnvironment> {
11535    let mut required = preprocessor_guard_environment_by_family(node, source)?;
11536    let excluded_by_language = required
11537        .all
11538        .iter()
11539        .any(|guard| preprocessor_guard_excludes_language(guard, reference_is_c));
11540    if excluded_by_language {
11541        return None;
11542    }
11543    let is_language_guard = |guard: &PreprocessorGuard| {
11544        matches!(guard, PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
11545            if name == "__cplusplus")
11546    };
11547    // The dialect decides this guard only when the admitted source closure
11548    // cannot mutate the builtin. Unknown include sources keep it undecided.
11549    if required.all.iter().any(is_language_guard) && language_guard_is_stable() {
11550        for guards in [&mut required.all, &mut required.partial] {
11551            guards.retain(|guard| !is_language_guard(guard));
11552        }
11553    }
11554    Some(required)
11555}
11556
11557fn preprocessor_guard_excludes_language(guard: &PreprocessorGuard, reference_is_c: bool) -> bool {
11558    match guard {
11559        PreprocessorGuard::Defined(name) => reference_is_c && name == "__cplusplus",
11560        PreprocessorGuard::Undefined(name) => !reference_is_c && name == "__cplusplus",
11561        _ => false,
11562    }
11563}
11564
11565pub fn preprocessor_guard_environment(
11566    node: Node<'_>,
11567    source: &str,
11568) -> Option<HashSet<PreprocessorGuard>> {
11569    let mut cache = PreprocessorContextCache::default();
11570    preprocessor_guard_environment_with_cache(node, source, &mut cache)
11571}
11572
11573pub(crate) fn preprocessor_guard_environment_with_cache(
11574    node: Node<'_>,
11575    source: &str,
11576    cache: &mut PreprocessorContextCache,
11577) -> Option<HashSet<PreprocessorGuard>> {
11578    preprocessor_guard_environment_by_family_with_cache(node, source, cache)
11579        .map(|environment| environment.all)
11580}
11581
11582/// The preprocessor guards `node` stands under, split by whether the
11583/// conditional that contributes each one belongs to a family that covers every
11584/// configuration.
11585///
11586/// A complete `#if`/`#else` or `#if`/`#elif`/`#else` family is a case analysis
11587/// every configuration takes exactly one branch of, which is why the resolver
11588/// already reads a declaration through one (`exhaustive_guard_family_activation`,
11589/// `complementary_same_fqn_type_declarations`). A lone `#if` is not: nothing in
11590/// the source says any configuration defines that macro, so only the build can
11591/// decide it, and it stays in `partial`.
11592#[derive(Clone)]
11593struct PreprocessorGuardEnvironment {
11594    all: HashSet<PreprocessorGuard>,
11595    partial: HashSet<PreprocessorGuard>,
11596}
11597
11598impl PreprocessorGuardEnvironment {
11599    /// The environment of a path standing under both `self` and `other`, or
11600    /// `None` when the two contradict. A guard a lone `#if` contributes on
11601    /// either side stays undecided for the whole path.
11602    fn merged(&self, other: &Self) -> Option<Self> {
11603        Some(Self {
11604            all: merge_preprocessor_guards(&self.all, &other.all)?,
11605            partial: self.partial.union(&other.partial).cloned().collect(),
11606        })
11607    }
11608}
11609
11610fn preprocessor_guard_environment_by_family(
11611    node: Node<'_>,
11612    source: &str,
11613) -> Option<PreprocessorGuardEnvironment> {
11614    let mut cache = PreprocessorContextCache::default();
11615    preprocessor_guard_environment_by_family_with_cache(node, source, &mut cache)
11616}
11617
11618fn preprocessor_guard_environment_by_family_with_cache(
11619    node: Node<'_>,
11620    source: &str,
11621    cache: &mut PreprocessorContextCache,
11622) -> Option<PreprocessorGuardEnvironment> {
11623    let mut all = HashSet::default();
11624    let mut partial = HashSet::default();
11625    let mut ancestor = node.parent();
11626    while let Some(conditional) = ancestor {
11627        if matches!(
11628            conditional.kind(),
11629            "preproc_if" | "preproc_ifdef" | "preproc_elif"
11630        ) && !is_file_covering_include_guard(conditional, source)
11631            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11632            && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
11633        {
11634            let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
11635            match guard {
11636                PreprocessorGuard::Constant(true) => {
11637                    ancestor = conditional.parent();
11638                    continue;
11639                }
11640                PreprocessorGuard::Constant(false) => return None,
11641                _ => {}
11642            }
11643            if all.contains(&guard.negated()) {
11644                return None;
11645            }
11646            if !preprocessor_conditional_family_has_terminal_else(
11647                preprocessor_conditional_family_root(conditional),
11648            ) {
11649                partial.insert(guard.clone());
11650            }
11651            all.insert(guard);
11652        }
11653        ancestor = conditional.parent();
11654    }
11655    if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
11656        match guard {
11657            PreprocessorGuard::Constant(true) => {}
11658            PreprocessorGuard::Constant(false) => return None,
11659            _ => {
11660                if all.contains(&guard.negated()) {
11661                    return None;
11662                }
11663                // The family of a conditional the parser split across a
11664                // statement boundary is not readable from the tree, so it
11665                // counts as undecided.
11666                partial.insert(guard.clone());
11667                all.insert(guard);
11668            }
11669        }
11670    }
11671    Some(PreprocessorGuardEnvironment { all, partial })
11672}
11673
11674pub(crate) fn callable_preprocessor_guard_requirements_with_cache(
11675    node: Node<'_>,
11676    source: &str,
11677    cache: &mut PreprocessorContextCache,
11678) -> Option<HashSet<PreprocessorGuard>> {
11679    let mut guards = HashSet::default();
11680    for guard in callable_preprocessor_ancestor_guards_with_cache(node, source, cache)? {
11681        match guard {
11682            PreprocessorGuard::Constant(true) => {}
11683            PreprocessorGuard::Constant(false) => return None,
11684            _ => {
11685                if guards.contains(&guard.negated()) {
11686                    return None;
11687                }
11688                guards.insert(guard);
11689            }
11690        }
11691    }
11692    Some(guards)
11693}
11694
11695fn callable_preprocessor_ancestor_guards(
11696    node: Node<'_>,
11697    source: &str,
11698) -> Option<Vec<PreprocessorGuard>> {
11699    let mut cache = PreprocessorContextCache::default();
11700    callable_preprocessor_ancestor_guards_with_cache(node, source, &mut cache)
11701}
11702
11703fn callable_preprocessor_ancestor_guards_with_cache(
11704    node: Node<'_>,
11705    source: &str,
11706    cache: &mut PreprocessorContextCache,
11707) -> Option<Vec<PreprocessorGuard>> {
11708    let mut guards = Vec::new();
11709    let mut ancestor = node.parent();
11710    while let Some(conditional) = ancestor {
11711        if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
11712            && !is_file_covering_include_guard(conditional, source)
11713            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11714            && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
11715        {
11716            guards.push(preprocessor_guard_for_descendant(
11717                conditional,
11718                node,
11719                source,
11720            )?);
11721        }
11722        ancestor = conditional.parent();
11723    }
11724    Some(guards)
11725}
11726
11727fn fragmented_statement_preprocessor_guard(
11728    descendant: Node<'_>,
11729    source: &str,
11730) -> Option<PreprocessorGuard> {
11731    // A conditional that starts before `} else if (...) {` crosses the
11732    // enclosing statement's grammar boundary. tree-sitter leaves its opener
11733    // as a `preproc_if` with a missing terminator in the consequence and
11734    // reparses the real `#endif` as a `preproc_call` in the alternative. Pair
11735    // those structured nodes before restoring the guard to intervening uses.
11736    let mut ancestor = descendant.parent();
11737    while let Some(statement) = ancestor {
11738        if statement.kind() == "if_statement"
11739            && let (Some(consequence), Some(alternative)) = (
11740                statement.child_by_field_name("consequence"),
11741                statement.child_by_field_name("alternative"),
11742            )
11743            && alternative.start_byte() <= descendant.start_byte()
11744            && descendant.end_byte() <= alternative.end_byte()
11745        {
11746            let mut cursor = consequence.walk();
11747            let openers = consequence
11748                .named_children(&mut cursor)
11749                .filter(|child| {
11750                    matches!(child.kind(), "preproc_if" | "preproc_ifdef")
11751                        && child
11752                            .child(child.child_count().saturating_sub(1))
11753                            .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
11754                })
11755                .collect::<Vec<_>>();
11756            if openers.len() != 1 {
11757                ancestor = statement.parent();
11758                continue;
11759            }
11760
11761            let mut terminators = Vec::new();
11762            let mut stack = vec![alternative];
11763            while let Some(node) = stack.pop() {
11764                if node.kind() == "preproc_call"
11765                    && node.start_byte() >= descendant.end_byte()
11766                    && node
11767                        .child_by_field_name("directive")
11768                        .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
11769                {
11770                    terminators.push(node);
11771                    continue;
11772                }
11773                push_named_children_reversed(node, &mut stack);
11774            }
11775            if terminators.len() == 1 {
11776                return simple_preprocessor_guard(openers[0], source);
11777            }
11778        }
11779        ancestor = statement.parent();
11780    }
11781    None
11782}
11783
11784pub(crate) fn preprocessor_guard_for_descendant(
11785    conditional: Node<'_>,
11786    descendant: Node<'_>,
11787    source: &str,
11788) -> Option<PreprocessorGuard> {
11789    let mut guard = simple_preprocessor_guard(conditional, source)?;
11790    if conditional
11791        .child_by_field_name("alternative")
11792        .is_some_and(|alternative| {
11793            alternative.start_byte() <= descendant.start_byte()
11794                && descendant.end_byte() <= alternative.end_byte()
11795        })
11796    {
11797        let alternative = conditional.child_by_field_name("alternative")?;
11798        // Tree-sitter nests an `#elif` chain in each `alternative` field. A
11799        // descendant in any later branch must first exclude the parent branch,
11800        // then collect the nested `preproc_elif` guard from its own ancestor.
11801        if !matches!(alternative.kind(), "preproc_else" | "preproc_elif") {
11802            return None;
11803        }
11804        guard = guard.negated();
11805    }
11806    Some(guard)
11807}
11808
11809pub(crate) fn preprocessor_conditional_contains_descendant(
11810    conditional: Node<'_>,
11811    descendant: Node<'_>,
11812) -> bool {
11813    cpp_displaced_preprocessor_boundary(conditional)
11814        .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
11815}
11816
11817fn preprocessor_conditional_contains_descendant_with_cache(
11818    conditional: Node<'_>,
11819    descendant: Node<'_>,
11820    cache: &mut PreprocessorContextCache,
11821) -> bool {
11822    cache
11823        .displaced_boundary_end(conditional)
11824        .is_none_or(|end_byte| descendant.end_byte() <= end_byte)
11825}
11826
11827pub fn merge_preprocessor_guards(
11828    left: &HashSet<PreprocessorGuard>,
11829    right: &HashSet<PreprocessorGuard>,
11830) -> Option<HashSet<PreprocessorGuard>> {
11831    let mut merged = left.clone();
11832    for guard in right {
11833        let boolean_negation = guard
11834            .as_boolean_expression()
11835            .map(|expression| expression.negated());
11836        if merged.contains(&guard.negated())
11837            || boolean_negation.is_some_and(|negated| {
11838                merged
11839                    .iter()
11840                    .filter_map(PreprocessorGuard::as_boolean_expression)
11841                    .any(|existing| existing == negated)
11842            })
11843        {
11844            return None;
11845        }
11846        merged.insert(guard.clone());
11847    }
11848    Some(merged)
11849}
11850
11851fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
11852    if conditional.kind() == "preproc_ifdef" {
11853        let name = conditional.child_by_field_name("name")?;
11854        let name = node_text(name, source).to_string();
11855        return match conditional.child(0)?.kind() {
11856            "#ifdef" => Some(PreprocessorGuard::Defined(name)),
11857            "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
11858            _ => None,
11859        };
11860    }
11861    let condition = conditional.child_by_field_name("condition")?;
11862    simple_preprocessor_expression_guard(condition, source).or_else(|| {
11863        Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
11864            node_text(condition, source),
11865        )))
11866    })
11867}
11868
11869fn simple_preprocessor_expression_guard(
11870    expression: Node<'_>,
11871    source: &str,
11872) -> Option<PreprocessorGuard> {
11873    match expression.kind() {
11874        "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
11875            node_text(expression, source).to_string(),
11876        ))),
11877        "number_literal" => match node_text(expression, source).trim() {
11878            "0" => Some(PreprocessorGuard::Constant(false)),
11879            "1" => Some(PreprocessorGuard::Constant(true)),
11880            _ => None,
11881        },
11882        "preproc_defined" => {
11883            let identifier = (0..expression.named_child_count())
11884                .filter_map(|index| expression.named_child(index))
11885                .find(|child| child.kind() == "identifier")?;
11886            Some(PreprocessorGuard::Defined(
11887                node_text(identifier, source).to_string(),
11888            ))
11889        }
11890        "unary_expression"
11891            if expression
11892                .child_by_field_name("operator")
11893                .is_some_and(|operator| operator.kind() == "!") =>
11894        {
11895            simple_preprocessor_expression_guard(
11896                expression.child_by_field_name("argument")?,
11897                source,
11898            )
11899            .map(|guard| guard.negated())
11900        }
11901        "parenthesized_expression" => (0..expression.named_child_count())
11902            .filter_map(|index| expression.named_child(index))
11903            .next()
11904            .and_then(|child| simple_preprocessor_expression_guard(child, source)),
11905        "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
11906            expression, source,
11907        ))),
11908        _ => None,
11909    }
11910}
11911
11912fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
11913    match expression.kind() {
11914        "number_literal" => match node_text(expression, source).trim() {
11915            "0" => BooleanGuardExpression::Constant(false),
11916            "1" => BooleanGuardExpression::Constant(true),
11917            _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11918                expression, source,
11919            ))),
11920        },
11921        "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
11922        "preproc_defined" => {
11923            let identifier = (0..expression.named_child_count())
11924                .filter_map(|index| expression.named_child(index))
11925                .find(|child| child.kind() == "identifier");
11926            identifier.map_or_else(
11927                || {
11928                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11929                        expression, source,
11930                    )))
11931                },
11932                |identifier| {
11933                    BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
11934                },
11935            )
11936        }
11937        "unary_expression"
11938            if expression
11939                .child_by_field_name("operator")
11940                .is_some_and(|operator| operator.kind() == "!") =>
11941        {
11942            expression.child_by_field_name("argument").map_or_else(
11943                || {
11944                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11945                        expression, source,
11946                    )))
11947                },
11948                |argument| boolean_preprocessor_expression(argument, source).negated(),
11949            )
11950        }
11951        "parenthesized_expression" => (0..expression.named_child_count())
11952            .filter_map(|index| expression.named_child(index))
11953            .next()
11954            .map_or_else(
11955                || {
11956                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11957                        expression, source,
11958                    )))
11959                },
11960                |child| boolean_preprocessor_expression(child, source),
11961            ),
11962        "binary_expression" => {
11963            let operands = || {
11964                Some((
11965                    boolean_preprocessor_expression(
11966                        expression.child_by_field_name("left")?,
11967                        source,
11968                    ),
11969                    boolean_preprocessor_expression(
11970                        expression.child_by_field_name("right")?,
11971                        source,
11972                    ),
11973                ))
11974            };
11975            match expression
11976                .child_by_field_name("operator")
11977                .map(|operator| operator.kind())
11978            {
11979                Some("&&") => operands().map_or_else(
11980                    || {
11981                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11982                            expression, source,
11983                        )))
11984                    },
11985                    |(left, right)| BooleanGuardExpression::all([left, right]),
11986                ),
11987                Some("||") => operands().map_or_else(
11988                    || {
11989                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11990                            expression, source,
11991                        )))
11992                    },
11993                    |(left, right)| BooleanGuardExpression::any([left, right]),
11994                ),
11995                _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11996                    expression, source,
11997                ))),
11998            }
11999        }
12000        _ => {
12001            BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
12002        }
12003    }
12004}
12005
12006fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
12007    if targets.len() == 1 {
12008        targets.pop()
12009    } else {
12010        None
12011    }
12012}
12013
12014fn callable_declaration_nodes<'tree>(
12015    analyzer: &CppGraphSource<'_>,
12016    prepared: &'tree PreparedSyntaxTree,
12017    candidate: &CodeUnit,
12018) -> Vec<Node<'tree>> {
12019    let root = prepared.tree().root_node();
12020    analyzer
12021        .ranges(candidate)
12022        .into_iter()
12023        .filter_map(|range| {
12024            let mut declaration =
12025                root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
12026            // A declaration an attribute-like macro invocation swallowed lives
12027            // inside the `ERROR` the parser left, not inside a `declaration`
12028            // node, so that envelope is where the climb stops (#2552).
12029            //
12030            // The climb can also run out of ancestors. A collapse that takes a
12031            // whole file with it -- whisper.cpp's `include/whisper.h` from its
12032            // first `WHISPER_DEPRECATED(` to the end (#3094) -- leaves no
12033            // envelope around most of what a region reparse recovers there, and
12034            // the recovered declaration's own `;` can fall outside the envelope
12035            // it does leave, so the range spans the container rather than one
12036            // node. Either way the declaration stands at the scope the climb
12037            // ended in, which is what the callers ask these nodes about.
12038            while !matches!(
12039                declaration.kind(),
12040                "declaration" | "field_declaration" | "function_definition"
12041            ) && !crate::declarations::is_macro_wrapped_declaration_envelope(
12042                declaration,
12043                prepared.source(),
12044            ) {
12045                let Some(parent) = declaration.parent() else {
12046                    break;
12047                };
12048                declaration = parent;
12049            }
12050            Some(declaration)
12051        })
12052        .collect()
12053}
12054
12055fn real_function_definition_ancestor<'tree>(
12056    node: Node<'tree>,
12057    source: &str,
12058) -> Option<Node<'tree>> {
12059    let mut ancestor = node.parent();
12060    while let Some(node) = ancestor {
12061        if node.kind() == "function_definition"
12062            && !is_recovered_declaration_scope_container(node, source)
12063        {
12064            return Some(node);
12065        }
12066        ancestor = node.parent();
12067    }
12068    None
12069}
12070
12071fn callable_declaration_activation_in_file(
12072    analyzer: &CppGraphSource<'_>,
12073    candidate: &CodeUnit,
12074    reference: &CallableReferenceContext<'_>,
12075) -> Option<usize> {
12076    analyzer
12077        .declaration_source_occurrences(candidate)?
12078        .into_iter()
12079        .filter_map(|occurrence| {
12080            let activation = occurrence.fact.callable_activation?;
12081            let guards = cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
12082            callable_guard_requirements_are_visible(
12083                &guards,
12084                occurrence.fact.callable_guard_completion_byte,
12085                reference,
12086            )
12087            .then_some(activation)
12088        })
12089        .min()
12090}
12091
12092fn callable_guard_requirements_are_visible(
12093    required: &HashSet<PreprocessorGuard>,
12094    completion_byte: Option<usize>,
12095    reference: &CallableReferenceContext<'_>,
12096) -> bool {
12097    if required.iter().all(|guard| match guard {
12098        PreprocessorGuard::Defined(name) if name == "__cplusplus" => !reference.is_c(),
12099        PreprocessorGuard::Undefined(name) if name == "__cplusplus" => reference.is_c(),
12100        guard => reference
12101            .guards()
12102            .is_some_and(|active| preprocessor_guard_holds_at_reference(guard, active)),
12103    }) {
12104        return true;
12105    }
12106    if required.iter().any(|guard| match guard {
12107        PreprocessorGuard::Defined(name) if name == "__cplusplus" => reference.is_c(),
12108        PreprocessorGuard::Undefined(name) if name == "__cplusplus" => !reference.is_c(),
12109        _ => false,
12110    }) {
12111        return false;
12112    }
12113    let undecided: HashSet<_> = required
12114        .iter()
12115        .filter(|guard| {
12116            !matches!(guard, PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
12117            if name == "__cplusplus")
12118        })
12119        .cloned()
12120        .collect();
12121    reference.position.as_ref().is_some_and(|position| {
12122        completion_byte.is_some_and(|end| end > 0 && end <= position.byte)
12123            && guards_compatible_at_reference(&undecided, reference.guards())
12124    })
12125}
12126
12127/// The reference side of a callable visibility question.
12128///
12129/// An include-graph walk and a whole-file arity activation ask the question
12130/// without one reference position, so they carry no `position` and therefore no
12131/// guard environment.
12132struct CallableReferenceContext<'a> {
12133    file: &'a ProjectFile,
12134    position: Option<CallableReferencePosition<'a>>,
12135}
12136
12137/// One reference position plus its preprocessor guard environment. The
12138/// environment is computed on demand because most declarations carry no
12139/// non-trivial guard.
12140struct CallableReferencePosition<'a> {
12141    prepared: &'a PreparedSyntaxTree,
12142    byte: usize,
12143    guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
12144}
12145
12146impl CallableReferenceContext<'_> {
12147    fn is_c(&self) -> bool {
12148        self.file
12149            .rel_path()
12150            .extension()
12151            .and_then(|extension| extension.to_str())
12152            == Some("c")
12153    }
12154
12155    fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
12156        let position = self.position.as_ref()?;
12157        position
12158            .guards
12159            .get_or_init(|| {
12160                position
12161                    .prepared
12162                    .tree()
12163                    .root_node()
12164                    .descendant_for_byte_range(position.byte, position.byte.saturating_add(1))
12165                    .and_then(|node| {
12166                        preprocessor_guard_environment(node, position.prepared.source())
12167                    })
12168            })
12169            .as_ref()
12170    }
12171}
12172
12173/// The undecidable preprocessor guards `node` stands under, or `None` when its
12174/// conditional context excludes it from `reference`'s translation unit outright:
12175/// a false constant, a condition this analyzer cannot read, or a `__cplusplus`
12176/// branch the reference's language never takes.
12177///
12178/// Collecting one guard per enclosing conditional makes the whole walk a
12179/// conjunction of the declaration's requirements. Which comparison those
12180/// requirements then face depends on where the declaration lives: a same-file
12181/// declaration shares the reference's configuration, so the reference's active
12182/// guards must imply them, while a foreign file resolves its own conditionals
12183/// and only has to stay compatible with the reference.
12184fn callable_declaration_guard_requirements(
12185    node: Node<'_>,
12186    source: &str,
12187    reference: &CallableReferenceContext<'_>,
12188) -> Option<HashSet<PreprocessorGuard>> {
12189    let reference_is_c = reference.is_c();
12190    let mut required = HashSet::default();
12191    for guard in callable_preprocessor_ancestor_guards(node, source)? {
12192        match guard {
12193            PreprocessorGuard::Constant(true) => {}
12194            PreprocessorGuard::Constant(false) => return None,
12195            guard if preprocessor_guard_excludes_language(&guard, reference_is_c) => {
12196                return None;
12197            }
12198            guard => {
12199                required.insert(guard);
12200            }
12201        }
12202    }
12203    Some(required)
12204}
12205
12206/// Whether a declaration in the reference's own file is co-active with the
12207/// reference: one translation unit resolves every conditional the same way, so
12208/// the reference's active guards must imply the declaration's requirements.
12209///
12210/// The exception is a declaration in one branch of a completed `#if`/`#else`
12211/// family the reference follows: the reference's own position settles nothing
12212/// about that family, so a compatible branch is one resolution the source
12213/// presents at the reference (#3297), exactly as
12214/// [`VisibilityIndex::external_type_candidate_visible_in_context`] reads a type
12215/// declaration there.
12216fn callable_preprocessor_context_is_visible_for_reference(
12217    node: Node<'_>,
12218    source: &str,
12219    reference: &CallableReferenceContext<'_>,
12220) -> bool {
12221    let Some(required) = callable_declaration_guard_requirements(node, source, reference) else {
12222        return false;
12223    };
12224    if required.is_empty() || guard_requirements_hold_at_reference(&required, reference.guards()) {
12225        return true;
12226    }
12227    reference.position.as_ref().is_some_and(|position| {
12228        guards_compatible_at_reference(&required, reference.guards())
12229            && declaration_follows_completed_family_before_reference(node, source, position.byte)
12230    })
12231}
12232
12233fn flattened_macro_namespace_declaration_matches(
12234    analyzer: &CppGraphSource<'_>,
12235    _cpp: &dyn CppSource,
12236    reference_file: &ProjectFile,
12237    visible_declaration: &CodeUnit,
12238    qualified_candidate: &CodeUnit,
12239    reference_byte: usize,
12240) -> bool {
12241    // Namespace-opening macros can leave tree-sitter unable to retain the
12242    // namespace owner after a later recovery point. In that shape the forward
12243    // declaration is indexed at translation-unit scope, while the definition
12244    // still has its qualified owner. Require all surviving structural evidence
12245    // before treating the declaration as activation for that definition.
12246    if visible_declaration.kind() != qualified_candidate.kind()
12247        || visible_declaration.identifier() != qualified_candidate.identifier()
12248        || visible_declaration.signature() != qualified_candidate.signature()
12249        || !visible_declaration.package_name().is_empty()
12250        || qualified_candidate.package_name().is_empty()
12251    {
12252        return false;
12253    }
12254
12255    let Some(occurrences) = analyzer.declaration_source_occurrences(visible_declaration) else {
12256        return false;
12257    };
12258    occurrences.into_iter().any(|occurrence| {
12259        occurrence
12260            .displaced_namespace_closing_brace
12261            .is_some_and(|closing_brace| {
12262                visible_declaration.source() != reference_file || closing_brace < reference_byte
12263            })
12264    })
12265}
12266
12267pub(crate) fn flattened_macro_namespace_components(
12268    declaration: Node<'_>,
12269    source: &str,
12270) -> Option<Vec<String>> {
12271    flattened_macro_function_namespace_components(declaration, source)
12272        .or_else(|| flattened_macro_error_namespace_components(declaration, source))
12273}
12274
12275fn flattened_macro_function_namespace_components(
12276    declaration: Node<'_>,
12277    source: &str,
12278) -> Option<Vec<String>> {
12279    let body = declaration
12280        .parent()
12281        .filter(|parent| parent.kind() == "compound_statement")?;
12282    let function = body.parent()?;
12283    if function.child_by_field_name("body") != Some(body) {
12284        return None;
12285    }
12286    let namespace_name = recovered_macro_namespace_name(function, source)?;
12287    let mut components = enclosing_namespace_components(declaration, source)?;
12288    components.push(namespace_name);
12289    Some(components)
12290}
12291
12292/// The namespace name a namespace-opening macro token displaced into a
12293/// synthetic `function_definition`, or `None` when `function` is not that
12294/// recovery shape.
12295///
12296/// `ABSL_NAMESPACE_BEGIN` (or `FMT_BEGIN_NAMESPACE`, ...) immediately before
12297/// `namespace x {` leaves tree-sitter with a `function_definition` whose type is
12298/// the macro token, whose declarator is the namespace name behind an `ERROR`
12299/// holding the `namespace` keyword, and whose body spans the whole namespace
12300/// region. The matching `*_NAMESPACE_END` sibling is what separates the recovery
12301/// artifact from a real function definition.
12302fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
12303    if function.kind() != "function_definition" || !function.has_error() {
12304        return None;
12305    }
12306    let body = function
12307        .child_by_field_name("body")
12308        .filter(|body| body.kind() == "compound_statement")?;
12309    let mut cursor = function.walk();
12310    let prefix = function
12311        .named_children(&mut cursor)
12312        .take_while(|child| child.start_byte() < body.start_byte())
12313        .filter(|child| child.kind() != "comment")
12314        .collect::<Vec<_>>();
12315    let begin_index = prefix.iter().rposition(|child| {
12316        flattened_macro_sentinel_name(*child, source)
12317            .is_some_and(|name| is_namespace_begin_sentinel(&name))
12318    })?;
12319    let mut identifiers = Vec::new();
12320    let mut stack = prefix[begin_index + 1..]
12321        .iter()
12322        .rev()
12323        .copied()
12324        .collect::<Vec<_>>();
12325    while let Some(current) = stack.pop() {
12326        if let Some(identifier) = direct_cpp_identifier_name(current, source) {
12327            identifiers.push(identifier);
12328            continue;
12329        }
12330        let mut cursor = current.walk();
12331        let children = current.named_children(&mut cursor).collect::<Vec<_>>();
12332        stack.extend(children.into_iter().rev());
12333    }
12334    let [keyword, namespace_name] = identifiers.as_slice() else {
12335        return None;
12336    };
12337    if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
12338    {
12339        return None;
12340    }
12341    let mut next = function.next_named_sibling();
12342    let next = loop {
12343        let candidate = next?;
12344        next = candidate.next_named_sibling();
12345        if candidate.kind() != "comment" {
12346            break candidate;
12347        }
12348    };
12349    flattened_macro_sentinel_name(next, source)
12350        .is_some_and(|name| is_namespace_end_sentinel(&name))
12351        .then(|| namespace_name.clone())
12352}
12353
12354/// A `function_definition` that exists only because tree-sitter recovered a
12355/// macro-decorated class head or a namespace-opening macro token. A declaration
12356/// in such a body keeps class or namespace scope, so a scope walk must step over
12357/// the wrapper instead of treating the declaration as block local.
12358pub(crate) fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
12359    crate::declarations::is_recovered_exported_class_container(node, source)
12360        || crate::declarations::is_recovered_fragmented_partial_specialization_container(
12361            node, source,
12362        )
12363        || recovered_macro_namespace_name(node, source).is_some()
12364}
12365
12366fn flattened_macro_error_namespace_components(
12367    declaration: Node<'_>,
12368    source: &str,
12369) -> Option<Vec<String>> {
12370    let parent = declaration
12371        .parent()
12372        .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
12373    let mut cursor = parent.walk();
12374    let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
12375    let declaration_index = siblings
12376        .iter()
12377        .position(|candidate| same_node(*candidate, declaration))?;
12378    let begin_index = (0..declaration_index).rev().find(|index| {
12379        flattened_macro_sentinel_name(siblings[*index], source)
12380            .is_some_and(|name| is_namespace_begin_sentinel(&name))
12381    })?;
12382
12383    let significant = siblings[begin_index + 1..declaration_index]
12384        .iter()
12385        .copied()
12386        .filter(|node| node.kind() != "comment")
12387        .collect::<Vec<_>>();
12388    let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
12389        return None;
12390    };
12391    if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
12392        return None;
12393    }
12394    let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
12395    if significant[2..].iter().any(|node| {
12396        flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
12397            is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
12398        })
12399    }) {
12400        return None;
12401    }
12402
12403    let mut saw_namespace_close = false;
12404    for sibling in siblings.iter().skip(declaration_index + 1).copied() {
12405        if sibling.kind() == "comment" {
12406            continue;
12407        }
12408        if !saw_namespace_close {
12409            if direct_unmatched_closing_brace(sibling) {
12410                saw_namespace_close = true;
12411                continue;
12412            }
12413            if flattened_macro_sentinel_name(sibling, source).is_some() {
12414                return None;
12415            }
12416            continue;
12417        }
12418        if !flattened_macro_sentinel_name(sibling, source)
12419            .is_some_and(|name| is_namespace_end_sentinel(&name))
12420        {
12421            return None;
12422        }
12423        let mut components = enclosing_namespace_components(declaration, source)?;
12424        components.push(namespace_name);
12425        return Some(components);
12426    }
12427    None
12428}
12429
12430fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
12431    // At translation-unit scope the trailing `X_NAMESPACE_END` token parses as
12432    // an `expression_statement` with a missing semicolon; inside a namespace
12433    // body the same token stays a bare `type_identifier`.
12434    let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
12435        node.named_child(0)?
12436    } else {
12437        node
12438    };
12439    let candidate = direct_cpp_identifier_name(node, source).or_else(|| {
12440        node.child_by_field_name("type")
12441            .and_then(|type_node| direct_cpp_identifier_name(type_node, source))
12442    })?;
12443    (cpp_export_macro_token(&candidate)
12444        && (is_namespace_begin_sentinel(&candidate) || is_namespace_end_sentinel(&candidate)))
12445    .then_some(candidate)
12446}
12447
12448/// Namespace-opening macros are spelled both ways in the wild:
12449/// `ABSL_NAMESPACE_BEGIN` (abseil, nlohmann) and `FMT_BEGIN_NAMESPACE` (fmt).
12450#[derive(Clone, Copy, PartialEq, Eq)]
12451enum NamespaceSentinel {
12452    Begin,
12453    End,
12454}
12455
12456fn namespace_sentinel(name: &str) -> Option<NamespaceSentinel> {
12457    if name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE") {
12458        Some(NamespaceSentinel::Begin)
12459    } else if name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE") {
12460        Some(NamespaceSentinel::End)
12461    } else {
12462        None
12463    }
12464}
12465
12466fn is_namespace_begin_sentinel(name: &str) -> bool {
12467    namespace_sentinel(name) == Some(NamespaceSentinel::Begin)
12468}
12469
12470fn is_namespace_end_sentinel(name: &str) -> bool {
12471    namespace_sentinel(name) == Some(NamespaceSentinel::End)
12472}
12473
12474fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
12475    if node.kind() != "ERROR" || node.named_child_count() != 1 {
12476        return None;
12477    }
12478    let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
12479    (!cpp_export_macro_token(&name)).then_some(name)
12480}
12481
12482fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
12483    if !matches!(
12484        node.kind(),
12485        "identifier" | "namespace_identifier" | "type_identifier"
12486    ) {
12487        return None;
12488    }
12489    let name = normalize_cpp_whitespace(node_text(node, source));
12490    (!name.is_empty()).then_some(name)
12491}
12492
12493fn guard_requirement_sets_match(
12494    left: &[(usize, HashSet<PreprocessorGuard>)],
12495    right: &[(usize, HashSet<PreprocessorGuard>)],
12496) -> bool {
12497    left.len() == right.len()
12498        && left.iter().all(|(_, left_guards)| {
12499            right
12500                .iter()
12501                .any(|(_, right_guards)| left_guards == right_guards)
12502        })
12503        && right.iter().all(|(_, right_guards)| {
12504            left.iter()
12505                .any(|(_, left_guards)| right_guards == left_guards)
12506        })
12507}
12508
12509pub(crate) fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
12510    node.kind() == "ERROR"
12511        && (0..node.child_count())
12512            .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
12513}
12514
12515pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
12516    let mut ancestor = node.parent();
12517    while let Some(parent) = ancestor {
12518        if is_cpp_preprocessor_conditional(parent)
12519            && !is_file_covering_include_guard(parent, source)
12520            && !is_split_cpp_language_linkage_wrapper(parent, node, source)
12521        {
12522            return false;
12523        }
12524        ancestor = parent.parent();
12525    }
12526    true
12527}
12528
12529fn is_split_cpp_language_linkage_wrapper(
12530    conditional: Node<'_>,
12531    descendant: Node<'_>,
12532    source: &str,
12533) -> bool {
12534    if conditional.child_by_field_name("alternative").is_some()
12535        || !matches!(
12536            simple_preprocessor_guard(conditional, source),
12537            Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12538        )
12539    {
12540        return false;
12541    }
12542    let mut current = descendant.parent();
12543    let linkage = loop {
12544        let Some(node) = current else {
12545            return false;
12546        };
12547        if node == conditional {
12548            return false;
12549        }
12550        if node.kind() == "linkage_specification" {
12551            break node;
12552        }
12553        current = node.parent();
12554    };
12555    if linkage
12556        .child_by_field_name("value")
12557        .is_none_or(|value| node_text(value, source) != "\"C\"")
12558    {
12559        return false;
12560    }
12561    let Some(body) = linkage.child_by_field_name("body") else {
12562        return false;
12563    };
12564    // Walk the body with cursors: an indexed `named_child(i)` restarts from
12565    // the first child, so indexed loops over a header-sized linkage body made
12566    // each call quadratic, and this runs once per declaration inside it
12567    // (#3397).
12568    let mut cursor = body.walk();
12569    let closes_opening_branch = body
12570        .named_children(&mut cursor)
12571        .take_while(|child| child.end_byte() <= descendant.start_byte())
12572        .any(|child| {
12573            child.kind() == "preproc_call"
12574                && child
12575                    .child_by_field_name("directive")
12576                    .is_some_and(|directive| node_text(directive, source) == "#endif")
12577        });
12578    let mut cursor = body.walk();
12579    let reopens_for_closing_brace = body
12580        .named_children(&mut cursor)
12581        .skip_while(|child| child.start_byte() < descendant.end_byte())
12582        .any(|child| {
12583            matches!(
12584                simple_preprocessor_guard(child, source),
12585                Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12586            ) && {
12587                let mut tokens = child.walk();
12588                child
12589                    .children(&mut tokens)
12590                    .any(|token| token.kind() == "#endif" && token.is_missing())
12591            }
12592        });
12593    closes_opening_branch && reopens_for_closing_brace
12594}
12595
12596/// The argument list a call-shaped node supplies: `f(args)`, `new T(args)`,
12597/// `T{args}` and the member initializer `: field(args)`, whose grammar gives its
12598/// argument list no field name.
12599pub fn call_arguments_node(node: Node<'_>) -> Option<Node<'_>> {
12600    node.child_by_field_name("arguments")
12601        .or_else(|| node.child_by_field_name("parameters"))
12602        .or_else(|| node.child_by_field_name("value"))
12603        .or_else(|| first_named_child_of_kind(node, "argument_list"))
12604        .or_else(|| first_named_child_of_kind(node, "initializer_list"))
12605}
12606
12607pub fn call_arity(node: Node<'_>) -> usize {
12608    call_arguments_node(node)
12609        .map(|args| argument_children(args).count())
12610        .unwrap_or(0)
12611}
12612
12613pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
12614    let recovered_block_arguments = recovered_block_literal_arguments(node);
12615    (0..node.child_count())
12616        .filter_map(move |index| node.child(index))
12617        .filter(|child| child.is_named() && !child.is_extra())
12618        .flat_map(move |child| {
12619            if let Some((raw, left, right)) = recovered_block_arguments
12620                && child == raw
12621            {
12622                [Some(left), Some(right)]
12623            } else {
12624                [Some(child), None]
12625            }
12626        })
12627        .flatten()
12628}
12629
12630/// Recover the two ordinary C values that the C++ grammar folds into one
12631/// `new_expression` for `callee(new, trailing)`.
12632///
12633/// The malformed node has an exact grammar-owned shape: the anonymous `new`
12634/// token, an extra `ERROR` containing only the comma token, and the trailing
12635/// value in the `type` field.  The caller supplies the compilation-dialect
12636/// proof from [`reference_uses_c_semantics`]; a C++ source therefore never
12637/// reinterprets a real new-expression through this path.
12638pub fn recovered_c_new_expression_arguments(
12639    node: Node<'_>,
12640    uses_c_semantics: bool,
12641) -> Option<[Node<'_>; 2]> {
12642    if !uses_c_semantics || node.kind() != "new_expression" {
12643        return None;
12644    }
12645    let parent = node.parent()?;
12646    if parent.kind() != "argument_list" {
12647        return None;
12648    }
12649    let keyword = node.child(0)?;
12650    let error = node.child(1)?;
12651    let trailing = node.child(2)?;
12652    if node.child(3).is_some()
12653        || keyword.kind() != "new"
12654        || keyword.is_named()
12655        || keyword.child_count() != 0
12656        || error.kind() != "ERROR"
12657        || !error.is_extra()
12658        || error.child_count() != 1
12659        || error.child(0).is_none_or(|comma| comma.kind() != ",")
12660        || node.child_by_field_name("type") != Some(trailing)
12661        || trailing.kind() != "type_identifier"
12662    {
12663        return None;
12664    }
12665    Some([keyword, trailing])
12666}
12667
12668/// The recovered C value covering one focused source range, starting from any
12669/// node within the malformed new-expression.
12670pub fn recovered_c_new_expression_argument_at(
12671    mut node: Node<'_>,
12672    start_byte: usize,
12673    end_byte: usize,
12674    uses_c_semantics: bool,
12675) -> Option<Node<'_>> {
12676    // The recovery only exists for C sources, and the climb below reaches the
12677    // root before it can answer no. `Node::parent` re-descends from the root on
12678    // every step, so leaving the check inside the loop made every scanned node
12679    // of a C++ file pay a full quadratic ancestor walk for an answer settled by
12680    // the file's language (#3097).
12681    if !uses_c_semantics {
12682        return None;
12683    }
12684    loop {
12685        if let Some(arguments) = recovered_c_new_expression_arguments(node, uses_c_semantics) {
12686            return arguments.into_iter().find(|argument| {
12687                argument.start_byte() <= start_byte && end_byte <= argument.end_byte()
12688            });
12689        }
12690        node = node.parent()?;
12691    }
12692}
12693
12694fn recovered_c_keyword_argument_count(
12695    file: &ProjectFile,
12696    call: Node<'_>,
12697    arguments: Node<'_>,
12698    source: &str,
12699) -> usize {
12700    // A C identifier that is a C++ keyword can be displaced twice by the C++
12701    // grammar: first into a direct parameter-list `ERROR(keyword)`, then into
12702    // a direct argument-list `ERROR(',', keyword)`. Match those CST tokens in
12703    // the enclosing C function before restoring the otherwise dropped slot;
12704    // ABI15's collapsed function-head shape is handled by the fallback below.
12705    if !is_c_source_file(file) || arguments.kind() != "argument_list" {
12706        return 0;
12707    }
12708    let mut ancestor = Some(call);
12709    let function = loop {
12710        let Some(current) = ancestor else {
12711            return recovered_c_keyword_argument_count_from_collapsed_function(call, arguments)
12712                .unwrap_or(0);
12713        };
12714        if current.kind() == "function_definition" {
12715            break current;
12716        }
12717        ancestor = current.parent();
12718    };
12719    let Some(parameters) = function
12720        .child_by_field_name("declarator")
12721        .and_then(|declarator| declarator.child_by_field_name("parameters"))
12722    else {
12723        return 0;
12724    };
12725    let displaced_parameter_keywords = (0..parameters.child_count())
12726        .filter_map(|index| parameters.child(index))
12727        .filter(|error| error.kind() == "ERROR")
12728        .filter_map(|error| {
12729            let parameter = error.prev_named_sibling()?;
12730            if parameter.kind() != "parameter_declaration"
12731                || parameter.end_byte() != error.start_byte()
12732                || extract_variable_name(parameter, source).is_some()
12733            {
12734                return None;
12735            }
12736            let mut children = (0..error.child_count())
12737                .filter_map(|index| error.child(index))
12738                .filter(|child| !child.is_extra() && !child.is_missing());
12739            let keyword = children.next()?;
12740            (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
12741                .then_some(keyword)
12742        })
12743        .collect::<Vec<_>>();
12744    if displaced_parameter_keywords.is_empty() {
12745        return 0;
12746    }
12747
12748    (0..arguments.child_count())
12749        .filter_map(|index| arguments.child(index))
12750        .filter(|error| error.kind() == "ERROR" && error.is_extra())
12751        .filter(|error| {
12752            let mut children = (0..error.child_count())
12753                .filter_map(|index| error.child(index))
12754                .filter(|child| !child.is_extra() && !child.is_missing());
12755            let Some(comma) = children.next() else {
12756                return false;
12757            };
12758            let Some(keyword) = children.next() else {
12759                return false;
12760            };
12761            children.next().is_none()
12762                && comma.kind() == ","
12763                && !keyword.is_named()
12764                && keyword.child_count() == 0
12765                && displaced_parameter_keywords
12766                    .iter()
12767                    .any(|parameter| parameter.kind_id() == keyword.kind_id())
12768        })
12769        .count()
12770}
12771
12772/// ABI15 can collapse a C function head into nested `ERROR` nodes when its
12773/// parameter name is a C++ keyword. The call still has a structured body, and
12774/// the outer error retains the function type, name, and opening parenthesis;
12775/// recover the displaced parameter only from those CST relationships. Text
12776/// matching would also accept an unbound call after the collapsed body.
12777fn recovered_c_keyword_argument_count_from_collapsed_function(
12778    call: Node<'_>,
12779    arguments: Node<'_>,
12780) -> Option<usize> {
12781    let argument_keywords = children_iter(arguments)
12782        .filter(|error| error.kind() == "ERROR" && error.is_extra())
12783        .filter_map(|error| {
12784            let mut children =
12785                children_iter(error).filter(|child| !child.is_extra() && !child.is_missing());
12786            let comma = children.next()?;
12787            let keyword = children.next()?;
12788            (children.next().is_none()
12789                && comma.kind() == ","
12790                && !keyword.is_named()
12791                && keyword.child_count() == 0)
12792                .then_some(keyword)
12793        })
12794        .collect::<Vec<_>>();
12795    if argument_keywords.is_empty() {
12796        return Some(0);
12797    }
12798
12799    // The collapsed function keeps its body under the parameter ERROR, while
12800    // ordinary control-flow blocks add compound_statement layers below it.
12801    // Stop before crossing a real callable body, whose parameters are a
12802    // separate binding scope.
12803    let mut ancestor = call.parent();
12804    let body = loop {
12805        let current = ancestor?;
12806        if matches!(current.kind(), "function_definition" | "lambda_expression") {
12807            return None;
12808        }
12809        if current.kind() == "compound_statement"
12810            && current
12811                .parent()
12812                .is_some_and(|parent| parent.kind() == "ERROR")
12813        {
12814            break current;
12815        }
12816        ancestor = current.parent();
12817    };
12818    let parameter_error = body.parent().filter(|parent| parent.kind() == "ERROR")?;
12819    let function_error = parameter_error
12820        .parent()
12821        .filter(|parent| parent.kind() == "ERROR")?;
12822    let function_children = children_iter(function_error).collect::<Vec<_>>();
12823    let parameter_error_index = function_children
12824        .iter()
12825        .position(|child| same_node(*child, parameter_error))?;
12826    // ERROR nodes expose their retained field labels by child index, while
12827    // child_by_field_name does not return their type child in runtime 0.25.
12828    let has_return_type = function_children[..parameter_error_index.saturating_sub(2)]
12829        .iter()
12830        .enumerate()
12831        .any(|(index, child)| {
12832            function_error.field_name_for_child(index as u32) == Some("type")
12833                && !child.is_extra()
12834                && !child.is_missing()
12835                && matches!(
12836                    child.kind(),
12837                    "primitive_type" | "sized_type_specifier" | "type_identifier"
12838                )
12839        });
12840    if parameter_error_index < 2
12841        || !has_return_type
12842        || function_children[parameter_error_index - 1].kind() != "("
12843        || function_children[parameter_error_index - 2].kind() != "identifier"
12844    {
12845        return Some(0);
12846    }
12847
12848    let parameter_children = children_iter(parameter_error).collect::<Vec<_>>();
12849    let body_index = parameter_children
12850        .iter()
12851        .position(|child| same_node(*child, body))?;
12852    let close_parenthesis_index = parameter_children[..body_index]
12853        .iter()
12854        .position(|child| child.kind() == ")")?;
12855    let parameter_keywords = parameter_children[..close_parenthesis_index]
12856        .iter()
12857        .enumerate()
12858        .filter(|(index, child)| {
12859            !child.is_named()
12860                && !child.is_extra()
12861                && !child.is_missing()
12862                && child.child_count() == 0
12863                && argument_keywords
12864                    .iter()
12865                    .any(|keyword| keyword.kind_id() == child.kind_id())
12866                && collapsed_c_parameter_name_position(&parameter_children, *index)
12867        })
12868        .map(|(_, child)| *child)
12869        .collect::<Vec<_>>();
12870    Some(
12871        argument_keywords
12872            .iter()
12873            .filter(|keyword| {
12874                parameter_keywords
12875                    .iter()
12876                    .any(|parameter| parameter.kind_id() == keyword.kind_id())
12877            })
12878            .count(),
12879    )
12880}
12881
12882/// The ABI15 collapsed head retains a parameter's type and pointer declarator
12883/// as direct children. Require that exact segment before accepting an
12884/// anonymous keyword as its name; a keyword after an `=` or attribute must not
12885/// borrow the same recovery.
12886fn collapsed_c_parameter_name_position(children: &[Node<'_>], keyword_index: usize) -> bool {
12887    let segment_start = children[..keyword_index]
12888        .iter()
12889        .rposition(|child| matches!(child.kind(), "(" | ","))
12890        .map_or(0, |index| index + 1);
12891    let mut saw_type = false;
12892    let mut saw_pointer = false;
12893    for child in &children[segment_start..keyword_index] {
12894        if child.kind() == "*" && !child.is_extra() && !child.is_missing() {
12895            saw_pointer = true;
12896            continue;
12897        }
12898        if saw_pointer {
12899            return false;
12900        }
12901        if !child.is_named() || child.is_extra() || child.is_missing() {
12902            return false;
12903        }
12904        if child.kind() == "type_qualifier" {
12905            continue;
12906        }
12907        if matches!(
12908            child.kind(),
12909            "class_specifier"
12910                | "dependent_type"
12911                | "enum_specifier"
12912                | "identifier"
12913                | "primitive_type"
12914                | "qualified_identifier"
12915                | "scoped_identifier"
12916                | "struct_specifier"
12917                | "template_type"
12918                | "type_identifier"
12919                | "union_specifier"
12920        ) {
12921            saw_type = true;
12922            continue;
12923        }
12924        return false;
12925    }
12926    saw_type && saw_pointer
12927}
12928
12929fn recovered_block_literal_arguments<'tree>(
12930    arguments: Node<'tree>,
12931) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
12932    if arguments.kind() != "argument_list" {
12933        return None;
12934    }
12935    let mut raw_arguments = (0..arguments.child_count())
12936        .filter_map(|index| arguments.child(index))
12937        .filter(|child| child.is_named() && !child.is_extra());
12938    let raw = raw_arguments.next()?;
12939    if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
12940        return None;
12941    }
12942
12943    let left = raw.child_by_field_name("left")?;
12944    if left.is_missing() || left.start_byte() == left.end_byte() {
12945        return None;
12946    }
12947    let right = raw.child_by_field_name("right")?;
12948    if right.kind() != "compound_literal_expression"
12949        || right.is_missing()
12950        || right
12951            .child_by_field_name("type")
12952            .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
12953        || right
12954            .child_by_field_name("value")
12955            .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
12956    {
12957        return None;
12958    }
12959    let has_intervening_error = (0..raw.child_count())
12960        .filter_map(|index| raw.child(index))
12961        .any(|child| {
12962            child.kind() == "ERROR"
12963                && !child.is_missing()
12964                && child.start_byte() >= left.end_byte()
12965                && child.end_byte() <= right.start_byte()
12966        });
12967    has_intervening_error.then_some((raw, left, right))
12968}
12969
12970pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
12971    match node.kind() {
12972        "new_expression" => node
12973            .child_by_field_name("type")
12974            .or_else(|| node.named_child(0)),
12975        "compound_literal_expression" => node.child_by_field_name("type"),
12976        "call_expression" => node.child_by_field_name("function"),
12977        _ => None,
12978    }
12979}
12980
12981/// The structured type named by a C-style cast expression.
12982///
12983/// Tree-sitter wraps the actual type syntax in a `type_descriptor`.  Return its
12984/// structured `type` field so resolution retains qualified and nested syntax
12985/// without reparsing source text.
12986pub fn cast_expression_type_node(node: Node<'_>) -> Option<Node<'_>> {
12987    if node.kind() != "cast_expression" {
12988        return None;
12989    }
12990    let descriptor = node.child_by_field_name("type")?;
12991    if descriptor.kind() == "type_descriptor" {
12992        descriptor.child_by_field_name("type")
12993    } else {
12994        Some(descriptor)
12995    }
12996}
12997
12998/// The expression a field access reads its member from.
12999///
13000/// That is normally the node in the `argument` field. A preprocessor
13001/// conditional inside a call's argument list defeats the grammar: tree-sitter
13002/// ends the argument list at the `#if` line, then reads the text that follows
13003/// `#endif` as a field access on the whole call expression and leaves the real
13004/// receiver in an `ERROR` node between the `argument` child and the operator.
13005/// mbedtls writes exactly that at `library/ssl_tls.c:4999`, where
13006/// `ssl->session->encrypt_then_mac` is one conditionally compiled call argument
13007/// (#2982).
13008///
13009/// `.` and `->` read whatever ends immediately before them in either tree, so
13010/// answer from the operator's position instead of trusting the `argument`
13011/// field, and unwrap the single expression a displaced `ERROR` holds.
13012pub fn cpp_field_expression_receiver(field: Node<'_>) -> Option<Node<'_>> {
13013    debug_assert_eq!(field.kind(), "field_expression");
13014    let operator = field.child_by_field_name("operator")?;
13015    let mut cursor = field.walk();
13016    let receiver = field
13017        .named_children(&mut cursor)
13018        .filter(|child| child.end_byte() <= operator.start_byte())
13019        .last()?;
13020    if receiver.kind() != "ERROR" {
13021        return Some(receiver);
13022    }
13023    (receiver.named_child_count() == 1)
13024        .then(|| receiver.named_child(0))
13025        .flatten()
13026}
13027
13028pub fn field_initializer_constructs_target(
13029    node: Node<'_>,
13030    ctx: &ScanCtx<'_>,
13031    owner: &CodeUnit,
13032) -> bool {
13033    // A qualified name in a constructor initializer denotes a base
13034    // subobject constructor (`namespace::Base(args)`), not a member field.  The
13035    // field-initializer grammar exposes the qualified name as one structured
13036    // `qualified_identifier`; resolve its owner through the same lexical type
13037    // machinery used for ordinary C++ type references before considering the
13038    // initializer a hit.  This keeps an unrelated `namespace::Other(...)`, a
13039    // qualified non-constructor member, and an unresolved owner out of the
13040    // target constructor's inverse usage set.
13041    if first_named_child_of_kind(node, "qualified_identifier").is_some() {
13042        return qualified_base_initializer_constructs_target(node, ctx, owner);
13043    }
13044    let Some(name) = node
13045        .child_by_field_name("name")
13046        .or_else(|| first_named_child_of_kind(node, "field_identifier"))
13047        .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
13048    else {
13049        return false;
13050    };
13051    let field_name = node_text(name, ctx.source);
13052    ctx.visibility
13053        .visible_identifier_candidates(ctx.file, field_name)
13054        .filter(|unit| unit.is_field() && unit.identifier() == field_name)
13055        .any(|unit| field_declares_type(unit, ctx, owner) && field_holds_subobject(unit, ctx))
13056}
13057
13058/// Whether a member initializer for `unit` runs a constructor of the type the
13059/// member declares.
13060///
13061/// Only a member that holds the type by value does. `Owner* p` initialized as
13062/// `p(nullptr)` binds a pointer and `Owner& r` as `r(other)` binds a
13063/// reference; neither runs an `Owner` constructor, even though both declare
13064/// `Owner` as the receiver type a member access through them resolves (#3286).
13065/// A member whose declaration the index cannot decode keeps the by-value
13066/// reading, which is what the declared-type match already assumed.
13067fn field_holds_subobject(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
13068    field_initializer_holds_subobject(&ctx.analyzer, ctx.visibility, unit)
13069}
13070
13071fn qualified_base_initializer_constructs_target(
13072    node: Node<'_>,
13073    ctx: &ScanCtx<'_>,
13074    owner: &CodeUnit,
13075) -> bool {
13076    let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
13077        return false;
13078    };
13079    let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
13080        return false;
13081    };
13082    let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
13083        return false;
13084    };
13085    let resolves_target = |components: &[String]| {
13086        matches!(
13087            ctx.visibility.resolve_type_components_lexically_for_target(
13088                &ctx.analyzer,
13089                ctx.file,
13090                components,
13091                is_globally_qualified_cpp_name(qualified),
13092                &lexical_scope,
13093                owner,
13094            ),
13095            LexicalTypeResolution::Resolved { unit, .. }
13096                if same_visible_symbol(&unit, owner)
13097        )
13098    };
13099    if resolves_target(&components) {
13100        return true;
13101    }
13102
13103    // Some real-world code spells a base mem-initializer as
13104    // `Base::Base(args)`. In that structured path the final component repeats
13105    // the constructor name; resolve the preceding type path. The terminal
13106    // identity check prevents an arbitrary qualified member from taking this
13107    // route.
13108    components
13109        .last()
13110        .is_some_and(|terminal| terminal == owner.identifier())
13111        && resolves_target(&components[..components.len() - 1])
13112}
13113
13114fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
13115    ctx.visibility
13116        .field_declared_type_fact(&ctx.analyzer, unit)
13117        .is_some_and(|fact| {
13118            ctx.visibility
13119                .resolves_to_type(&ctx.analyzer, ctx.file, &fact.type_text, owner)
13120                || ctx.visibility.resolves_to_type(
13121                    &ctx.analyzer,
13122                    ctx.file,
13123                    &normalize_field_type_text(&fact.type_text),
13124                    owner,
13125                )
13126        })
13127}
13128
13129pub fn field_declared_binding(
13130    analyzer: &CppGraphSource<'_>,
13131    visibility: &VisibilityIndex<'_>,
13132    visible_from: &ProjectFile,
13133    field: &CodeUnit,
13134) -> Option<CppScanBinding> {
13135    let (normalized, resolved, indirection) =
13136        field_declared_type_binding(analyzer, visibility, visible_from, field)?;
13137    let resolved = resolved
13138        .or_else(|| anonymous_aggregate_field_owner(analyzer, visibility, visible_from, field));
13139    Some(CppScanBinding::from_type_name(
13140        normalized,
13141        resolved,
13142        indirection,
13143    ))
13144}
13145
13146/// Whether a member-initializer `field(args)` runs the declared type's
13147/// constructor: only a member that holds the type by value does. `Owner* p`
13148/// initialized as `p(nullptr)` binds a pointer and `Owner& r` as `r(other)`
13149/// binds a reference; neither runs an `Owner` constructor (#3286). A member
13150/// whose declaration the index cannot decode keeps the by-value reading, the
13151/// same assumption the usage scan's declared-type match makes.
13152pub fn field_initializer_holds_subobject(
13153    analyzer: &CppGraphSource<'_>,
13154    visibility: &VisibilityIndex<'_>,
13155    field: &CodeUnit,
13156) -> bool {
13157    visibility
13158        .field_declared_type_fact(analyzer, field)
13159        .is_none_or(|fact| !fact.binds_indirectly)
13160}
13161
13162/// Resolve the receiver type minted for a named declarator on an anonymous C
13163/// aggregate, such as `struct { int r; } c`. The declaration index preserves
13164/// the aggregate as the nested class `Owner$c`, but the field's type fact has
13165/// no spelling that can name that class. Confirm the anonymous aggregate from
13166/// its parsed declaration, then use the owner's structured child relationship
13167/// to recover the one corresponding receiver type.
13168fn anonymous_aggregate_field_owner(
13169    analyzer: &CppGraphSource<'_>,
13170    visibility: &VisibilityIndex<'_>,
13171    visible_from: &ProjectFile,
13172    field: &CodeUnit,
13173) -> Option<CodeUnit> {
13174    let owner = type_owner_of(analyzer, field)?;
13175    if !owner.is_class() {
13176        return None;
13177    }
13178    let declaration = analyzer.get_source(field, false)?;
13179    let mut parser = Parser::new();
13180    parser
13181        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13182        .ok()?;
13183    let tree = parser.parse(&declaration, None)?;
13184    let mut stack = vec![tree.root_node()];
13185    while let Some(node) = stack.pop() {
13186        if matches!(node.kind(), "declaration" | "field_declaration")
13187            && let Some(type_node) = node
13188                .child_by_field_name("type")
13189                .or_else(|| first_type_child(node))
13190            && matches!(type_node.kind(), "struct_specifier" | "union_specifier")
13191            && type_node.child_by_field_name("name").is_none()
13192            && declared_name_binding(node, type_node, field.identifier(), &declaration).is_some()
13193        {
13194            let matches = visibility
13195                .visible_members_for_owner_name(visible_from, &owner, field.identifier())
13196                .into_iter()
13197                .filter(|child| child.is_class() && child.identifier() == field.identifier())
13198                .collect::<Vec<_>>();
13199            return match matches.as_slice() {
13200                [child] => Some((*child).clone()),
13201                _ => None,
13202            };
13203        }
13204        let mut cursor = node.walk();
13205        stack.extend(node.named_children(&mut cursor));
13206    }
13207    None
13208}
13209
13210/// Resolve an anonymous C aggregate's generated owner from its declaration
13211/// range. Anonymous local structs and unions have no type name to enter into
13212/// the visibility index; declaration extraction gives them a structured class
13213/// identity keyed by the aggregate node's exact CST range instead. Matching
13214/// that range keeps nested aggregates and unrelated generated owners out of
13215/// the result without inspecting source text.
13216pub fn anonymous_aggregate_owner(
13217    analyzer: &CppGraphSource<'_>,
13218    file: &ProjectFile,
13219    node: Node<'_>,
13220) -> Option<CodeUnit> {
13221    if !matches!(node.kind(), "struct_specifier" | "union_specifier")
13222        || node.child_by_field_name("name").is_some()
13223    {
13224        return None;
13225    }
13226    let mut candidates = analyzer
13227        .declarations(file)
13228        .into_iter()
13229        .filter(|candidate| {
13230            candidate.is_class()
13231                && analyzer.ranges(candidate).into_iter().any(|range| {
13232                    range.start_byte == node.start_byte() && range.end_byte == node.end_byte()
13233                })
13234        })
13235        .collect::<Vec<_>>();
13236    candidates.sort_by_key(|candidate| candidate.fq_name());
13237    candidates.dedup();
13238    match candidates.as_slice() {
13239        [candidate] => Some(candidate.clone()),
13240        _ => None,
13241    }
13242}
13243
13244/// The one logical type the candidates name, or why they do not name one.
13245fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
13246    let Some(first) = candidates.first() else {
13247        return Err(TypeCandidateFailure::Unresolvable);
13248    };
13249    if candidates
13250        .iter()
13251        .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
13252    {
13253        Ok((*first).clone())
13254    } else {
13255        Err(TypeCandidateFailure::Ambiguous)
13256    }
13257}
13258
13259fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
13260    logical_type_candidate(candidates).ok()
13261}
13262
13263fn unique_type_candidate_preserving_alias(
13264    analyzer: &CppGraphSource<'_>,
13265    file: &ProjectFile,
13266    candidates: &[&CodeUnit],
13267) -> Option<CodeUnit> {
13268    let first = *candidates.first()?;
13269    if declared_type_alias(analyzer, first) {
13270        return candidates
13271            .iter()
13272            .all(|candidate| {
13273                declared_type_alias(analyzer, candidate)
13274                    && candidate.kind() == first.kind()
13275                    && candidate.fq_name() == first.fq_name()
13276                    && candidate.source() == first.source()
13277            })
13278            .then(|| first.clone());
13279    }
13280    if analyzer.reference_uses_c_semantics(file)
13281        && first.is_class()
13282        && indexed_c_tag_kind(analyzer, first).is_some()
13283    {
13284        let mut full_source = None;
13285        let mut tag_kind = None;
13286        for candidate in candidates.iter().copied() {
13287            let candidate_tag_kind = indexed_c_tag_kind(analyzer, candidate)?;
13288            if tag_kind
13289                .replace(candidate_tag_kind)
13290                .is_some_and(|existing| existing != candidate_tag_kind)
13291            {
13292                return None;
13293            }
13294            if cpp_class_declaration_strength(analyzer, candidate)
13295                == CppClassDeclarationStrength::Full
13296                && full_source
13297                    .replace(candidate.source())
13298                    .is_some_and(|existing| existing != candidate.source())
13299            {
13300                return None;
13301            }
13302        }
13303    }
13304    candidates
13305        .iter()
13306        .all(|candidate| {
13307            !declared_type_alias(analyzer, candidate)
13308                && candidate.kind() == first.kind()
13309                && candidate.fq_name() == first.fq_name()
13310        })
13311        .then(|| first.clone())
13312}
13313
13314pub(crate) fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
13315    brokk_bifrost_core::analyzer::capabilities::provider_declares_type_alias(
13316        analyzer.type_alias_provider(),
13317        unit,
13318    )
13319}
13320
13321pub fn field_declared_type_binding(
13322    analyzer: &CppGraphSource<'_>,
13323    visibility: &VisibilityIndex<'_>,
13324    visible_from: &ProjectFile,
13325    field: &CodeUnit,
13326) -> Option<(String, Option<CodeUnit>, i32)> {
13327    let fact = visibility.field_declared_type_fact(analyzer, field)?;
13328    let normalized = normalize_field_type_text(&fact.type_text);
13329    let primary = visibility.resolve_unique_canonical_type_for_declaration(
13330        analyzer,
13331        visible_from,
13332        field,
13333        &normalized,
13334    );
13335    let resolved = match (primary, fact.template_arguments.as_deref()) {
13336        (Some(primary), Some(arguments)) => visibility
13337            .resolve_template_arguments(visible_from, primary, arguments)
13338            .ok(),
13339        (resolved, None) => resolved,
13340        (None, Some(_)) => None,
13341    };
13342    Some((normalized, resolved, fact.indirection))
13343}
13344
13345fn decode_field_declared_type_fact(
13346    analyzer: &CppGraphSource<'_>,
13347    field: &CodeUnit,
13348) -> Option<DeclaredFieldTypeFact> {
13349    if let Some(facts) = analyzer.declaration_source_properties(field)
13350        && let Some(first) = facts.first().and_then(|fact| fact.field_type.as_ref())
13351        && facts
13352            .iter()
13353            .all(|fact| fact.field_type.as_ref() == Some(first))
13354    {
13355        return Some(first.clone());
13356    }
13357    let Some(declaration) = analyzer.get_source(field, false) else {
13358        return decode_indexed_field_declared_type_fact(analyzer, field);
13359    };
13360    let mut parser = Parser::new();
13361    parser
13362        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13363        .ok()?;
13364    // A field's indexed source is stored without its surrounding class body.
13365    // Give tree-sitter that grammatical context before checking recovery-only
13366    // field shapes such as `PyObject_HEAD Imaging image;`.
13367    let contextual_declaration = format!("struct __bifrost_field_context {{ {declaration} }};");
13368    let contextual_tree = parser.parse(&contextual_declaration, None)?;
13369    let mut stack = vec![contextual_tree.root_node()];
13370    while let Some(node) = stack.pop() {
13371        if let Some(recovered) = recovered_pyobject_head_field(node, &contextual_declaration)
13372            && node_text(recovered.name, &contextual_declaration) == field.identifier()
13373        {
13374            return Some(DeclaredFieldTypeFact {
13375                type_text: node_text(recovered.type_node, &contextual_declaration).to_string(),
13376                indirection: recovered.pointer_depth(),
13377                binds_indirectly: recovered.pointer_depth() > 0,
13378                template_arguments: None,
13379            });
13380        }
13381        if let Some(recovered) =
13382            recovered_function_like_field_declarator(node, &contextual_declaration)
13383            && node_text(recovered.name, &contextual_declaration) == field.identifier()
13384        {
13385            let type_node = node
13386                .child_by_field_name("type")
13387                .or_else(|| first_type_child(node))?;
13388            return Some(DeclaredFieldTypeFact {
13389                type_text: node_text(type_node, &contextual_declaration).to_string(),
13390                indirection: recovered.pointer_depth(),
13391                // A function-like declarator names a callable member, never a
13392                // subobject of the declared return type.
13393                binds_indirectly: true,
13394                template_arguments: cpp_template_reference_arguments(
13395                    type_node,
13396                    &contextual_declaration,
13397                ),
13398            });
13399        }
13400        if let Some(fact) =
13401            decode_declared_field_type_node(node, field.identifier(), &contextual_declaration)
13402        {
13403            return Some(fact);
13404        }
13405        let mut cursor = node.walk();
13406        stack.extend(node.named_children(&mut cursor));
13407    }
13408    let tree = parser.parse(&declaration, None)?;
13409    let mut stack = vec![tree.root_node()];
13410    while let Some(node) = stack.pop() {
13411        if let Some(fact) = decode_declared_field_type_node(node, field.identifier(), &declaration)
13412        {
13413            return Some(fact);
13414        }
13415        let mut cursor = node.walk();
13416        stack.extend(node.named_children(&mut cursor));
13417    }
13418    None
13419}
13420
13421/// Decode a field whose generated owner prevents the ordinary source lookup
13422/// from selecting a standalone declaration. The indexed ranges still point
13423/// into the physical syntax tree, so recover the enclosing declaration from
13424/// that structure and apply the same declarator decoder to it.
13425fn decode_indexed_field_declared_type_fact(
13426    analyzer: &CppGraphSource<'_>,
13427    field: &CodeUnit,
13428) -> Option<DeclaredFieldTypeFact> {
13429    let cpp = analyzer.cpp?;
13430    let prepared = cpp.prepared_syntax(analyzer.token, field.source())?;
13431    let source = prepared.source();
13432    let root = prepared.tree().root_node();
13433    for range in analyzer.ranges(field) {
13434        let end = range.start_byte.saturating_add(1).min(source.len());
13435        let mut current = root.descendant_for_byte_range(range.start_byte, end);
13436        while let Some(node) = current {
13437            if matches!(node.kind(), "declaration" | "field_declaration")
13438                && let Some(fact) =
13439                    decode_declared_field_type_node(node, field.identifier(), source)
13440            {
13441                return Some(fact);
13442            }
13443            current = node.parent();
13444        }
13445    }
13446    None
13447}
13448
13449fn decode_declared_field_type_node(
13450    node: Node<'_>,
13451    field_name: &str,
13452    source: &str,
13453) -> Option<DeclaredFieldTypeFact> {
13454    if !matches!(node.kind(), "declaration" | "field_declaration") {
13455        return None;
13456    }
13457    let type_node = node
13458        .child_by_field_name("type")
13459        .or_else(|| first_type_child(node))?;
13460    cpp_declared_field_type_fact(node, type_node, field_name, source)
13461}
13462
13463pub(crate) fn cpp_declared_field_type_fact(
13464    node: Node<'_>,
13465    type_node: Node<'_>,
13466    field_name: &str,
13467    source: &str,
13468) -> Option<DeclaredFieldTypeFact> {
13469    let binding = declared_name_binding(node, type_node, field_name, source)?;
13470    let declared_type = if matches!(
13471        type_node.kind(),
13472        "class_specifier" | "struct_specifier" | "union_specifier"
13473    ) {
13474        type_node.child_by_field_name("name")
13475    } else {
13476        Some(type_node)
13477    };
13478    Some(DeclaredFieldTypeFact {
13479        type_text: declared_type.map_or_else(
13480            || field_name.to_string(),
13481            |declared_type| node_text(declared_type, source).to_string(),
13482        ),
13483        indirection: binding.pointer_depth,
13484        binds_indirectly: binding.indirect,
13485        template_arguments: declared_type
13486            .and_then(|declared_type| cpp_template_reference_arguments(declared_type, source)),
13487    })
13488}
13489
13490/// Text of the type that a C or C++ alias declaration names, read from the
13491/// `type_definition` or `alias_declaration` node's `type` field.
13492///
13493/// The declaration text is never scanned. A function-pointer typedef
13494/// interleaves its aliased type with its declarator (`typedef R (*F)(int)`),
13495/// so no prefix or suffix of the spelling isolates the target.
13496///
13497/// An alias whose declarator is a function declarator names a function type:
13498/// `typedef R F(int)`, `typedef R (*F)(int)`, `typedef R *F(int)`, and
13499/// `using F = R (*)(int)`. The analyzer's type model names declared types only,
13500/// so such an alias has no canonical target. Its `type` field holds the return
13501/// type `R`, which is a different type from the alias, so this returns `None`
13502/// rather than that return type.
13503pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
13504    let mut parser = Parser::new();
13505    parser
13506        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13507        .ok()?;
13508    let tree = parser.parse(declaration, None)?;
13509    let mut stack = vec![tree.root_node()];
13510    while let Some(node) = stack.pop() {
13511        let type_node = match node.kind() {
13512            "type_definition" => {
13513                let mut cursor = node.walk();
13514                if node
13515                    .children_by_field_name("declarator", &mut cursor)
13516                    .any(declarator_names_function_type)
13517                {
13518                    return None;
13519                }
13520                node.child_by_field_name("type")?
13521            }
13522            "alias_declaration" => {
13523                let type_node = node.child_by_field_name("type")?;
13524                if type_node
13525                    .child_by_field_name("declarator")
13526                    .is_some_and(declarator_names_function_type)
13527                {
13528                    return None;
13529                }
13530                type_node
13531            }
13532            _ => {
13533                let mut cursor = node.walk();
13534                let children = node.named_children(&mut cursor).collect::<Vec<_>>();
13535                stack.extend(children.into_iter().rev());
13536                continue;
13537            }
13538        };
13539        return Some(node_text(type_node, declaration).to_string());
13540    }
13541    None
13542}
13543
13544/// True when an alias declarator names a function type.
13545///
13546/// The declarator chain is walked through the `declarator` field, so the
13547/// parameter list -- a sibling field -- is never entered and a parameter's own
13548/// function declarator cannot be mistaken for the alias's.
13549pub(crate) fn declarator_names_function_type(declarator: Node<'_>) -> bool {
13550    let mut current = Some(declarator);
13551    while let Some(node) = current {
13552        match node.kind() {
13553            "function_declarator" | "abstract_function_declarator" => return true,
13554            "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
13555                current = node.named_child(0);
13556            }
13557            _ => current = node.child_by_field_name("declarator"),
13558        }
13559    }
13560    false
13561}
13562
13563fn decode_structured_alias_target(
13564    analyzer: &CppGraphSource<'_>,
13565    unit: &CodeUnit,
13566) -> Option<StructuredAliasTarget> {
13567    let facts = analyzer.declaration_source_properties(unit)?;
13568    let first = facts.first()?.alias_target.as_ref()?;
13569    facts
13570        .iter()
13571        .all(|fact| fact.alias_target.as_ref() == Some(first))
13572        .then(|| first.clone())
13573}
13574
13575pub(crate) fn structured_alias_type_target(
13576    mut type_node: Node<'_>,
13577    source: &str,
13578) -> Option<StructuredAliasTarget> {
13579    while type_node.kind() == "type_descriptor" {
13580        type_node = type_node.child_by_field_name("type")?;
13581    }
13582    if type_node.kind() == "primitive_type" {
13583        return Some(StructuredAliasTarget::Builtin);
13584    }
13585    if matches!(
13586        type_node.kind(),
13587        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
13588    ) {
13589        type_node = type_node.child_by_field_name("name")?;
13590    }
13591    let global = type_node.child_by_field_name("scope").is_none()
13592        && type_node.child(0).is_some_and(|child| child.kind() == "::");
13593    let mut components = Vec::new();
13594    append_structured_type_components(type_node, source, &mut components)?;
13595    let arguments = cpp_template_reference_arguments(type_node, source);
13596    (!components.is_empty()).then_some(StructuredAliasTarget::Named {
13597        components,
13598        global,
13599        arguments,
13600    })
13601}
13602
13603fn append_structured_type_components(
13604    node: Node<'_>,
13605    source: &str,
13606    out: &mut Vec<String>,
13607) -> Option<()> {
13608    match node.kind() {
13609        "identifier" | "namespace_identifier" | "type_identifier" => {
13610            out.push(node_text(node, source).to_string());
13611            Some(())
13612        }
13613        "template_type" | "template_function" => {
13614            append_structured_type_components(node.child_by_field_name("name")?, source, out)
13615        }
13616        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
13617            let (scope, name) = qualified_name_parts(node)?;
13618            if let Some(scope) = scope {
13619                append_structured_type_components(scope, source, out)?;
13620            }
13621            append_structured_type_components(name, source, out)
13622        }
13623        _ => None,
13624    }
13625}
13626
13627/// How the declarator that names a declared entity binds the declaration's
13628/// type.
13629pub(crate) struct DeclaredNameBinding {
13630    /// Pointer declarators between the type and the name.
13631    pub pointer_depth: i32,
13632    /// The name binds the declared type through a pointer or a reference, so
13633    /// the declared entity holds no subobject of that type. An array of the
13634    /// type does hold subobjects, so it is not indirect.
13635    pub indirect: bool,
13636}
13637
13638pub(crate) fn declared_name_binding(
13639    declaration: Node<'_>,
13640    type_node: Node<'_>,
13641    field_name: &str,
13642    source: &str,
13643) -> Option<DeclaredNameBinding> {
13644    let mut stack = Vec::new();
13645    let mut cursor = declaration.walk();
13646    stack.extend(
13647        declaration
13648            .named_children(&mut cursor)
13649            .filter(|child| !same_node(*child, type_node)),
13650    );
13651    while let Some(node) = stack.pop() {
13652        if matches!(node.kind(), "identifier" | "field_identifier")
13653            && node_text(node, source) == field_name
13654        {
13655            let mut binding = DeclaredNameBinding {
13656                pointer_depth: 0,
13657                indirect: false,
13658            };
13659            let mut current = node.parent();
13660            while let Some(parent) = current {
13661                if same_node(parent, declaration) {
13662                    return Some(binding);
13663                }
13664                match parent.kind() {
13665                    "pointer_declarator" => {
13666                        binding.pointer_depth += 1;
13667                        binding.indirect = true;
13668                    }
13669                    "reference_declarator" => binding.indirect = true,
13670                    _ => {}
13671                }
13672                current = parent.parent();
13673            }
13674            return None;
13675        }
13676        let mut cursor = node.walk();
13677        stack.extend(node.named_children(&mut cursor));
13678    }
13679    None
13680}
13681
13682fn normalize_field_type_text(type_text: &str) -> String {
13683    const FIELD_SPECIFIERS: [&str; 8] = [
13684        "extern ",
13685        "static ",
13686        "mutable ",
13687        "constexpr ",
13688        "constinit ",
13689        "inline ",
13690        "volatile ",
13691        "const ",
13692    ];
13693
13694    let mut normalized = normalize_type_text(type_text);
13695    loop {
13696        let Some(stripped) = FIELD_SPECIFIERS
13697            .iter()
13698            .find_map(|specifier| normalized.strip_prefix(specifier))
13699        else {
13700            return normalized;
13701        };
13702        normalized = normalize_type_text(stripped);
13703    }
13704}
13705
13706pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
13707    let Some(type_node) = node.child_by_field_name("type") else {
13708        return false;
13709    };
13710    ctx.visibility.resolves_to_type(
13711        &ctx.analyzer,
13712        ctx.file,
13713        node_text(type_node, ctx.source),
13714        owner,
13715    )
13716}
13717
13718pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13719    !ctx.analyzer
13720        .declarations(ctx.file)
13721        .into_iter()
13722        .filter(|unit| unit.is_function())
13723        .any(|unit| {
13724            ctx.analyzer.ranges(&unit).iter().any(|range| {
13725                node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
13726            })
13727        })
13728}
13729
13730/// How a `T var ...;` declaration initializes its object.
13731pub enum DeclarationConstructorInitializer<'tree> {
13732    /// Direct initialization, `T var(args)` or `T var{args}`: the argument list
13733    /// the declaration hands the constructor.
13734    Arguments(Node<'tree>),
13735    /// Copy initialization from one expression, `T var = expr`, which supplies a
13736    /// single constructor argument without spelling an argument list.
13737    Expression(Node<'tree>),
13738    /// `T var;`, which names no constructor argument at all.
13739    Empty,
13740}
13741
13742pub fn declaration_constructor_initializer(
13743    node: Node<'_>,
13744) -> DeclarationConstructorInitializer<'_> {
13745    let mut cursor = node.walk();
13746    for child in node.named_children(&mut cursor) {
13747        if child.kind() == "init_declarator" {
13748            let Some(value) = child
13749                .child_by_field_name("value")
13750                .or_else(|| first_named_child_of_kind(child, "initializer_list"))
13751                .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
13752            else {
13753                return DeclarationConstructorInitializer::Empty;
13754            };
13755            return match value.kind() {
13756                "argument_list" | "initializer_list" => {
13757                    DeclarationConstructorInitializer::Arguments(value)
13758                }
13759                "compound_literal_expression" => call_arguments_node(value)
13760                    .map_or(DeclarationConstructorInitializer::Empty, |arguments| {
13761                        DeclarationConstructorInitializer::Arguments(arguments)
13762                    }),
13763                _ => DeclarationConstructorInitializer::Expression(value),
13764            };
13765        }
13766        if let Some(declarator) = declaration_declarator(node, child) {
13767            return declarator_parameters(declarator)
13768                .map_or(DeclarationConstructorInitializer::Empty, |parameters| {
13769                    DeclarationConstructorInitializer::Arguments(parameters)
13770                });
13771        }
13772    }
13773    DeclarationConstructorInitializer::Empty
13774}
13775
13776pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
13777    match declaration_constructor_initializer(node) {
13778        DeclarationConstructorInitializer::Arguments(arguments) => {
13779            argument_children(arguments).count()
13780        }
13781        DeclarationConstructorInitializer::Expression(_) => 1,
13782        DeclarationConstructorInitializer::Empty => 0,
13783    }
13784}
13785
13786/// The parameter list of the innermost declarator, which is where a
13787/// `T var(args)` declaration parsed as a function declarator keeps the
13788/// constructor arguments.
13789fn declarator_parameters(node: Node<'_>) -> Option<Node<'_>> {
13790    let mut current = node;
13791    loop {
13792        if let Some(parameters) = current.child_by_field_name("parameters") {
13793            return Some(parameters);
13794        }
13795        current = current.child_by_field_name("declarator")?;
13796    }
13797}
13798
13799fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
13800    brokk_bifrost_core::analyzer::tree_walk::first_named_descendant_of_kind_inclusive(root, kind)
13801}
13802
13803fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
13804    if node.kind() == "identifier" {
13805        return true;
13806    }
13807    if node.kind() == "parenthesized_expression" {
13808        return false;
13809    }
13810    if node.kind() == "call_expression" {
13811        return node
13812            .child_by_field_name("function")
13813            .is_some_and(|function| function.kind() == "identifier");
13814    }
13815    let mut stack = vec![node];
13816    while let Some(descendant) = stack.pop() {
13817        if descendant != node && descendant.kind() == "parenthesized_expression" {
13818            continue;
13819        }
13820        if descendant.kind() == "identifier" {
13821            return true;
13822        }
13823        if descendant.kind() == "call_expression" {
13824            if descendant
13825                .child_by_field_name("function")
13826                .is_some_and(|function| function.kind() == "identifier")
13827            {
13828                return true;
13829            }
13830            continue;
13831        }
13832        push_named_children_reversed(descendant, &mut stack);
13833    }
13834    false
13835}
13836
13837fn macro_expansion_shape_is_safe(
13838    node: Node<'_>,
13839    source: &str,
13840    parameters: &[String],
13841    environment: &MacroEnvironment,
13842) -> bool {
13843    if matches!(node.kind(), "identifier" | "parenthesized_expression") {
13844        return true;
13845    }
13846    if node.kind() == "call_expression" {
13847        let Some(function) = node.child_by_field_name("function") else {
13848            return true;
13849        };
13850        if function.kind() != "identifier" {
13851            return true;
13852        }
13853        let function_name = node_text(function, source);
13854        if parameters
13855            .iter()
13856            .any(|parameter| parameter == function_name)
13857        {
13858            return false;
13859        }
13860        if !environment.may_bind(function_name) {
13861            return true;
13862        }
13863        let Some(arguments) = node.child_by_field_name("arguments") else {
13864            return false;
13865        };
13866        return argument_children(arguments).all(|argument| {
13867            if argument.kind() == "identifier"
13868                && parameters
13869                    .iter()
13870                    .any(|parameter| parameter == node_text(argument, source))
13871            {
13872                return false;
13873            }
13874            macro_expansion_shape_is_safe(argument, source, parameters, environment)
13875        });
13876    }
13877    let mut stack = vec![node];
13878    while let Some(descendant) = stack.pop() {
13879        if descendant != node {
13880            if descendant.kind() == "parenthesized_expression" {
13881                continue;
13882            }
13883            if descendant.kind() == "call_expression" {
13884                let expands = descendant
13885                    .child_by_field_name("function")
13886                    .filter(|function| function.kind() == "identifier")
13887                    .is_some_and(|function| environment.may_bind(node_text(function, source)));
13888                if expands {
13889                    return false;
13890                }
13891                continue;
13892            }
13893        }
13894        if descendant.kind() == "identifier" {
13895            let identifier = node_text(descendant, source);
13896            if parameters.iter().any(|parameter| parameter == identifier)
13897                || environment.may_bind(identifier)
13898            {
13899                return false;
13900            }
13901        }
13902        push_named_children_reversed(descendant, &mut stack);
13903    }
13904    true
13905}
13906
13907fn collect_structured_include_facts(prepared: &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]> {
13908    let source = prepared.source();
13909    let mut facts = Vec::new();
13910    let mut nodes = vec![prepared.tree().root_node()];
13911    while let Some(node) = nodes.pop() {
13912        if node.kind() == "preproc_include" {
13913            let Some(path) = node
13914                .child_by_field_name("path")
13915                .and_then(|path| crate::imports::include_path_literal_value(path, source))
13916                .map(str::to_owned)
13917            else {
13918                continue;
13919            };
13920            facts.push(StructuredIncludeFact {
13921                start_byte: node.start_byte(),
13922                end_byte: node.end_byte(),
13923                path,
13924            });
13925            continue;
13926        }
13927        push_named_children_reversed(node, &mut nodes);
13928    }
13929    Arc::from(facts.into_boxed_slice())
13930}
13931
13932fn has_unresolved_include_visible_before_in_prepared(
13933    file: &ProjectFile,
13934    prepared: &PreparedSyntaxTree,
13935    include_targets: &IncludeTargetIndex,
13936    facts: &[StructuredIncludeFact],
13937    before_byte: usize,
13938) -> bool {
13939    let guards = OnceCell::new();
13940    let reference = CallableReferenceContext {
13941        file,
13942        position: Some(CallableReferencePosition {
13943            prepared,
13944            byte: before_byte,
13945            guards: &guards,
13946        }),
13947    };
13948    let root = prepared.tree().root_node();
13949    facts
13950        .iter()
13951        .filter(|fact| fact.end_byte <= before_byte)
13952        .any(|fact| {
13953            let node = root
13954                .descendant_for_byte_range(fact.start_byte, fact.end_byte)
13955                .expect("structured include fact range must be in prepared tree");
13956            assert_eq!(
13957                node.kind(),
13958                "preproc_include",
13959                "structured include fact range must identify its include node"
13960            );
13961            callable_preprocessor_context_is_visible_for_reference(
13962                node,
13963                prepared.source(),
13964                &reference,
13965            ) && resolve_include_targets_with_index(file, &fact.path, include_targets).is_empty()
13966        })
13967}
13968
13969fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
13970    let descendant = node;
13971    while let Some(parent) = node.parent() {
13972        if is_cpp_preprocessor_conditional(parent)
13973            && !is_file_covering_include_guard(parent, source)
13974            && preprocessor_conditional_contains_descendant(parent, descendant)
13975        {
13976            return true;
13977        }
13978        node = parent;
13979    }
13980    false
13981}
13982
13983/// The [`OwningPreprocessorConditionals`] of a macro event at `event`.
13984///
13985/// The descendant this walks up from is the one
13986/// [`VisibilityIndex::macro_event_condition_value`] starts from -- the
13987/// innermost node at the event's first byte, not the event node itself --
13988/// because containment compares that descendant's end against the recovered
13989/// conditional boundary, and the two nodes end in different places. An event
13990/// that [`has_preprocessor_conditional_ancestor`] rejects owns nothing: that
13991/// predicate is what has always decided whether an event is conditional at
13992/// all, and answering it first also skips the walk for the ordinary
13993/// unconditional event.
13994fn owning_preprocessor_conditionals(
13995    root: Node<'_>,
13996    event: Node<'_>,
13997    source: &str,
13998) -> OwningPreprocessorConditionals {
13999    if !has_preprocessor_conditional_ancestor(event, source) {
14000        return OwningPreprocessorConditionals::default();
14001    }
14002    let start = event.start_byte();
14003    let descendant = root
14004        .descendant_for_byte_range(start, start.saturating_add(1).min(source.len()))
14005        .expect("a byte inside the parsed tree names a descendant");
14006    let mut owners = Vec::new();
14007    let mut current = descendant.parent();
14008    while let Some(conditional) = current {
14009        if is_cpp_preprocessor_conditional(conditional)
14010            && !is_file_covering_include_guard(conditional, source)
14011            && preprocessor_conditional_contains_descendant(conditional, descendant)
14012        {
14013            owners.push(conditional.start_byte());
14014        }
14015        current = conditional.parent();
14016    }
14017    owners.into_boxed_slice()
14018}
14019
14020fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
14021    node.parent()
14022        .filter(|parent| parent.kind() == "translation_unit")
14023        .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
14024        && is_canonical_include_guard(node, source)
14025}
14026
14027fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
14028    if node.kind() != "preproc_ifdef"
14029        || node
14030            .child(0)
14031            .is_none_or(|directive| directive.kind() != "#ifndef")
14032        || node.child_by_field_name("alternative").is_some()
14033    {
14034        return false;
14035    }
14036    let Some(guard_name) = node.child_by_field_name("name") else {
14037        return false;
14038    };
14039    let mut cursor = node.walk();
14040    node.named_children(&mut cursor)
14041        .find(|child| *child != guard_name && child.kind() != "comment")
14042        .filter(|child| child.kind() == "preproc_def")
14043        .and_then(|definition| definition.child_by_field_name("name"))
14044        .is_some_and(|defined_name| {
14045            node_text(defined_name, source) == node_text(guard_name, source)
14046        })
14047}
14048
14049fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
14050    let mut guard = None;
14051    for child in named_children_iter(root) {
14052        if child.kind() == "comment" || is_pragma_once(child, source) {
14053            continue;
14054        }
14055        if guard.is_none() && is_canonical_include_guard(child, source) {
14056            guard = Some(child);
14057        } else {
14058            return None;
14059        }
14060    }
14061    guard
14062        .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
14063        .map(|name| node_text(name, source).to_string())
14064}
14065
14066fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
14067    if (0..root.named_child_count())
14068        .filter_map(|index| root.named_child(index))
14069        .any(|child| is_pragma_once(child, source))
14070    {
14071        return MacroIncludeProtection::PragmaOnce;
14072    }
14073    top_level_canonical_include_guard_name(root, source)
14074        .map(MacroIncludeProtection::MacroGuard)
14075        .unwrap_or(MacroIncludeProtection::None)
14076}
14077
14078fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
14079    node.kind() == "preproc_call"
14080        && node
14081            .child_by_field_name("directive")
14082            .is_some_and(|directive| node_text(directive, source) == "#pragma")
14083        && node
14084            .child_by_field_name("argument")
14085            .is_some_and(|argument| node_text(argument, source).trim() == "once")
14086}
14087
14088fn parse_preproc_identifier(argument: &str) -> Option<String> {
14089    let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
14090    let mut parser = Parser::new();
14091    parser
14092        .set_language(&tree_sitter_cpp::LANGUAGE.into())
14093        .ok()?;
14094    let tree = parser.parse(&sentinel, None)?;
14095    if tree.root_node().has_error() {
14096        return None;
14097    }
14098    let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
14099    let identifier = statement.named_child(0)?;
14100    (identifier.kind() == "identifier" && statement.named_child_count() == 1)
14101        .then(|| node_text(identifier, &sentinel).to_string())
14102}
14103
14104pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
14105    match node.kind() {
14106        "identifier" | "field_identifier" => {
14107            let name = node_text(node, source).trim();
14108            (!name.is_empty()).then(|| name.to_string())
14109        }
14110        "abstract_array_declarator"
14111        | "abstract_function_declarator"
14112        | "abstract_parenthesized_declarator"
14113        | "abstract_pointer_declarator"
14114        | "abstract_reference_declarator" => None,
14115        "function_declarator" => node
14116            .child_by_field_name("declarator")
14117            .or_else(|| node.child_by_field_name("name"))
14118            .and_then(|child| extract_variable_name(child, source)),
14119        _ => node
14120            .child_by_field_name("declarator")
14121            .or_else(|| node.child_by_field_name("name"))
14122            .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
14123            .and_then(|child| extract_variable_name(child, source)),
14124    }
14125}
14126
14127/// Whether `file` is proven to use plain-C source semantics.
14128///
14129/// `Language::Cpp` intentionally serves both C and C++. Headers do not carry a
14130/// compilation dialect on their own, so only an exact `.c` source extension is
14131/// sufficient to reinterpret C++-grammar keyword nodes such as `this` as C
14132/// identifiers.
14133///
14134/// The exact-lowercase-`.c` rule itself lives in [`LanguageDialect::for_path`],
14135/// which extraction reads too (a `.c` file is extracted with C tag scope), so
14136/// the doctrine has exactly one definition.
14137pub fn is_c_source_file(file: &ProjectFile) -> bool {
14138    LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
14139}
14140
14141/// Whether tree-sitter parsed the operand of C `sizeof(T)` as an expression
14142/// identifier even though `T` may denote a typedef.
14143///
14144/// The grammar cannot distinguish `sizeof(value)` from `sizeof(Type)` without
14145/// semantic information. Keep this helper structural and narrow; callers must
14146/// still prove a visible type and reject an active ordinary-namespace shadow.
14147pub fn is_c_sizeof_expression_type_candidate(file: &ProjectFile, node: Node<'_>) -> bool {
14148    if !is_c_source_file(file) || node.kind() != "identifier" {
14149        return false;
14150    }
14151    let mut operand = node;
14152    while let Some(parent) = operand.parent().filter(|parent| {
14153        parent.kind() == "parenthesized_expression"
14154            && parent.named_child_count() == 1
14155            && parent.named_child(0) == Some(operand)
14156    }) {
14157        operand = parent;
14158    }
14159    operand.parent().is_some_and(|parent| {
14160        parent.kind() == "sizeof_expression" && parent.child_by_field_name("value") == Some(operand)
14161    })
14162}
14163
14164/// Return the type and member leaves of a C `offsetof` member designator.
14165///
14166/// `offsetof_expression` is a dedicated tree-sitter node, so its two operands
14167/// must be interpreted through their named fields.  In particular, do not
14168/// infer the aggregate from the enclosing lexical scope: an `offsetof` can
14169/// name a member of an unrelated aggregate, including a field promoted from
14170/// an anonymous union.  A missing or unsupported operand is deliberately
14171/// rejected so callers can keep the reference unresolved.
14172pub fn c_offsetof_member_parts(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
14173    if !is_c_offsetof_member_node(node) {
14174        return None;
14175    }
14176    let expression = node.parent()?;
14177    if expression.has_error() {
14178        return None;
14179    }
14180    let closing = expression.child(expression.child_count().saturating_sub(1))?;
14181    if closing.kind() != ")" || closing.is_missing() {
14182        return None;
14183    }
14184    let type_descriptor = expression.child_by_field_name("type")?;
14185    if type_descriptor.kind() != "type_descriptor"
14186        || type_descriptor.is_missing()
14187        || type_descriptor.has_error()
14188    {
14189        return None;
14190    }
14191    let type_specifier = type_descriptor.child_by_field_name("type")?;
14192    if type_specifier.is_missing() || type_specifier.has_error() {
14193        return None;
14194    }
14195    let type_reference = match type_specifier.kind() {
14196        "class_specifier" | "struct_specifier" | "union_specifier" => {
14197            type_specifier.child_by_field_name("name")?
14198        }
14199        _ => type_specifier,
14200    };
14201    (!type_reference.is_missing() && !type_reference.has_error()).then_some((type_reference, node))
14202}
14203
14204/// Whether `node` is the member leaf of an `offsetof_expression`, including a
14205/// malformed type operand.  Callers use this guard to prevent the ordinary
14206/// field-name heuristics from guessing an owner after structured resolution
14207/// has failed.
14208pub fn is_c_offsetof_member_node(node: Node<'_>) -> bool {
14209    node.kind() == "field_identifier"
14210        && node.parent().is_some_and(|parent| {
14211            parent.kind() == "offsetof_expression"
14212                && parent.child_by_field_name("member") == Some(node)
14213        })
14214}
14215
14216/// Admit an ABI 15 template-function shape to type resolution only through
14217/// the structured argument list of a type descriptor. This is syntax evidence;
14218/// callers must still resolve a visible type binding before proving identity.
14219pub fn cpp_template_function_has_type_owner(node: Node<'_>) -> bool {
14220    if node.kind() != "template_function" {
14221        return false;
14222    }
14223    let Some(arguments) = node
14224        .parent()
14225        .filter(|parent| parent.kind() == "template_argument_list")
14226    else {
14227        return false;
14228    };
14229    let Some(owner) = arguments.parent().filter(|parent| {
14230        parent.kind() == "template_type"
14231            && parent.child_by_field_name("arguments") == Some(arguments)
14232    }) else {
14233        return false;
14234    };
14235
14236    let mut current = owner;
14237    while let Some(parent) = current.parent() {
14238        if parent.kind() == "type_descriptor" && parent.child_by_field_name("type") == Some(current)
14239        {
14240            return true;
14241        }
14242        if parent.kind() == "qualified_identifier"
14243            && parent.child_by_field_name("name") == Some(current)
14244        {
14245            current = parent;
14246            continue;
14247        }
14248        return false;
14249    }
14250    false
14251}
14252
14253/// Whether `node` is a template argument name that tree-sitter spelled with
14254/// type syntax.
14255///
14256/// The grammar cannot tell a type argument from a non-type (value) argument, so
14257/// it gives both the same shape:
14258/// `template_argument_list -> type_descriptor -> type_identifier`. In
14259/// `std::array<W, N>` the type `W` and the constant `N` parse identically, and
14260/// so do `std::span<const uint8_t, ED448_LEN>`'s length and a nested type
14261/// member used as a real type argument.
14262///
14263/// This helper reports only the syntactic position. A caller must still prove
14264/// which namespace explains the spelling: forward navigation asks the type
14265/// namespace first and reads the leaf as a value only when no type explains it,
14266/// and the inverse field scan admits the leaf only when no visible type does
14267/// (#2556).
14268pub fn is_type_shaped_template_argument_name(node: Node<'_>) -> bool {
14269    if node.kind() != "type_identifier" {
14270        return false;
14271    }
14272    let Some(descriptor) = node
14273        .parent()
14274        .filter(|parent| parent.kind() == "type_descriptor")
14275    else {
14276        return false;
14277    };
14278    if descriptor.child_by_field_name("type") != Some(node) {
14279        return false;
14280    }
14281    let Some(arguments) = descriptor
14282        .parent()
14283        .filter(|parent| parent.kind() == "template_argument_list")
14284    else {
14285        return false;
14286    };
14287    arguments.parent().is_some_and(|owner| {
14288        matches!(
14289            owner.kind(),
14290            "template_type" | "template_function" | "template_method"
14291        ) && owner.child_by_field_name("arguments") == Some(arguments)
14292    })
14293}
14294
14295/// Whether a reference written in `file` reads C++ source with C semantics.
14296///
14297/// [`is_c_source_file`] answers the half a path settles on its own. The other
14298/// half is a header, which has no dialect of its own: it is read as C exactly
14299/// when every workspace translation unit that provably compiles it compiles it
14300/// as C ([`CppSource::header_uses_c_semantics`], issue #1970).
14301///
14302/// This is the gate for anything that is really about the compilation
14303/// language of the code being read -- which reading of an included header's
14304/// declarations is in scope, whether `this` is an ordinary identifier. It is
14305/// NOT the gate for a question that is genuinely about a `.c` file on disk;
14306/// those keep calling [`is_c_source_file`].
14307pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
14308    is_c_source_file(file) || cpp.header_uses_c_semantics(file)
14309}
14310
14311pub fn is_declarator_node(node: Node<'_>) -> bool {
14312    matches!(
14313        node.kind(),
14314        "identifier"
14315            | "field_identifier"
14316            | "qualified_identifier"
14317            | "scoped_identifier"
14318            | "pointer_declarator"
14319            | "reference_declarator"
14320            | "array_declarator"
14321            | "parenthesized_declarator"
14322            | "function_declarator"
14323    )
14324}
14325
14326/// Returns the declarator represented by one direct child of a declaration.
14327///
14328/// Tree-sitter exposes the first declarator through the `declarator` field,
14329/// but subsequent comma-separated declarators are unfielded direct children:
14330/// `int first, second;` therefore has an `identifier` child for `first` and a
14331/// second, otherwise identical, `identifier` child for `second`. Consumers
14332/// must inspect both shapes or a later declarator can be mistaken for a use of
14333/// an unrelated indexed symbol.
14334pub fn declaration_declarator<'tree>(
14335    declaration: Node<'tree>,
14336    child: Node<'tree>,
14337) -> Option<Node<'tree>> {
14338    if !matches!(
14339        declaration.kind(),
14340        "declaration"
14341            | "field_declaration"
14342            | "parameter_declaration"
14343            | "optional_parameter_declaration"
14344            | "function_definition"
14345            | "type_definition"
14346            | "alias_declaration"
14347            | "template_instantiation"
14348    ) {
14349        return None;
14350    }
14351    if declaration
14352        .child_by_field_name("type")
14353        .is_some_and(|type_node| same_node(type_node, child))
14354    {
14355        return None;
14356    }
14357    if child.kind() == "init_declarator" {
14358        return child.child_by_field_name("declarator");
14359    }
14360    let field = field_name_in_parent(declaration, child);
14361    if (is_declarator_node(child) && matches!(field, Some("declarator") | None))
14362        || (declaration.kind() == "type_definition"
14363            && child.kind() == "type_identifier"
14364            && matches!(field, Some("declarator") | None))
14365    {
14366        Some(child)
14367    } else {
14368        None
14369    }
14370}
14371
14372/// Read the name, declarator, and optional type of a typed local binding.
14373pub fn cpp_binding_parts<'tree>(
14374    node: Node<'tree>,
14375    source: &str,
14376) -> Option<(String, Node<'tree>, Option<Node<'tree>>)> {
14377    let declarator = node.child_by_field_name("declarator")?;
14378    let name = extract_variable_name(declarator, source)?;
14379    let type_node = node
14380        .child_by_field_name("type")
14381        .or_else(|| first_type_child(node));
14382    Some((name, declarator, type_node))
14383}
14384
14385/// One run of a container's children whose parsed namespaces differ from
14386/// their lexical namespaces, with the complete lexical namespace path.
14387#[derive(Clone, Debug, PartialEq, Eq)]
14388pub struct RecoveredNamespaceRegion {
14389    /// Start byte of the first child in the run.
14390    pub start: usize,
14391    /// End byte of the last child in the run.
14392    pub end: usize,
14393    /// The complete enclosing namespace path of the run, outermost first.
14394    /// This can be empty when a parsed namespace extends past its real close.
14395    pub components: Vec<String>,
14396    /// The `namespace Name { ... }` construct that opens each of `components`,
14397    /// parallel to it, from the `namespace` keyword through the close the index
14398    /// proved for its `{`. A proven surviving namespace definition uses its
14399    /// body's direct close; a recovered head, or a namespace body exposed by
14400    /// malformed member recovery, uses the file-global brace stack. A level
14401    /// whose close the index could not prove -- the file never closes it, or
14402    /// the file's brace token stream lost an opening token (#3328) -- carries
14403    /// the head alone. This is the range the declaration walk records for the
14404    /// Module, so a namespace whose head collapsed into an `ERROR` reports
14405    /// where it is written rather than where its first member is (#3309).
14406    pub component_ranges: Vec<Range>,
14407    /// The name token each of `components` is written as, parallel to it. The
14408    /// declaration walk interns this subspan as the Module's name occurrence:
14409    /// the node the walk is visiting when it declares the level belongs to an
14410    /// unrelated construct inside the region, and deriving the name from it
14411    /// paired the level's range with that construct's name token (#4292).
14412    pub component_name_ranges: Vec<Range>,
14413    /// The close the index proved for each level's `{`, parallel to
14414    /// `components`. A node after that close is outside the level (#3633).
14415    /// `None` when the file never closes the level or the walk proved no
14416    /// pairing in the file (#3328); the level then applies to the whole region
14417    /// as a best-effort scope.
14418    pub component_closes: Vec<Option<Range>>,
14419}
14420
14421/// The namespaces C++ parse recovery drops from a file's tree.
14422///
14423/// When tree-sitter cannot parse a construct inside a namespace body it closes
14424/// an inner scope with a MISSING brace, or skips an opening brace into an
14425/// ERROR node. Every real `}` after that then closes one scope too early: a
14426/// class body's `}` closes the namespace, the namespace's own `}` closes its
14427/// parent, and the outermost real closes land in a trailing ERROR node. The
14428/// declarations between a stolen close and the real one keep their byte
14429/// positions but lose their `namespace_definition` ancestors (Catch2's
14430/// `catch_matchers_templated.hpp`, issue #1537).
14431///
14432/// A file-global stack over real AST brace tokens restores both lost and
14433/// overextended namespaces. Missing braces do nothing; error-free subtrees are
14434/// balanced and can be skipped. For each child of a damaged container, compare
14435/// the namespaces still open on that stack with the parsed ancestor path. A
14436/// difference yields a region carrying the complete lexical path, including
14437/// an empty path when recovery swallowed file-scope declarations. Consecutive
14438/// children sharing that path form one [`RecoveredNamespaceRegion`]. The same
14439/// pass retains matching brace positions for declaration partitioning (#3087).
14440/// A file without parse errors needs neither correction nor a brace index.
14441///
14442/// The stack restores the source's nesting only while the tree's brace tokens
14443/// are the source's braces. Severe recovery can break that even when the real
14444/// brace counts balance: in simdjson's `compile_time_json-inl.h`, ABI 15 keeps
14445/// the reflection case labels as `character` nodes but loses the outer
14446/// namespace's closing token. The global stack then cascades the function and
14447/// inner namespace closes into the wrong owners. A surviving
14448/// `namespace_definition` can supply stronger provenance when its body has a
14449/// genuine declaration boundary: its direct real `}` belongs to that
14450/// namespace, while a missing direct close proves that the namespace has no
14451/// close to claim. A body whose direct boundary is itself recovered from a
14452/// class/member error stays with the global fallback. The proven boundaries
14453/// override the global stack only when the file has no unpaired real close. An
14454/// unpaired close proves the stream lost an open at a position the stack cannot
14455/// recover, so all pairing in that file remains invalid:
14456/// [`Self::matching_close_brace`] then answers nothing and each restored level
14457/// keeps the head that writes it. The namespace paths remain, as the
14458/// best-effort structure recovery they are.
14459///
14460/// Every consumer reads a node's namespace path through one authority rule,
14461/// shared by [`Self::enclosing_namespace_levels`] and
14462/// [`Self::restore_enclosing_namespaces`], so declaration collection and both
14463/// lookup directions agree:
14464///
14465/// - Outside every region, the node's parsed `namespace_definition` chain is
14466///   the whole answer.
14467/// - Inside a region, the region's restored levels come first and only the
14468///   parsed ancestors that start inside the region follow them. The region
14469///   overrides a parsed ancestor that starts before it, which is how a
14470///   namespace the tree kept open past its real close gives up the
14471///   declarations after that close (#3087).
14472/// - A restored level stops applying at the close the stack paired with its
14473///   `{`, and so does every level inside it. The walk compares a child with
14474///   the stack before it descends into that child, so a run can reach past a
14475///   close inside its last child. In simdjson's `tests/dom/basictests.cpp` the
14476///   collapsed `namespace type_tests {` on line 1467 closes on line 1811, and
14477///   recovery put that `}` inside a `preproc_else` that runs to line 2505: the
14478///   region that restores `type_tests` covers the whole `preproc_else`, and
14479///   without this check every namespace written after the close inherited
14480///   `type_tests` (#3633). A level whose close the stack did not prove applies
14481///   to the whole region, the best-effort answer #3328 leaves.
14482/// - A region whose restored path is empty keeps exactly the parsed ancestors
14483///   whose own `{` and `}` are both real tokens, wherever they start, and drops
14484///   the others. This holds whether or not the file's pairing is proven.
14485#[derive(Clone, Debug, Default)]
14486pub struct OrphanedNamespaceScopeIndex {
14487    regions: Vec<RecoveredNamespaceRegion>,
14488    /// Open-brace start byte to the close the file-global stack paired with it.
14489    /// Empty when the walk proved the brace token stream incomplete.
14490    brace_closes: HashMap<usize, Range>,
14491}
14492
14493impl OrphanedNamespaceScopeIndex {
14494    pub fn build(root: Node<'_>, source: &str) -> Self {
14495        if !root.has_error() {
14496            return Self::default();
14497        }
14498        /// One namespace level open on the brace stack: the `namespace Name {`
14499        /// head it is written as, and the `{`'s start byte, which is the
14500        /// `brace_closes` key its close lands under. The close is not known
14501        /// while the level is open, so the final ranges are assembled once the
14502        /// walk has paired every brace.
14503        #[derive(Clone, Copy)]
14504        struct OpenHead {
14505            head: Range,
14506            open: usize,
14507        }
14508        /// A [`RecoveredNamespaceRegion`] whose levels still name their open
14509        /// braces rather than their closes.
14510        struct PendingRegion {
14511            start: usize,
14512            end: usize,
14513            components: Vec<String>,
14514            heads: Vec<OpenHead>,
14515            name_ranges: Vec<Range>,
14516        }
14517        struct Frame<'tree> {
14518            node: Node<'tree>,
14519            children: Vec<Node<'tree>>,
14520            next: usize,
14521            parsed_scope: Vec<String>,
14522            run: Option<PendingRegion>,
14523        }
14524        fn frame<'tree>(
14525            node: Node<'tree>,
14526            mut parsed_scope: Vec<String>,
14527            source: &str,
14528        ) -> Frame<'tree> {
14529            if node.kind() == "namespace_definition"
14530                && let Some(name) = node.child_by_field_name("name")
14531            {
14532                let mut components = Vec::new();
14533                if append_cpp_name_components(name, source, &mut components).is_some() {
14534                    parsed_scope.extend(components);
14535                }
14536            }
14537            let mut cursor = node.walk();
14538            Frame {
14539                node,
14540                children: node.children(&mut cursor).collect(),
14541                next: 0,
14542                parsed_scope,
14543                run: None,
14544            }
14545        }
14546        let mut regions = Vec::new();
14547        let mut brace_closes = HashMap::default();
14548        // A surviving namespace_definition may own a direct brace boundary of
14549        // its declaration_list. Keep that evidence separate from the file-
14550        // global stack: recovery can lose an outer namespace close while
14551        // retaining balanced braces from nested constructs, so token order
14552        // alone can assign a real close to the wrong namespace. The helper
14553        // rejects body boundaries exposed by malformed class/member recovery.
14554        let mut namespace_brace_proofs = Vec::new();
14555        // Real closes that arrived with nothing open. Each one proves the tree
14556        // is missing a scope-opening token the source writes, which is what
14557        // makes every pairing in the file unusable as an extent (#3328). The
14558        // complete set is kept, not a count, so a walk that has to be diagnosed
14559        // shows every position the stream went out of sync.
14560        let mut unpaired_closes: Vec<Range> = Vec::new();
14561        // The brace stack belongs to the file, not to a parser frame. A real
14562        // close inside a damaged child can close its parent's namespace. The
14563        // following children must see that removal immediately (#3087).
14564        let mut open = Vec::new();
14565        let mut lexical_scope = Vec::new();
14566        // One head per entry of `lexical_scope`: `namespace a::b {` opens two
14567        // levels and both are written by that one head, exactly as the
14568        // ordinary namespace visit records one range for every level of the
14569        // C++17 shorthand (#1878).
14570        let mut lexical_heads: Vec<OpenHead> = Vec::new();
14571        // One name token per entry of `lexical_scope`: unlike the shared head,
14572        // each level of a `namespace a::b {` shorthand has its own token.
14573        let mut lexical_name_ranges: Vec<Range> = Vec::new();
14574        let mut frames = vec![frame(root, Vec::new(), source)];
14575        // A frame's node is the previous frame's direct child, so the frame
14576        // below answers what Node::parent would without re-descending from the
14577        // root; per-node parent or sibling climbs are quadratic over a wide
14578        // translation unit (#3141).
14579        while !frames.is_empty() {
14580            let parent_node = frames.len().checked_sub(2).map(|index| frames[index].node);
14581            let current = frames.last_mut().expect("frames is non-empty");
14582            if current.next == current.children.len() {
14583                regions.extend(frames.pop().expect("the frame just borrowed").run);
14584                continue;
14585            }
14586            let child = current.children[current.next];
14587            current.next += 1;
14588            match child.kind() {
14589                "{" if !child.is_missing() => {
14590                    regions.extend(current.run.take());
14591                    if let Some(namespace) = parent_node.filter(|parent| {
14592                        parent.kind() == "namespace_definition"
14593                            && parent.child_by_field_name("body") == Some(current.node)
14594                            && child.start_byte() == current.node.start_byte()
14595                    }) && let Some(proof) = namespace_body_brace_proof(current.node)
14596                    {
14597                        namespace_brace_proofs.push((child.start_byte(), namespace, proof));
14598                    }
14599                    // The head is the construct that writes the namespace: the
14600                    // `namespace_definition` node when one survived, and the
14601                    // bare `namespace` keyword when recovery collapsed the head
14602                    // into flat `ERROR` children (#3084).
14603                    let mut named = parent_node.and_then(|parent| {
14604                        namespace_body_name_components(parent, current.node, source)
14605                    });
14606                    if named.is_none() {
14607                        named = recovered_namespace_open_components(
14608                            &current.children[..current.next - 1],
14609                            source,
14610                        );
14611                    }
14612                    open.push((child.start_byte(), lexical_scope.len()));
14613                    if let Some((written_at, components)) = named {
14614                        let head = OpenHead {
14615                            head: Range {
14616                                start_byte: written_at.start_byte(),
14617                                end_byte: child.end_byte(),
14618                                start_line: written_at.start_position().row + 1,
14619                                end_line: child.end_position().row + 1,
14620                            },
14621                            open: child.start_byte(),
14622                        };
14623                        lexical_heads.extend(std::iter::repeat_n(head, components.len()));
14624                        lexical_name_ranges
14625                            .extend(components.iter().map(|(_, name_range)| *name_range));
14626                        lexical_scope
14627                            .extend(components.into_iter().map(|(component, _)| component));
14628                    }
14629                    continue;
14630                }
14631                "}" if !child.is_missing() => {
14632                    regions.extend(current.run.take());
14633                    let close = Range {
14634                        start_byte: child.start_byte(),
14635                        end_byte: child.end_byte(),
14636                        start_line: child.start_position().row + 1,
14637                        end_line: child.end_position().row + 1,
14638                    };
14639                    match open.pop() {
14640                        Some((start, namespace_len)) => {
14641                            debug_assert!(
14642                                start < close.start_byte,
14643                                "a close pairs with an open before it: {start} then {close:?}"
14644                            );
14645                            lexical_scope.truncate(namespace_len);
14646                            lexical_heads.truncate(namespace_len);
14647                            lexical_name_ranges.truncate(namespace_len);
14648                            brace_closes.insert(start, close);
14649                        }
14650                        None => unpaired_closes.push(close),
14651                    }
14652                    continue;
14653                }
14654                _ => {}
14655            }
14656            // The namespace head and body precede their opening brace; their
14657            // own parsed namespace is not yet on the lexical stack. Compare
14658            // the items inside the body, not this header bookkeeping.
14659            if current.node.kind() != "namespace_definition"
14660                && lexical_scope != current.parsed_scope
14661            {
14662                match &mut current.run {
14663                    Some(run) if run.components == lexical_scope => run.end = child.end_byte(),
14664                    run => {
14665                        regions.extend(run.take());
14666                        *run = Some(PendingRegion {
14667                            start: child.start_byte(),
14668                            end: child.end_byte(),
14669                            components: lexical_scope.clone(),
14670                            heads: lexical_heads.clone(),
14671                            name_ranges: lexical_name_ranges.clone(),
14672                        });
14673                    }
14674                }
14675            } else {
14676                regions.extend(current.run.take());
14677            }
14678            // Error-free subtrees are balanced. Keeping them intact also
14679            // leaves their valid nested namespaces to the ordinary ancestry
14680            // walk in restore_enclosing_namespaces.
14681            if child.has_error() {
14682                let parsed_scope = current.parsed_scope.clone();
14683                frames.push(frame(child, parsed_scope, source));
14684            }
14685        }
14686        // A close with nothing open is only possible when a `{` the source
14687        // writes is not a `{` token of the tree. The stack then paired some
14688        // earlier open with a close that belongs to an inner scope, and
14689        // nothing in the token stream says which pairs absorbed the loss: the
14690        // lost token sits at an unknown position before the close that
14691        // exposed it. Keep no pairing rather than an unprovable extent. An
14692        // open the file never closes is the opposite case and stays: it makes
14693        // no claim, so the levels it writes keep their heads either way.
14694        if !unpaired_closes.is_empty() {
14695            brace_closes.clear();
14696        } else {
14697            // Use AST ownership when the global walk has no unpaired close. A missing
14698            // direct close removes a misleading stack pairing; a real direct
14699            // close replaces any cascade from a damaged descendant. Apply this
14700            // only to the namespace chain containing a proven missing boundary;
14701            // unrelated or ambiguous namespaces retain the global fallback.
14702            let missing_namespaces = namespace_brace_proofs
14703                .iter()
14704                .filter_map(|(_, namespace, proof)| {
14705                    matches!(proof, NamespaceBodyBraceProof::Missing).then_some(namespace.id())
14706                })
14707                .collect::<HashSet<_>>();
14708            for (open, namespace, proof) in namespace_brace_proofs {
14709                if !std::iter::successors(Some(namespace), |node| node.parent())
14710                    .any(|ancestor| missing_namespaces.contains(&ancestor.id()))
14711                {
14712                    continue;
14713                }
14714                match proof {
14715                    NamespaceBodyBraceProof::Real(close) => {
14716                        brace_closes.insert(open, close);
14717                    }
14718                    NamespaceBodyBraceProof::Missing => {
14719                        brace_closes.remove(&open);
14720                    }
14721                }
14722            }
14723        }
14724        // Every surviving brace is paired now, so each level's head can grow to
14725        // the construct it writes. A level whose `{` never closes keeps its
14726        // head: the file ends inside the namespace and there is no real close
14727        // to reach for.
14728        let regions = regions
14729            .into_iter()
14730            .map(|region| {
14731                debug_assert_eq!(
14732                    region.components.len(),
14733                    region.heads.len(),
14734                    "every recovered namespace level carries the head that writes it: {:?}",
14735                    region.components
14736                );
14737                debug_assert_eq!(
14738                    region.components.len(),
14739                    region.name_ranges.len(),
14740                    "every recovered namespace level carries the name token that writes it: {:?}",
14741                    region.components
14742                );
14743                let component_closes: Vec<_> = region
14744                    .heads
14745                    .iter()
14746                    .map(|level| brace_closes.get(&level.open).copied())
14747                    .collect();
14748                let component_ranges = region
14749                    .heads
14750                    .iter()
14751                    .zip(&component_closes)
14752                    .map(|(level, close)| match close {
14753                        Some(close) => Range {
14754                            start_byte: level.head.start_byte,
14755                            end_byte: close.end_byte,
14756                            start_line: level.head.start_line,
14757                            end_line: close.end_line,
14758                        },
14759                        None => level.head,
14760                    })
14761                    .collect();
14762                RecoveredNamespaceRegion {
14763                    start: region.start,
14764                    end: region.end,
14765                    components: region.components,
14766                    component_ranges,
14767                    component_name_ranges: region.name_ranges,
14768                    component_closes,
14769                }
14770            })
14771            .collect();
14772        Self {
14773            regions,
14774            brace_closes,
14775        }
14776    }
14777
14778    /// The real AST brace matching an opening brace in a damaged subtree.
14779    /// Missing tokens and braces inside comments or literals do not participate.
14780    ///
14781    /// `None` when this open has no close in the file, and for every open in a
14782    /// file whose brace token stream lost an opening token: a caller cannot
14783    /// treat a pairing the walk could not prove as an enclosing extent (#3328).
14784    pub fn matching_close_brace(&self, open: usize) -> Option<Range> {
14785        self.brace_closes.get(&open).copied()
14786    }
14787
14788    pub fn is_empty(&self) -> bool {
14789        self.regions.is_empty()
14790    }
14791
14792    /// The bytes this index holds, for the analyzer cache's weight.
14793    pub fn approximate_size(&self) -> usize {
14794        self.regions.iter().fold(
14795            self.brace_closes.len() * std::mem::size_of::<(usize, Range)>(),
14796            |total, region| {
14797                total
14798                    .saturating_add(std::mem::size_of::<RecoveredNamespaceRegion>())
14799                    .saturating_add(region.components.iter().map(String::len).sum::<usize>())
14800                    .saturating_add(region.component_ranges.len() * std::mem::size_of::<Range>())
14801                    .saturating_add(
14802                        region.component_name_ranges.len() * std::mem::size_of::<Range>(),
14803                    )
14804                    .saturating_add(
14805                        region.component_closes.len() * std::mem::size_of::<Option<Range>>(),
14806                    )
14807            },
14808        )
14809    }
14810
14811    /// The innermost recovered region containing `byte`.
14812    pub fn region_at(&self, byte: usize) -> Option<&RecoveredNamespaceRegion> {
14813        self.regions
14814            .iter()
14815            .filter(|region| region.start <= byte && byte < region.end)
14816            .min_by_key(|region| region.end - region.start)
14817    }
14818
14819    /// The enclosing namespaces of `node`, outermost first, restoring the ones
14820    /// parse recovery dropped from its ancestor chain. The one answer both
14821    /// lookup directions and declaration collection use (issue #1537).
14822    pub fn enclosing_namespace_components(&self, node: Node<'_>, source: &str) -> Vec<String> {
14823        self.enclosing_namespace_levels(node, source)
14824            .into_iter()
14825            .map(|(component, ..)| component)
14826            .collect()
14827    }
14828
14829    /// [`Self::enclosing_namespace_components`] with the construct that writes
14830    /// each level and the level's own name token, for the declaration walk
14831    /// that has to record both ranges.
14832    ///
14833    /// A level the brace stack restored carries the ranges this index recorded
14834    /// for its head and name; a surviving `namespace_definition` ancestor
14835    /// carries its own node range and name token, which is what the ordinary
14836    /// namespace visit records.
14837    pub fn enclosing_namespace_levels(
14838        &self,
14839        node: Node<'_>,
14840        source: &str,
14841    ) -> Vec<(String, Range, Range)> {
14842        let mut parsed = Vec::new();
14843        let mut current = node.parent();
14844        while let Some(parent) = current {
14845            if parent.kind() == "namespace_definition"
14846                && let Some(name) = parent.child_by_field_name("name")
14847                && let Some(components) = cpp_name_component_ranges(name, source)
14848            {
14849                let range = node_range(parent);
14850                parsed.push((
14851                    parent.start_byte(),
14852                    components
14853                        .into_iter()
14854                        .map(|(component, name_range)| (component, range, name_range))
14855                        .collect(),
14856                    namespace_definition_has_real_braces(parent),
14857                ));
14858            }
14859            current = parent.parent();
14860        }
14861        parsed.reverse();
14862        self.splice_recovered_path(parsed, node.start_byte(), |region, active_components| {
14863            region
14864                .components
14865                .iter()
14866                .take(active_components)
14867                .cloned()
14868                .zip(
14869                    region
14870                        .component_ranges
14871                        .iter()
14872                        .take(active_components)
14873                        .copied(),
14874                )
14875                .zip(
14876                    region
14877                        .component_name_ranges
14878                        .iter()
14879                        .take(active_components)
14880                        .copied(),
14881                )
14882                .map(|((component, range), name_range)| (component, range, name_range))
14883                .collect()
14884        })
14885    }
14886
14887    /// [`Self::enclosing_namespace_components`] for a caller that has already
14888    /// climbed the ancestor chain: `parsed` lists the node's named
14889    /// `namespace_definition` ancestors outermost first, each with its start
14890    /// byte and whether its own opening and closing braces survived parsing.
14891    /// The answer follows the authority rule on [`OrphanedNamespaceScopeIndex`].
14892    pub fn restore_enclosing_namespaces(
14893        &self,
14894        parsed: Vec<(usize, Vec<String>, bool)>,
14895        node_start: usize,
14896    ) -> Vec<String> {
14897        self.splice_recovered_path(parsed, node_start, |region, active_components| {
14898            region
14899                .components
14900                .iter()
14901                .take(active_components)
14902                .cloned()
14903                .collect()
14904        })
14905    }
14906
14907    /// Splice a node's parsed namespace ancestors onto the path a recovered
14908    /// region restores, by the authority rule on [`OrphanedNamespaceScopeIndex`].
14909    /// `recovered` receives the region and the number of its outermost levels
14910    /// still open at the node, and reads those levels in whatever shape the
14911    /// caller needs.
14912    fn splice_recovered_path<T>(
14913        &self,
14914        parsed: Vec<(usize, Vec<T>, bool)>,
14915        node_start: usize,
14916        recovered: impl FnOnce(&RecoveredNamespaceRegion, usize) -> Vec<T>,
14917    ) -> Vec<T> {
14918        let Some(region) = self.region_at(node_start) else {
14919            return parsed
14920                .into_iter()
14921                .flat_map(|(_, levels, _)| levels)
14922                .collect();
14923        };
14924        debug_assert_eq!(
14925            region.components.len(),
14926            region.component_closes.len(),
14927            "every recovered namespace component carries its close proof: {region:?}"
14928        );
14929        debug_assert_eq!(
14930            region.components.len(),
14931            region.component_name_ranges.len(),
14932            "every recovered namespace component carries its name token: {region:?}"
14933        );
14934        if region.components.is_empty() {
14935            return parsed
14936                .into_iter()
14937                .filter(|(_, _, complete)| *complete)
14938                .flat_map(|(_, levels, _)| levels)
14939                .collect();
14940        }
14941        let active_components = region
14942            .component_closes
14943            .iter()
14944            .take_while(|close| {
14945                close
14946                    .as_ref()
14947                    .is_none_or(|close| node_start < close.end_byte)
14948            })
14949            .count();
14950        recovered(region, active_components)
14951            .into_iter()
14952            .chain(
14953                parsed
14954                    .into_iter()
14955                    .filter(|(start, _, _)| *start >= region.start)
14956                    .flat_map(|(_, levels, _)| levels),
14957            )
14958            .collect()
14959    }
14960}
14961
14962/// Whether a parsed namespace owns real AST tokens for both of its boundary
14963/// braces. Recovery may damage its body while leaving this boundary usable as
14964/// lexical ownership evidence.
14965pub(super) fn namespace_definition_has_real_braces(namespace: Node<'_>) -> bool {
14966    debug_assert_eq!(namespace.kind(), "namespace_definition");
14967    let Some(body) = namespace.child_by_field_name("body") else {
14968        return false;
14969    };
14970    let mut opening = false;
14971    let mut closing = false;
14972    let mut cursor = body.walk();
14973    for child in body.children(&mut cursor) {
14974        match child.kind() {
14975            "{" if !child.is_missing() => opening = true,
14976            "}" if !child.is_missing() => closing = true,
14977            _ => {}
14978        }
14979    }
14980    opening && closing
14981}
14982
14983/// The `namespace` keyword and name components of the namespace a stray `{`
14984/// opens, or `None` when the brace does not follow a namespace head.
14985///
14986/// When a namespace body holds a construct tree-sitter cannot parse, recovery
14987/// can collapse the whole `namespace Name { ... }` into one `ERROR` instead of
14988/// a `namespace_definition`: the head survives as the node's own `namespace`
14989/// keyword, name and `{` tokens, in that order, and everything the namespace
14990/// declares becomes a flat sibling of them (Catch2's `catch_decomposer.hpp`,
14991/// issue #3084). Read the head from those siblings so the brace stack names
14992/// the scope the brace opens. An anonymous namespace has no representable
14993/// name and keeps an opaque scope.
14994///
14995/// `preceding` is the open brace's preceding siblings, nearest last. The
14996/// caller already holds the container's child list; `Node::prev_sibling`
14997/// would re-descend from the root for each step (#3141).
14998fn recovered_namespace_open_components<'tree>(
14999    preceding: &[Node<'tree>],
15000    source: &str,
15001) -> Option<(Node<'tree>, Vec<(String, Range)>)> {
15002    let mut head = Vec::new();
15003    for &sibling in preceding.iter().rev() {
15004        if sibling.kind() != "comment" {
15005            head.push(sibling);
15006            if head.len() == 2 {
15007                break;
15008            }
15009        }
15010    }
15011    let [name, keyword] = head[..] else {
15012        return None;
15013    };
15014    if keyword.kind() != "namespace" {
15015        return None;
15016    }
15017    let components = cpp_name_component_ranges(name, source)?;
15018    (!components.is_empty()).then_some((keyword, components))
15019}
15020
15021/// The `namespace_definition` `parent` and the name components with their
15022/// token ranges of the namespace whose body `body` is, or `None` when `body`
15023/// is not the body of a named `namespace_definition`.
15024fn namespace_body_name_components<'tree>(
15025    parent: Node<'tree>,
15026    body: Node<'tree>,
15027    source: &str,
15028) -> Option<(Node<'tree>, Vec<(String, Range)>)> {
15029    if body.kind() != "declaration_list"
15030        || parent.kind() != "namespace_definition"
15031        || parent.child_by_field_name("body") != Some(body)
15032    {
15033        return None;
15034    }
15035    let name = parent.child_by_field_name("name")?;
15036    let components = cpp_name_component_ranges(name, source)?;
15037    (!components.is_empty()).then_some((parent, components))
15038}
15039
15040#[derive(Clone, Copy)]
15041enum NamespaceBodyBraceProof {
15042    Real(Range),
15043    Missing,
15044}
15045
15046/// Prove a namespace body's boundary from its direct AST children. Require the
15047/// body's boundary as well as direct ownership, so a stray brace inside a
15048/// recovered body cannot become namespace ownership evidence. Unknown is
15049/// distinct from a proven missing token: unknown leaves the global pairing in
15050/// place, while a direct MISSING `}` removes a misleading pairing.
15051fn namespace_body_brace_proof(body: Node<'_>) -> Option<NamespaceBodyBraceProof> {
15052    if body.kind() != "declaration_list" {
15053        return None;
15054    }
15055    let mut cursor = body.walk();
15056    let children = body.children(&mut cursor).collect::<Vec<_>>();
15057    let close_index = children
15058        .iter()
15059        .rposition(|child| child.kind() == "}" && child.end_byte() == body.end_byte())?;
15060    let close = children[close_index];
15061    if close.is_missing() {
15062        return Some(NamespaceBodyBraceProof::Missing);
15063    }
15064    let namespace_terminator_sibling = body
15065        .parent()
15066        .filter(|namespace| namespace.kind() == "namespace_definition")
15067        .and_then(|namespace| namespace.next_sibling())
15068        .is_some_and(|sibling| {
15069            sibling.kind() == ";"
15070                || (sibling.kind() == "expression_statement"
15071                    && sibling.child_count() == 1
15072                    && sibling.child(0).is_some_and(|child| child.kind() == ";"))
15073        });
15074    // A recovered class close can become the namespace body boundary while
15075    // its class terminator remains as a sibling semicolon. A malformed class
15076    // can also leak its final expression or label directly into the namespace
15077    // body. Neither shape proves that this `}` belongs to the namespace.
15078    if namespace_terminator_sibling
15079        || children
15080            .get(close_index + 1)
15081            .is_some_and(|sibling| sibling.kind() == ";")
15082        || children[..close_index]
15083            .iter()
15084            .rev()
15085            .find(|child| !child.is_extra() && child.kind() != "{")
15086            .is_some_and(|previous| {
15087                matches!(
15088                    previous.kind(),
15089                    "expression_statement" | "labeled_statement"
15090                )
15091            })
15092    {
15093        return None;
15094    }
15095    Some(NamespaceBodyBraceProof::Real(node_range(close)))
15096}
15097
15098#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15099pub enum RecoveredDeclaratorTypeContext {
15100    Declaration,
15101    FunctionDefinition,
15102    Parameter,
15103}
15104
15105/// Recognize a real type displaced into a qualified declarator by parser
15106/// recovery.
15107///
15108/// Tree-sitter parses `API Result *make(Arg);` as if `API` were the declared
15109/// type and `Result` were the scope of a qualified declarator with a missing
15110/// `::`. A template return such as `API Result<T> make()` uses a
15111/// `template_type` for the same recovered scope. The same recovery occurs for
15112/// macro-prefixed definitions, extern variables, and macro-decorated
15113/// parameters (`f(MACRO T* p)`, where the parameter's own `type` field takes
15114/// the macro). Keep this intentionally structural: the recovered scope must
15115/// have the grammar's missing separator, the qualified node must occupy the
15116/// declaration's declarator chain, a separate nonempty type must occupy the
15117/// normal type field, and the recovered name must unwrap to a real declarator
15118/// name.
15119pub fn recovered_macro_decorated_declarator_type(
15120    node: Node<'_>,
15121) -> Option<RecoveredDeclaratorTypeContext> {
15122    recovered_macro_decorated_type_node(node).map(|(_, context)| context)
15123}
15124
15125/// Return the declaration/function `type` displaced by a macro-shaped
15126/// qualified declarator, together with the enclosing declaration context.
15127/// Callers use the macro scope only as structural admission evidence; the
15128/// returned node is the real type reference to resolve and record.
15129pub fn recovered_macro_decorated_type_node(
15130    node: Node<'_>,
15131) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
15132    if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
15133        return None;
15134    }
15135    let qualified = node.parent()?;
15136    if qualified.kind() != "qualified_identifier"
15137        || qualified.child_by_field_name("scope") != Some(node)
15138        || !(0..qualified.child_count())
15139            .filter_map(|index| qualified.child(index))
15140            .any(|child| child.kind() == "::" && child.is_missing())
15141    {
15142        return None;
15143    }
15144    if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
15145        return None;
15146    }
15147
15148    let (declaration, context) = recovered_declarator_container(qualified)?;
15149    let type_node = declaration
15150        .child_by_field_name("type")
15151        .filter(|type_node| {
15152            *type_node != qualified
15153                && !type_node.is_missing()
15154                && type_node.start_byte() != type_node.end_byte()
15155        })?;
15156    Some((type_node, context))
15157}
15158
15159fn recovered_declarator_container(
15160    mut declarator: Node<'_>,
15161) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
15162    loop {
15163        let parent = declarator.parent()?;
15164        if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
15165            return Some((
15166                parent
15167                    .parent()
15168                    .filter(|declaration| declaration.kind() == "declaration")?,
15169                RecoveredDeclaratorTypeContext::Declaration,
15170            ));
15171        }
15172        if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
15173            return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
15174        }
15175        if parent.kind() == "function_definition"
15176            && has_field_child(parent, "declarator", declarator)
15177        {
15178            return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
15179        }
15180        // `f(MACRO T* p)` recovers exactly like `MACRO T *make(...)` does, one
15181        // level down: the parameter's `type` field takes the macro token and
15182        // the real type `T` becomes the recovered scope of the declarator.
15183        // Declining here left every xxhash `XXH_NOESCAPE` parameter with no
15184        // candidate at all (#1830).
15185        if matches!(
15186            parent.kind(),
15187            "parameter_declaration" | "optional_parameter_declaration"
15188        ) && has_field_child(parent, "declarator", declarator)
15189        {
15190            return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
15191        }
15192        if !matches!(
15193            parent.kind(),
15194            "array_declarator"
15195                | "function_declarator"
15196                | "parenthesized_declarator"
15197                | "pointer_declarator"
15198                | "pointer_type_declarator"
15199                | "reference_declarator"
15200        ) || !has_field_child(parent, "declarator", declarator)
15201        {
15202            return None;
15203        }
15204        declarator = parent;
15205    }
15206}
15207
15208fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
15209    brokk_bifrost_core::analyzer::structural::adapter_helpers::is_field_of(parent, target, field)
15210}
15211
15212fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
15213    loop {
15214        if node.is_missing() || node.start_byte() == node.end_byte() {
15215            return false;
15216        }
15217        match node.kind() {
15218            "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
15219                return true;
15220            }
15221            "array_declarator"
15222            | "function_declarator"
15223            | "parenthesized_declarator"
15224            | "pointer_declarator"
15225            | "pointer_type_declarator"
15226            | "reference_declarator" => {
15227                let Some(declarator) = node.child_by_field_name("declarator") else {
15228                    return false;
15229                };
15230                node = declarator;
15231            }
15232            _ => return false,
15233        }
15234    }
15235}
15236
15237/// Aggregate-owner proof for a structurally recognized designated initializer.
15238pub enum DesignatedInitializerOwner {
15239    Resolved(CodeUnit),
15240    Unresolved,
15241}
15242
15243enum InitializerOwnerStep {
15244    Field(String),
15245    AggregateWrapper,
15246}
15247
15248/// Recognize a designated-initializer field and, when possible, resolve its
15249/// aggregate owner.
15250///
15251/// Covers both the grammar's ordinary `field_designator` shape and the exact
15252/// recovery used for `.field = value` after a preprocessor-split array
15253/// initializer. Ordered multi-component designators follow each preceding
15254/// field's declared aggregate type. `None` means the node is not a designator
15255/// at all; an unresolved designator remains classified so callers cannot fall
15256/// through to unrelated global/member heuristics.
15257pub fn designated_initializer_owner(
15258    analyzer: &CppGraphSource<'_>,
15259    visibility: &VisibilityIndex<'_>,
15260    file: &ProjectFile,
15261    source: &str,
15262    node: Node<'_>,
15263) -> Option<DesignatedInitializerOwner> {
15264    if let Some(designator) = node
15265        .parent()
15266        .filter(|parent| parent.kind() == "field_designator")
15267    {
15268        let pair = designator.parent()?;
15269        if pair.kind() != "initializer_pair" {
15270            return None;
15271        }
15272        let mut cursor = pair.walk();
15273        let designators = pair
15274            .children_by_field_name("designator", &mut cursor)
15275            .collect::<Vec<_>>();
15276        let position = designators
15277            .iter()
15278            .position(|candidate| same_node(*candidate, designator))?;
15279        let initializer = pair.parent()?;
15280        if initializer.kind() != "initializer_list" {
15281            return None;
15282        }
15283        let mut owner = initializer_list_owner(analyzer, visibility, file, source, initializer);
15284        for prior in &designators[..position] {
15285            let field = prior
15286                .child_by_field_name("field")
15287                .or_else(|| first_named_child_of_kind(*prior, "field_identifier"))?;
15288            owner = owner.and_then(|owner| {
15289                initializer_field_owner(analyzer, visibility, file, owner, node_text(field, source))
15290            });
15291        }
15292        return Some(classified_designated_owner(owner));
15293    }
15294
15295    let init_declarator = node.parent()?;
15296    if init_declarator.child_by_field_name("declarator") != Some(node)
15297        || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
15298    {
15299        return None;
15300    }
15301    Some(classified_designated_owner(declaration_owner(
15302        analyzer,
15303        visibility,
15304        file,
15305        source,
15306        init_declarator.parent()?,
15307    )))
15308}
15309
15310fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
15311    owner.map_or(
15312        DesignatedInitializerOwner::Unresolved,
15313        DesignatedInitializerOwner::Resolved,
15314    )
15315}
15316
15317fn initializer_list_owner(
15318    analyzer: &CppGraphSource<'_>,
15319    visibility: &VisibilityIndex<'_>,
15320    file: &ProjectFile,
15321    source: &str,
15322    initializer: Node<'_>,
15323) -> Option<CodeUnit> {
15324    let mut current = initializer;
15325    let mut steps = Vec::new();
15326    loop {
15327        let parent = current.parent()?;
15328        match parent.kind() {
15329            "initializer_pair" if parent.child_by_field_name("value") == Some(current) => {
15330                let designator = parent.child_by_field_name("designator")?;
15331                let step = designator
15332                    .child_by_field_name("field")
15333                    .or_else(|| first_named_child_of_kind(designator, "field_identifier"))
15334                    .map(|field| InitializerOwnerStep::Field(node_text(field, source).to_string()))
15335                    .unwrap_or(InitializerOwnerStep::AggregateWrapper);
15336                steps.push(step);
15337                current = parent.parent()?;
15338            }
15339            "initializer_list" => {
15340                current = parent;
15341            }
15342            "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
15343                let declaration = parent.parent()?;
15344                let owner = declaration_owner(analyzer, visibility, file, source, declaration)?;
15345                return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
15346            }
15347            "compound_literal_expression"
15348                if parent.child_by_field_name("value") == Some(current) =>
15349            {
15350                let type_node = parent.child_by_field_name("type")?;
15351                let owner =
15352                    resolve_designated_owner_type(analyzer, visibility, file, source, type_node)?;
15353                return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
15354            }
15355            "ERROR" => current = parent,
15356            _ => return None,
15357        }
15358    }
15359}
15360
15361fn apply_initializer_owner_steps(
15362    analyzer: &CppGraphSource<'_>,
15363    visibility: &VisibilityIndex<'_>,
15364    file: &ProjectFile,
15365    mut owner: CodeUnit,
15366    steps: Vec<InitializerOwnerStep>,
15367) -> Option<CodeUnit> {
15368    for step in steps.into_iter().rev() {
15369        if let InitializerOwnerStep::Field(field_name) = step {
15370            owner = initializer_field_owner(analyzer, visibility, file, owner, &field_name)?;
15371        }
15372    }
15373    Some(owner)
15374}
15375
15376fn initializer_field_owner(
15377    analyzer: &CppGraphSource<'_>,
15378    visibility: &VisibilityIndex<'_>,
15379    file: &ProjectFile,
15380    owner: CodeUnit,
15381    field_name: &str,
15382) -> Option<CodeUnit> {
15383    let fields = visibility
15384        .visible_members_for_owner_name(file, &owner, field_name)
15385        .into_iter()
15386        .filter(|field| field.is_field())
15387        .collect::<Vec<_>>();
15388    let field = match fields.as_slice() {
15389        [field] => *field,
15390        _ => return None,
15391    };
15392    field_declared_binding(analyzer, visibility, file, field)?.unit
15393}
15394
15395fn declaration_owner(
15396    analyzer: &CppGraphSource<'_>,
15397    visibility: &VisibilityIndex<'_>,
15398    file: &ProjectFile,
15399    source: &str,
15400    declaration: Node<'_>,
15401) -> Option<CodeUnit> {
15402    if !matches!(declaration.kind(), "declaration" | "field_declaration") {
15403        return None;
15404    }
15405    let type_node = declaration
15406        .child_by_field_name("type")
15407        .or_else(|| first_type_child(declaration))?;
15408    resolve_designated_owner_type(analyzer, visibility, file, source, type_node)
15409}
15410
15411fn resolve_designated_owner_type(
15412    analyzer: &CppGraphSource<'_>,
15413    visibility: &VisibilityIndex<'_>,
15414    file: &ProjectFile,
15415    source: &str,
15416    type_node: Node<'_>,
15417) -> Option<CodeUnit> {
15418    if let Some(owner) = anonymous_aggregate_owner(analyzer, file, type_node) {
15419        return Some(owner);
15420    }
15421    let type_name = normalize_type_text(node_text(type_node, source));
15422    visibility
15423        .resolve_type(file, &type_name)
15424        .filter(CodeUnit::is_class)
15425}
15426
15427pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
15428    let mut cursor = node.walk();
15429    node.named_children(&mut cursor).find(|child| {
15430        matches!(
15431            child.kind(),
15432            "type_identifier"
15433                | "primitive_type"
15434                | "qualified_identifier"
15435                | "scoped_type_identifier"
15436                | "struct_specifier"
15437                | "union_specifier"
15438                | "enum_specifier"
15439        )
15440    })
15441}
15442
15443pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
15444    visibility: &VisibilityIndex<'_>,
15445    file: &ProjectFile,
15446    source: &str,
15447    declarator: Node<'_>,
15448    type_text: Option<&str>,
15449    bindings: &LocalInferenceEngine<T>,
15450) -> bool {
15451    if !has_ancestor_kind(declarator, "compound_statement") {
15452        return false;
15453    }
15454    if declarator
15455        .child_by_field_name("declarator")
15456        .is_none_or(|declarator| declarator.kind() != "identifier")
15457    {
15458        return false;
15459    }
15460    if !type_text
15461        .and_then(|text| visibility.resolve_type(file, text))
15462        .is_some_and(|unit| unit.is_class())
15463    {
15464        return false;
15465    }
15466    declarator
15467        .child_by_field_name("parameters")
15468        .is_some_and(|parameters| {
15469            constructor_parameters_look_like_expressions(parameters, source, bindings)
15470        })
15471}
15472
15473fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
15474    parameters: Node<'_>,
15475    source: &str,
15476    bindings: &LocalInferenceEngine<T>,
15477) -> bool {
15478    let mut cursor = parameters.walk();
15479    parameters.named_children(&mut cursor).any(|parameter| {
15480        !matches!(
15481            parameter.kind(),
15482            "parameter_declaration" | "optional_parameter_declaration"
15483        ) || parameter_declaration_is_local_expression(parameter, source, bindings)
15484    })
15485}
15486
15487fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
15488    parameter: Node<'_>,
15489    source: &str,
15490    bindings: &LocalInferenceEngine<T>,
15491) -> bool {
15492    let text = node_text(parameter, source).trim();
15493    if text
15494        .chars()
15495        .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
15496        && bindings.is_shadowed(text)
15497    {
15498        return true;
15499    }
15500
15501    let Some(base) = parameter
15502        .child_by_field_name("type")
15503        .filter(|base| base.kind() == "type_identifier")
15504    else {
15505        return false;
15506    };
15507    let Some(subscript) = parameter
15508        .child_by_field_name("declarator")
15509        .filter(|declarator| declarator.kind() == "abstract_array_declarator")
15510    else {
15511        return false;
15512    };
15513    subscript.child_by_field_name("size").is_some()
15514        && bindings.is_shadowed(node_text(base, source).trim())
15515}
15516
15517pub fn is_declaration_name(node: Node<'_>) -> bool {
15518    let Some(parent) = node.parent() else {
15519        return false;
15520    };
15521    if parent
15522        .child_by_field_name("name")
15523        .is_some_and(|name| same_node(name, node))
15524    {
15525        if matches!(
15526            parent.kind(),
15527            "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
15528        ) {
15529            return cpp_tag_specifier_declares_name(parent);
15530        }
15531        if matches!(
15532            parent.kind(),
15533            "namespace_definition"
15534                | "namespace_alias_definition"
15535                | "alias_declaration"
15536                | "enumerator"
15537        ) {
15538            return true;
15539        }
15540    }
15541
15542    let mut current = Some(parent);
15543    while let Some(ancestor) = current {
15544        let type_definition = ancestor.kind() == "type_definition";
15545        let mut child_cursor = ancestor.walk();
15546        if ancestor.named_children(&mut child_cursor).any(|child| {
15547            declaration_declarator(ancestor, child).is_some_and(|declarator| {
15548                declarator_name_path_contains(declarator, node, type_definition)
15549            })
15550        }) {
15551            return true;
15552        }
15553        if matches!(
15554            ancestor.kind(),
15555            "declaration"
15556                | "field_declaration"
15557                | "parameter_declaration"
15558                | "optional_parameter_declaration"
15559                | "function_definition"
15560                | "type_definition"
15561                | "alias_declaration"
15562                | "template_instantiation"
15563                | "class_specifier"
15564                | "struct_specifier"
15565                | "union_specifier"
15566                | "enum_specifier"
15567        ) {
15568            return false;
15569        }
15570        current = ancestor.parent();
15571    }
15572    false
15573}
15574
15575/// Whether tree-sitter recovered a qualified friend-class type as an ordinary
15576/// declaration's declarator inside a malformed class body.
15577///
15578/// An export macro between `class` and the class name can make the containing
15579/// body parse as a function body. A source declaration such as
15580/// `friend class internal::Friend;` then retains this exact structure:
15581/// `declaration(type: friend, ERROR(class), declarator: internal::Friend)`.
15582/// The declarator is a type reference despite its field role.
15583pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
15584    if !matches!(
15585        node.kind(),
15586        "qualified_identifier" | "scoped_type_identifier"
15587    ) {
15588        return false;
15589    }
15590    let Some(declaration) = node
15591        .parent()
15592        .filter(|parent| parent.kind() == "declaration")
15593    else {
15594        return false;
15595    };
15596    if declaration.child_by_field_name("declarator") != Some(node)
15597        || !declaration
15598            .child_by_field_name("type")
15599            .is_some_and(|friend| {
15600                friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
15601            })
15602    {
15603        return false;
15604    }
15605    let mut cursor = declaration.walk();
15606    let mut errors = declaration
15607        .named_children(&mut cursor)
15608        .filter(|child| child.kind() == "ERROR");
15609    let Some(error) = errors.next() else {
15610        return false;
15611    };
15612    errors.next().is_none()
15613        && error.named_child_count() == 1
15614        && error.named_child(0).is_some_and(|class| {
15615            class.kind() == "identifier" && node_text(class, source) == "class"
15616        })
15617}
15618
15619pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
15620    if !matches!(node.kind(), "identifier" | "field_identifier") {
15621        return false;
15622    }
15623    if let Some(parent) = node.parent() {
15624        if parent.kind() == "call_expression"
15625            && parent.child_by_field_name("function") == Some(node)
15626        {
15627            return false;
15628        }
15629        if matches!(parent.kind(), "labeled_statement" | "goto_statement")
15630            && parent.child_by_field_name("label") == Some(node)
15631        {
15632            return false;
15633        }
15634    }
15635    if is_declaration_name(node) {
15636        return false;
15637    }
15638    let mut current = node.parent();
15639    while let Some(ancestor) = current {
15640        match ancestor.kind() {
15641            "preproc_ifdef" | "preproc_ifndef" => {
15642                if ancestor
15643                    .child_by_field_name("name")
15644                    .is_some_and(|name| node_range_contains(name, node))
15645                {
15646                    return false;
15647                }
15648            }
15649            "preproc_if" | "preproc_elif" => {
15650                if ancestor
15651                    .child_by_field_name("condition")
15652                    .is_some_and(|condition| node_range_contains(condition, node))
15653                {
15654                    return false;
15655                }
15656            }
15657            "preproc_else" => {}
15658            kind if kind.starts_with("preproc_") => return false,
15659            _ => {}
15660        }
15661        if matches!(
15662            ancestor.kind(),
15663            "translation_unit" | "function_definition" | "compound_statement"
15664        ) {
15665            break;
15666        }
15667        current = ancestor.parent();
15668    }
15669    true
15670}
15671
15672fn recovered_c_reference_node(
15673    visibility: &VisibilityIndex<'_>,
15674    file: &ProjectFile,
15675    node: Node<'_>,
15676    source: &str,
15677) -> bool {
15678    if node.start_byte() >= node.end_byte()
15679        || node.is_error()
15680        || node.is_missing()
15681        || !matches!(
15682            node.kind(),
15683            "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
15684        )
15685        || recovered_c_macro_binding_role(node)
15686        || recovered_c_label_role(node)
15687    {
15688        return false;
15689    }
15690    // A declaration name can be an identifier child of a recovered ERROR
15691    // (for example `EFI_STATUS Encode()` in C files parsed as C++). Do not
15692    // let a same-named macro turn that binder into a reference. An
15693    // assignment whose C callee follows the recovered `explicit` token is
15694    // the one expression-shaped exception: the generic declarator walk sees
15695    // its function_declarator as a declaration path, but the ERROR sibling
15696    // proves it is a call.
15697    let name = node_text(node, source);
15698    let recovered_function_call = recovered_c_function_call(visibility, file, node, name);
15699    let recovered_macro_call = recovered_c_function_declarator_invocation(node)
15700        && visibility.macro_name_may_be_bound_at(file, name, node.start_byte());
15701    let recovered_parenthesized_reference = recovered_c_parenthesized_declarator_reference(node);
15702    if is_declaration_name(node)
15703        && !recovered_c_explicit_assignment_callee(visibility, file, node, name)
15704        && !recovered_function_call
15705        && !recovered_macro_call
15706        && !recovered_parenthesized_reference
15707    {
15708        return false;
15709    }
15710
15711    if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
15712        return true;
15713    }
15714    if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
15715        return true;
15716    }
15717    if recovered_parenthesized_reference {
15718        return true;
15719    }
15720    if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
15721        if recovered_function_call {
15722            return true;
15723        }
15724        return visibility
15725            .visible_identifier_candidates(file, name)
15726            .any(|candidate| candidate.is_class() || candidate.is_module());
15727    }
15728    let visible = visibility
15729        .visible_identifier_candidates(file, name)
15730        .next()
15731        .is_some();
15732    visible
15733        && (recovered_c_reference_anchor(node)
15734            || recovered_c_error_expression_leaf(node)
15735            || recovered_function_call)
15736}
15737
15738fn push_recovered_c_range(
15739    ranges: &mut Vec<Range>,
15740    seen: &mut HashSet<(usize, usize)>,
15741    start_byte: usize,
15742    end_byte: usize,
15743    node: Node<'_>,
15744    limit: usize,
15745) -> bool {
15746    if start_byte >= end_byte || !seen.insert((start_byte, end_byte)) {
15747        return true;
15748    }
15749    if ranges.len() >= limit {
15750        return false;
15751    }
15752    ranges.push(Range {
15753        start_byte,
15754        end_byte,
15755        start_line: node.start_position().row,
15756        end_line: node.end_position().row,
15757    });
15758    true
15759}
15760
15761/// A reference leaf can sit directly beneath an ERROR while its ERROR parent
15762/// is still attached to a real expression (most often a recovered macro call
15763/// argument). The expression parent is the structured proof; an unindexed
15764/// identifier beneath a bare recovery envelope has no such proof.
15765fn recovered_c_error_expression_leaf(node: Node<'_>) -> bool {
15766    let mut current = node.parent();
15767    while let Some(parent) = current {
15768        if parent.is_error() {
15769            let Some(anchor) = parent.parent() else {
15770                return false;
15771            };
15772            return anchor.kind().ends_with("_expression")
15773                || matches!(
15774                    anchor.kind(),
15775                    "argument_list"
15776                        | "return_statement"
15777                        | "expression_statement"
15778                        | "case_statement"
15779                        | "initializer_list"
15780                        | "field_designator"
15781                        | "enumerator"
15782                );
15783        }
15784        if matches!(
15785            parent.kind(),
15786            "translation_unit" | "function_definition" | "compound_statement"
15787        ) {
15788            return false;
15789        }
15790        current = parent.parent();
15791    }
15792    false
15793}
15794
15795/// C recovery may represent a call as `identifier > function_declarator >
15796/// function_declarator > ERROR > compound_statement`. This shape is only a
15797/// call when the malformed declarator is attached to a real function body and
15798/// the name is an indexed visible callable. A declaration's
15799/// `ERROR > function_declarator > declaration` shape deliberately fails this
15800/// test.
15801fn recovered_c_function_call(
15802    visibility: &VisibilityIndex<'_>,
15803    file: &ProjectFile,
15804    node: Node<'_>,
15805    name: &str,
15806) -> bool {
15807    if !matches!(
15808        node.kind(),
15809        "identifier" | "field_identifier" | "type_identifier"
15810    ) {
15811        return false;
15812    }
15813    // C++ keywords used as C arguments can leave only the call prefix under
15814    // ERROR, with the remaining arguments in a following expression statement.
15815    // Require a statement boundary in a real block, an opening parenthesis,
15816    // and a recovered argument. A bare ERROR identifier supplies no call role.
15817    let error_call_prefix = node.parent().is_some_and(|error| {
15818        error.is_error()
15819            && error
15820                .parent()
15821                .is_some_and(|parent| parent.kind() == "compound_statement")
15822    }) && node
15823        .prev_sibling()
15824        .is_none_or(|previous| previous.kind() == ";")
15825        && node.next_sibling().is_some_and(|open| {
15826            open.kind() == "("
15827                && open.next_named_sibling().is_some_and(|argument| {
15828                    argument.kind() == "parameter_declaration" && !argument.has_error()
15829                })
15830        });
15831    (error_call_prefix || recovered_c_function_declarator_invocation(node))
15832        && visibility
15833            .visible_identifier_candidates(file, name)
15834            .any(CodeUnit::is_function)
15835}
15836
15837/// Return whether a C identifier belongs to a call-shaped declarator that the
15838/// C++ grammar put under an `ERROR` node.
15839///
15840/// The malformed call can be direct (`f(arg)`) or nested in a parameter
15841/// declaration when one of its arguments looks like a type (`f(TYPE, value)`).
15842/// In both cases the CST retains the function-declarator and its enclosing
15843/// recovery envelope. We walk only those declarator/parameter nodes and stop
15844/// at a real expression-bearing boundary; declarations therefore cannot pass
15845/// this predicate merely because they have a parameter list.
15846fn recovered_c_function_declarator_invocation(node: Node<'_>) -> bool {
15847    let mut function_declarator = if node.parent().is_some_and(|parent| {
15848        parent.kind() == "function_declarator"
15849            && parent.child_by_field_name("declarator") == Some(node)
15850    }) {
15851        node.parent().expect("checked function declarator parent")
15852    } else {
15853        let Some(parameter) = node.parent().filter(|parent| {
15854            parent.kind() == "parameter_declaration"
15855                && parent.child_by_field_name("type") == Some(node)
15856        }) else {
15857            return false;
15858        };
15859        if !parameter
15860            .child_by_field_name("declarator")
15861            .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
15862        {
15863            return false;
15864        }
15865        let Some(parameters) = parameter
15866            .parent()
15867            .filter(|parent| parent.kind() == "parameter_list")
15868        else {
15869            return false;
15870        };
15871        let Some(function_declarator) = parameters
15872            .parent()
15873            .filter(|parent| parent.kind() == "function_declarator")
15874        else {
15875            return false;
15876        };
15877        function_declarator
15878    };
15879
15880    // Recovery may absorb the next statement's parenthesized cast into a
15881    // second function declarator. Follow only the original declarator chain.
15882    while let Some(parent) = function_declarator.parent().filter(|parent| {
15883        parent.kind() == "function_declarator"
15884            && parent.child_by_field_name("declarator") == Some(function_declarator)
15885    }) {
15886        function_declarator = parent;
15887    }
15888    let Some(mut current) = function_declarator
15889        .parent()
15890        .filter(|parent| parent.is_error())
15891    else {
15892        return false;
15893    };
15894    loop {
15895        let Some(parent) = current.parent() else {
15896            return false;
15897        };
15898        if matches!(
15899            parent.kind(),
15900            "translation_unit"
15901                | "compound_statement"
15902                | "preproc_if"
15903                | "preproc_ifdef"
15904                | "preproc_ifndef"
15905                | "preproc_else"
15906                | "preproc_elif"
15907        ) {
15908            return true;
15909        }
15910        if parent.kind() == "function_definition"
15911            && parent.child_by_field_name("declarator") == Some(current)
15912            && parent.named_child(0) == Some(current)
15913            && parent.child_by_field_name("body").is_some()
15914        {
15915            return true;
15916        }
15917        if parent.is_error()
15918            || matches!(
15919                parent.kind(),
15920                "parameter_declaration"
15921                    | "parameter_list"
15922                    | "function_declarator"
15923                    | "abstract_function_declarator"
15924                    | "parenthesized_declarator"
15925            )
15926        {
15927            current = parent;
15928            continue;
15929        }
15930        return false;
15931    }
15932}
15933
15934/// C permits an identifier named `typename`. The C++ grammar can recover an
15935/// assignment using that identifier as a declaration whose declarator is a
15936/// parenthesized argument list, for example `typename = f(ctx, value)`. Only
15937/// the argument retained beneath the nested `ERROR` is a reference; sibling
15938/// declarator identifiers remain binders/grammar artifacts.
15939fn recovered_c_parenthesized_declarator_reference(node: Node<'_>) -> bool {
15940    let Some(error) = node.parent().filter(|parent| parent.is_error()) else {
15941        return false;
15942    };
15943    if error.named_child_count() != 1 || error.named_child(0) != Some(node) {
15944        return false;
15945    }
15946    let Some(declarator) = error
15947        .parent()
15948        .filter(|parent| parent.kind() == "parenthesized_declarator")
15949    else {
15950        return false;
15951    };
15952    let Some(declaration) = declarator
15953        .parent()
15954        .filter(|parent| parent.kind() == "declaration")
15955    else {
15956        return false;
15957    };
15958    if declaration.child_by_field_name("declarator") != Some(declarator) {
15959        return false;
15960    }
15961    let Some(type_node) = declaration.child_by_field_name("type") else {
15962        return false;
15963    };
15964    type_node.kind() == "dependent_type"
15965        && type_node
15966            .child(0)
15967            .is_some_and(|keyword| keyword.kind() == "typename")
15968}
15969
15970fn recovered_c_explicit_assignment_callee(
15971    visibility: &VisibilityIndex<'_>,
15972    file: &ProjectFile,
15973    node: Node<'_>,
15974    name: &str,
15975) -> bool {
15976    let mut current = node;
15977    let error = loop {
15978        let Some(parent) = current.parent() else {
15979            return false;
15980        };
15981        if parent.is_error() {
15982            break parent;
15983        }
15984        current = parent;
15985    };
15986    let mut cursor = error.walk();
15987    let explicit_recovery_precedes_callee = error
15988        .named_children(&mut cursor)
15989        .take_while(|child| child.start_byte() < node.start_byte())
15990        .any(|child| child.kind() == "explicit_function_specifier");
15991    if !explicit_recovery_precedes_callee {
15992        return false;
15993    }
15994    visibility
15995        .visible_identifier_candidates(file, name)
15996        .any(CodeUnit::is_function)
15997}
15998
15999fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
16000    while let Some(parent) = node.parent() {
16001        if matches!(
16002            parent.kind(),
16003            "preproc_def" | "preproc_function_def" | "preproc_params"
16004        ) {
16005            return true;
16006        }
16007        if parent.is_error()
16008            || matches!(
16009                parent.kind(),
16010                "translation_unit" | "function_definition" | "compound_statement"
16011            )
16012        {
16013            return false;
16014        }
16015        node = parent;
16016    }
16017    false
16018}
16019
16020fn recovered_c_label_role(node: Node<'_>) -> bool {
16021    node.parent().is_some_and(|parent| {
16022        matches!(parent.kind(), "labeled_statement" | "goto_statement")
16023            && parent.child_by_field_name("label") == Some(node)
16024    })
16025}
16026
16027fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
16028    while let Some(parent) = node.parent() {
16029        if parent.is_error() {
16030            return false;
16031        }
16032        // A C macro call recovered as a function declarator can parse an
16033        // assignment-shaped argument as an optional parameter. Its
16034        // `default_value` field remains an expression role even though the
16035        // surrounding call shape is beneath ERROR.
16036        if parent.kind() == "optional_parameter_declaration"
16037            && parent
16038                .child_by_field_name("default_value")
16039                .is_some_and(|value| node_range_contains(value, node))
16040        {
16041            return true;
16042        }
16043        if parent.kind().ends_with("_expression")
16044            || matches!(
16045                parent.kind(),
16046                "argument_list"
16047                    | "return_statement"
16048                    | "expression_statement"
16049                    | "case_statement"
16050                    | "initializer_list"
16051                    | "init_declarator"
16052                    | "array_declarator"
16053                    | "field_designator"
16054                    | "enumerator"
16055            )
16056        {
16057            return true;
16058        }
16059        if matches!(
16060            parent.kind(),
16061            "translation_unit"
16062                | "function_definition"
16063                | "compound_statement"
16064                | "declaration"
16065                | "field_declaration"
16066                | "parameter_declaration"
16067        ) {
16068            return false;
16069        }
16070        node = parent;
16071    }
16072    false
16073}
16074
16075/// Whether a parameter declaration belongs to the callable scope whose body can
16076/// contain references to it.
16077///
16078/// Error recovery can wrap a macro-decorated class body in a synthetic outer
16079/// `function_definition`. Merely finding any callable ancestor would then leak
16080/// parameters from member prototypes into later member bodies. Require the
16081/// parameter to be inside that definition's own declarator instead.
16082pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
16083    let mut current = parameter.parent();
16084    while let Some(ancestor) = current {
16085        if ancestor.kind() == "lambda_expression" {
16086            return ancestor
16087                .child_by_field_name("declarator")
16088                .is_some_and(|declarator| {
16089                    declarator.start_byte() <= parameter.start_byte()
16090                        && parameter.end_byte() <= declarator.end_byte()
16091                });
16092        }
16093        if ancestor.kind() == "function_definition" {
16094            return ancestor
16095                .child_by_field_name("declarator")
16096                .is_some_and(|declarator| {
16097                    declarator.start_byte() <= parameter.start_byte()
16098                        && parameter.end_byte() <= declarator.end_byte()
16099                });
16100        }
16101        current = ancestor.parent();
16102    }
16103    false
16104}
16105
16106pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
16107    let mut current = node.parent();
16108    while let Some(ancestor) = current {
16109        if matches!(
16110            ancestor.kind(),
16111            "parameter_declaration" | "optional_parameter_declaration"
16112        ) {
16113            return crate::declarations::cpp_type_field_contains_node(ancestor, node);
16114        }
16115        if matches!(
16116            ancestor.kind(),
16117            "function_definition" | "lambda_expression" | "compound_statement"
16118        ) {
16119            return false;
16120        }
16121        current = ancestor.parent();
16122    }
16123    false
16124}
16125
16126/// Walk tag-specifier ancestors until a type-reference context makes its name
16127/// an operand rather than a declaration. Callers retain their own terminal
16128/// declaration boundaries after this shared exclusion scan.
16129pub fn cpp_tag_specifier_ancestors_before_type_reference_context(
16130    specifier: Node<'_>,
16131) -> impl Iterator<Item = Node<'_>> {
16132    std::iter::successors(specifier.parent(), |ancestor| ancestor.parent()).take_while(|ancestor| {
16133        !matches!(
16134            ancestor.kind(),
16135            "type_descriptor"
16136                | "parameter_declaration"
16137                | "optional_parameter_declaration"
16138                | "template_argument_list"
16139                | "cast_expression"
16140        )
16141    })
16142}
16143
16144pub fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
16145    if specifier.child_by_field_name("body").is_some() {
16146        return true;
16147    }
16148    for ancestor in cpp_tag_specifier_ancestors_before_type_reference_context(specifier) {
16149        match ancestor.kind() {
16150            "declaration" | "field_declaration" => {
16151                let mut cursor = ancestor.walk();
16152                return ancestor
16153                    .children_by_field_name("declarator", &mut cursor)
16154                    .next()
16155                    .is_none();
16156            }
16157            "translation_unit" => return true,
16158            _ => {}
16159        }
16160    }
16161    false
16162}
16163
16164pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
16165    match node.kind() {
16166        "identifier"
16167        | "field_identifier"
16168        | "qualified_identifier"
16169        | "scoped_identifier"
16170        | "operator_name"
16171        | "destructor_name"
16172        | "literal_operator_name" => Some(node),
16173        "reference_declarator" | "parenthesized_declarator" => {
16174            node.named_child(0).and_then(declarator_name_node)
16175        }
16176        _ => node
16177            .child_by_field_name("declarator")
16178            .or_else(|| node.child_by_field_name("name"))
16179            .or_else(|| node.child_by_field_name("field"))
16180            .and_then(declarator_name_node),
16181    }
16182}
16183
16184/// Whether `candidate` occurs on the declarator path before its selected name.
16185pub fn declarator_path_contains_before_name(
16186    declarator: Node<'_>,
16187    name: Node<'_>,
16188    candidate: Node<'_>,
16189) -> bool {
16190    let mut current = Some(declarator);
16191    while let Some(node) = current {
16192        if same_node(node, candidate) {
16193            return true;
16194        }
16195        if same_node(node, name) {
16196            return false;
16197        }
16198        current = node
16199            .child_by_field_name("declarator")
16200            .or_else(|| node.child_by_field_name("name"))
16201            .or_else(|| node.child_by_field_name("field"));
16202    }
16203    false
16204}
16205
16206fn declarator_name_path_contains(
16207    declarator: Node<'_>,
16208    candidate: Node<'_>,
16209    allow_type_identifier: bool,
16210) -> bool {
16211    let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
16212        return false;
16213    };
16214    declarator_path_contains_before_name(declarator, name, candidate)
16215}
16216
16217fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
16218    match node.kind() {
16219        "identifier"
16220        | "field_identifier"
16221        | "operator_name"
16222        | "destructor_name"
16223        | "literal_operator_name" => Some(node),
16224        "type_identifier" if allow_type_identifier => Some(node),
16225        _ => node
16226            .child_by_field_name("declarator")
16227            .or_else(|| node.child_by_field_name("name"))
16228            .or_else(|| node.child_by_field_name("field"))
16229            .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
16230    }
16231}
16232
16233/// True when `node` is a component of a larger structured type node whose outer
16234/// range is the single reference surfaced to callers.
16235pub fn is_nested_type_node(node: Node<'_>) -> bool {
16236    node.parent().is_some_and(|parent| {
16237        matches!(
16238            parent.kind(),
16239            "qualified_identifier" | "scoped_type_identifier" | "template_type"
16240        )
16241    })
16242}
16243
16244pub struct OutOfLineMemberDefinitionOwners<'tree> {
16245    pub owners: Vec<(Node<'tree>, CodeUnit)>,
16246    innermost: Option<(Node<'tree>, CodeUnit)>,
16247}
16248
16249impl OutOfLineMemberDefinitionOwners<'_> {
16250    pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
16251        self.innermost.as_ref().map(|(node, owner)| (*node, owner))
16252    }
16253}
16254
16255pub struct QualifiedOwnerComponents<'tree> {
16256    pub nodes: Vec<Node<'tree>>,
16257    pub names: Vec<String>,
16258    pub global: bool,
16259}
16260
16261/// True when each structured qualifier on the callable-name path has a real
16262/// `::` token. A macro-prefixed return type can make tree-sitter insert a
16263/// zero-width missing separator and parse `TYPE Result<T> method()` as the
16264/// false qualified declarator `Result<T>::method`.
16265pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
16266    let mut stack = vec![node];
16267    let mut found_separator = false;
16268    while let Some(current) = stack.pop() {
16269        if !matches!(
16270            current.kind(),
16271            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
16272        ) {
16273            continue;
16274        }
16275        let mut current_has_separator = false;
16276        for child in children_iter(current) {
16277            if child.kind() == "::" {
16278                if child.is_missing() {
16279                    return false;
16280                }
16281                current_has_separator = true;
16282                found_separator = true;
16283            }
16284        }
16285        if !current_has_separator {
16286            return false;
16287        }
16288        for field in ["scope", "name"] {
16289            if let Some(child) = current.child_by_field_name(field)
16290                && matches!(
16291                    child.kind(),
16292                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
16293                )
16294            {
16295                stack.push(child);
16296            }
16297        }
16298    }
16299    found_separator
16300}
16301
16302pub fn qualified_owner_components<'tree>(
16303    node: Node<'tree>,
16304    source: &str,
16305) -> Option<QualifiedOwnerComponents<'tree>> {
16306    if !qualified_name_has_concrete_scope_separators(node) {
16307        return None;
16308    }
16309    let mut nodes = cpp_name_component_nodes(node)?;
16310    nodes.pop()?;
16311    if nodes.is_empty() {
16312        return None;
16313    }
16314    let names = nodes
16315        .iter()
16316        .map(|component| node_text(*component, source).to_string())
16317        .collect();
16318    Some(QualifiedOwnerComponents {
16319        nodes,
16320        names,
16321        global: is_globally_qualified_cpp_name(node),
16322    })
16323}
16324
16325pub fn out_of_line_member_definition_owner<'tree>(
16326    analyzer: &CppGraphSource<'_>,
16327    visibility: &VisibilityIndex<'_>,
16328    file: &ProjectFile,
16329    source: &str,
16330    node: Node<'tree>,
16331) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
16332    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
16333        || !has_ancestor_kind(node, "function_definition")
16334        || !is_function_declarator_name_root(node)
16335    {
16336        return None;
16337    }
16338    let qualified = qualified_owner_components(node, source)?;
16339    let lexical_scope = enclosing_namespace_components(node, source)?;
16340    let mut owners = Vec::new();
16341    let mut innermost = None;
16342
16343    for component_count in 1..=qualified.names.len() {
16344        if let LexicalTypeResolution::Resolved { unit, .. } = visibility
16345            .resolve_type_components_lexically(
16346                analyzer,
16347                file,
16348                &qualified.names[..component_count],
16349                qualified.global,
16350                &lexical_scope,
16351            )
16352            && !owners
16353                .iter()
16354                .any(|(_, existing)| same_visible_symbol(existing, &unit))
16355        {
16356            if component_count == qualified.names.len() {
16357                innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
16358            }
16359            owners.push((qualified.nodes[component_count - 1], unit));
16360        }
16361    }
16362
16363    // The C++ analyzer has already reconciled an indexed out-of-line callable
16364    // against the include-visible class table. Consult that canonical owner
16365    // chain only when ordinary lexical lookup could not recover the innermost
16366    // owner.  A one-segment qualifier is safe here only when the enclosing
16367    // indexed callable has an authoritative class owner and the parser's
16368    // namespace path is a (possibly sparse) subsequence of that owner path.
16369    // The latter is what lets macro-wrapped namespace sentinels recover a
16370    // missing `time_internal`/`cord_internal` component without guessing an
16371    // unrelated short name.
16372    if innermost.is_none() {
16373        let indexed_owner_components = visibility
16374            .indexed_enclosing_owner_scope(analyzer, file, node)
16375            .or_else(|| {
16376                // Retain the legacy rendered-name fallback for the existing
16377                // multi-segment path when an enclosing owner chain is not
16378                // available (for example, cache-loaded units without parent
16379                // links).  One-segment recovery must stay canonical-only.
16380                if qualified.names.len() <= 1 {
16381                    return None;
16382                }
16383                let range = Range {
16384                    start_byte: node.start_byte(),
16385                    end_byte: node.end_byte(),
16386                    start_line: node.start_position().row,
16387                    end_line: node.end_position().row,
16388                };
16389                let start = analyzer.enclosing_code_unit(file, &range)?;
16390                let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
16391                    brokk_bifrost_core::analyzer::Language::Cpp,
16392                    &cpp_name_for(&start),
16393                );
16394                components.pop();
16395                Some(components)
16396            });
16397        if let Some(indexed_owner_components) = indexed_owner_components
16398            && indexed_owner_components.len() > qualified.names.len()
16399            && indexed_owner_components.ends_with(&qualified.names)
16400            && indexed_namespace_path_is_recoverable(
16401                &lexical_scope,
16402                &indexed_owner_components,
16403                qualified.names.len(),
16404            )
16405            // A globally-qualified one-segment owner is an explicit request
16406            // for the top-level binding; do not reinterpret it as a missing
16407            // namespace component.  Existing multi-segment global lookups
16408            // retain their historical indexed recovery.
16409            && (qualified.names.len() > 1 || !qualified.global)
16410        {
16411            let namespace_count = indexed_owner_components.len() - qualified.names.len();
16412            for component_count in 1..=qualified.names.len() {
16413                let expected = &indexed_owner_components[..namespace_count + component_count];
16414                let owner_node = qualified.nodes[component_count - 1];
16415                for owner in visibility
16416                    .visible_identifier_candidates(file, &qualified.names[component_count - 1])
16417                    .filter(|candidate| candidate.is_class())
16418                    .filter(|candidate| {
16419                        canonical_cpp_scope_components(candidate) == expected
16420                            && visibility.external_type_candidate_visible_in_context(
16421                                analyzer, file, candidate, node,
16422                            )
16423                    })
16424                {
16425                    if component_count == qualified.names.len() && innermost.is_none() {
16426                        innermost = Some((owner_node, owner.clone()));
16427                    }
16428                    if !owners
16429                        .iter()
16430                        .any(|(_, existing)| same_symbol(existing, owner))
16431                    {
16432                        owners.push((owner_node, owner.clone()));
16433                    }
16434                }
16435            }
16436        }
16437    }
16438    (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
16439}
16440
16441fn is_function_declarator_name_root(node: Node<'_>) -> bool {
16442    let mut current = node;
16443    while let Some(parent) = current.parent() {
16444        if parent.kind() == "function_declarator" {
16445            return parent.child_by_field_name("declarator") == Some(current);
16446        }
16447        if matches!(
16448            parent.kind(),
16449            "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
16450        ) && parent.child_by_field_name("declarator") == Some(current)
16451        {
16452            current = parent;
16453            continue;
16454        }
16455        return false;
16456    }
16457    false
16458}
16459
16460pub fn append_cpp_name_components(
16461    node: Node<'_>,
16462    source: &str,
16463    out: &mut Vec<String>,
16464) -> Option<()> {
16465    out.extend(
16466        cpp_name_component_nodes(node)?
16467            .into_iter()
16468            .map(|component| node_text(component, source).to_string()),
16469    );
16470    Some(())
16471}
16472
16473/// The components of a C++ name with the source range of the token that
16474/// writes each one, outermost first.
16475fn cpp_name_component_ranges(node: Node<'_>, source: &str) -> Option<Vec<(String, Range)>> {
16476    cpp_name_component_nodes(node).map(|components| {
16477        components
16478            .into_iter()
16479            .map(|component| {
16480                (
16481                    node_text(component, source).to_string(),
16482                    node_range(component),
16483                )
16484            })
16485            .collect()
16486    })
16487}
16488
16489pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
16490    let mut components = Vec::new();
16491    append_cpp_name_components(node, source, &mut components)?;
16492    Some(components)
16493}
16494
16495/// Resolve a structured type spelling from an object-like macro replacement
16496/// when definition-site source order has no answer.
16497///
16498/// Macro replacement tokens are looked up where the macro is expanded, so a
16499/// type declared later in the defining header can still be their destination.
16500/// Without expanding every invocation, accept only one include-visible logical
16501/// class or alias whose structured path ends in the replacement components.
16502/// An ordinary lexical answer always takes precedence at the call site.
16503pub fn unique_macro_replacement_type_candidate(
16504    analyzer: &CppGraphSource<'_>,
16505    visibility: &VisibilityIndex<'_>,
16506    file: &ProjectFile,
16507    components: &[String],
16508) -> Option<CodeUnit> {
16509    let terminal = components.last()?;
16510    let mut candidates = Vec::new();
16511    for candidate in visibility
16512        .visible_identifier_candidates(file, terminal)
16513        .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
16514        .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
16515    {
16516        if !candidates
16517            .iter()
16518            .any(|existing| same_logical_symbol(existing, candidate))
16519        {
16520            candidates.push(candidate.clone());
16521        }
16522    }
16523    (candidates.len() == 1).then(|| candidates.remove(0))
16524}
16525
16526/// The base scopes named by member using-declarations for `member` in one
16527/// class source range.
16528///
16529/// The grammar supplies the qualified identifier and each component. Keep
16530/// this interpretation shared between forward overload lookup and inverse
16531/// owner routing rather than reparsing a rendered `Base::member` string at
16532/// either call site.
16533pub fn cpp_member_using_declaration_scopes(source: &str, member: &str) -> Vec<String> {
16534    let mut parser = Parser::new();
16535    if parser
16536        .set_language(&tree_sitter_cpp::LANGUAGE.into())
16537        .is_err()
16538    {
16539        return Vec::new();
16540    }
16541    let Some(tree) = parser.parse(source, None) else {
16542        return Vec::new();
16543    };
16544    let mut scopes = Vec::new();
16545    let mut pending = vec![tree.root_node()];
16546    while let Some(node) = pending.pop() {
16547        if node.kind() == "using_declaration" {
16548            let Some(imported) = node.named_child(0) else {
16549                continue;
16550            };
16551            let Some(mut components) = cpp_type_name_components(imported, source) else {
16552                continue;
16553            };
16554            if components.pop().as_deref() == Some(member) && !components.is_empty() {
16555                scopes.push(components.join("::"));
16556            }
16557            continue;
16558        }
16559        push_named_children_reversed(node, &mut pending);
16560    }
16561    scopes
16562}
16563
16564/// Whether a structured using-declaration scope can name `qualified` as an
16565/// ancestor class. The boundary check prevents `Base` from matching
16566/// `OtherBase` while allowing a relative `Base` spelling to match `ns::Base`.
16567pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
16568    qualified == scope
16569        || qualified
16570            .strip_suffix(scope)
16571            .is_some_and(|prefix| prefix.ends_with("::"))
16572}
16573
16574/// Whether `node` is the direct structured type payload of a template
16575/// argument. This role remains meaningful even when a surrounding expression
16576/// is below tree-sitter recovery, because both the `template_argument_list`
16577/// and the `type_descriptor` retain their named fields.
16578pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
16579    let Some(type_descriptor) = node.parent() else {
16580        return false;
16581    };
16582    if type_descriptor.kind() != "type_descriptor"
16583        || type_descriptor.child_by_field_name("type") != Some(node)
16584    {
16585        return false;
16586    }
16587    let Some(arguments) = type_descriptor.parent() else {
16588        return false;
16589    };
16590    if arguments.kind() != "template_argument_list" {
16591        return false;
16592    }
16593    arguments.parent().is_some_and(|parent| {
16594        matches!(parent.kind(), "template_type" | "template_function")
16595            && parent.child_by_field_name("arguments") == Some(arguments)
16596    })
16597}
16598
16599pub fn cpp_template_reference_arguments(
16600    mut node: Node<'_>,
16601    source: &str,
16602) -> Option<Vec<CppTemplateExpression>> {
16603    loop {
16604        match node.kind() {
16605            "template_type" | "template_function" => {
16606                let arguments = node.child_by_field_name("arguments")?;
16607                let mut cursor = arguments.walk();
16608                return Some(
16609                    arguments
16610                        .named_children(&mut cursor)
16611                        .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
16612                        .map(|argument| CppTemplateExpression {
16613                            text: normalize_cpp_whitespace(node_text(argument, source)),
16614                            // One template term from a resolver query; see `ParentIndex::unindexed`.
16615                            term: cpp_template_term(
16616                                argument,
16617                                source,
16618                                &[],
16619                                &ParentIndex::unindexed(),
16620                            ),
16621                        })
16622                        .collect(),
16623                );
16624            }
16625            "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
16626                node = node
16627                    .child_by_field_name("name")
16628                    .or_else(|| node.child_by_field_name("type"))?;
16629            }
16630            _ => return None,
16631        }
16632    }
16633}
16634
16635fn cpp_reconcile_primary_template_parameters(
16636    candidates: &[(&CodeUnit, &CppTemplateMetadata)],
16637    preferred: &CodeUnit,
16638) -> Option<Vec<CppTemplateParameterMetadata>> {
16639    let canonical = candidates
16640        .iter()
16641        .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
16642    let mut merged = canonical
16643        .parameters
16644        .iter()
16645        .map(|parameter| CppTemplateParameterMetadata {
16646            name: parameter.name.clone(),
16647            kind: parameter.kind,
16648            variadic: parameter.variadic,
16649            default: None,
16650        })
16651        .collect::<Vec<_>>();
16652
16653    for (_, metadata) in candidates {
16654        if metadata.parameters.len() != merged.len() {
16655            return None;
16656        }
16657        let rename_bindings = metadata
16658            .parameters
16659            .iter()
16660            .zip(&merged)
16661            .map(|(parameter, canonical)| {
16662                (
16663                    parameter.name.clone(),
16664                    CppTemplateTerm::Parameter(canonical.name.clone()),
16665                )
16666            })
16667            .collect::<HashMap<_, _>>();
16668        for ((parameter, canonical), merged_parameter) in metadata
16669            .parameters
16670            .iter()
16671            .zip(&canonical.parameters)
16672            .zip(&mut merged)
16673        {
16674            if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
16675                return None;
16676            }
16677            let Some(default) = &parameter.default else {
16678                continue;
16679            };
16680            let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
16681            if let Some(existing) = &merged_parameter.default {
16682                if !cpp_template_terms_equal(&existing.term, &normalized_term) {
16683                    return None;
16684                }
16685            } else {
16686                merged_parameter.default = Some(CppTemplateExpression {
16687                    text: default.text.clone(),
16688                    term: normalized_term,
16689                });
16690            }
16691        }
16692    }
16693    Some(merged)
16694}
16695
16696pub fn cpp_bind_template_arguments(
16697    parameters: &[CppTemplateParameterMetadata],
16698    explicit_arguments: &[CppTemplateExpression],
16699) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
16700    let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
16701    if variadic_index.is_some_and(|index| {
16702        index + 1 != parameters.len()
16703            || parameters[index + 1..]
16704                .iter()
16705                .any(|parameter| parameter.variadic)
16706    }) {
16707        return None;
16708    }
16709    let fixed_count = variadic_index.unwrap_or(parameters.len());
16710    if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
16711        return None;
16712    }
16713    let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
16714    let mut expanded = explicit_arguments[..explicit_fixed_count]
16715        .iter()
16716        .map(cpp_clone_template_expression_iterative)
16717        .collect::<Vec<_>>();
16718    let mut bindings = HashMap::default();
16719    for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
16720        bindings.insert(
16721            parameter.name.clone(),
16722            cpp_clone_template_term_iterative(&argument.term),
16723        );
16724    }
16725    for parameter in &parameters[explicit_fixed_count..fixed_count] {
16726        let default = parameter.default.as_ref()?;
16727        let term = cpp_substitute_template_term(&default.term, &bindings)?;
16728        bindings.insert(parameter.name.clone(), term.clone());
16729        expanded.push(CppTemplateExpression {
16730            text: default.text.clone(),
16731            term,
16732        });
16733    }
16734    if let Some(index) = variadic_index {
16735        let packed_arguments = &explicit_arguments[explicit_fixed_count..];
16736        expanded.extend(
16737            packed_arguments
16738                .iter()
16739                .map(cpp_clone_template_expression_iterative),
16740        );
16741        bindings.insert(
16742            parameters[index].name.clone(),
16743            CppTemplateTerm::Node {
16744                kind: "parameter_pack".to_string(),
16745                children: packed_arguments
16746                    .iter()
16747                    .map(|argument| cpp_clone_template_term_iterative(&argument.term))
16748                    .collect(),
16749            },
16750        );
16751    }
16752    Some((expanded, bindings))
16753}
16754
16755fn cpp_specialization_matches(
16756    metadata: &CppTemplateMetadata,
16757    arguments: &[CppTemplateExpression],
16758) -> bool {
16759    cpp_specialization_terms_match(
16760        &metadata.parameters,
16761        &metadata.specialization_arguments,
16762        arguments,
16763    )
16764}
16765
16766fn cpp_specialization_terms_match(
16767    parameters: &[CppTemplateParameterMetadata],
16768    patterns: &[CppTemplateExpression],
16769    arguments: &[CppTemplateExpression],
16770) -> bool {
16771    if patterns.len() != arguments.len() {
16772        return false;
16773    }
16774    let parameter_names = parameters
16775        .iter()
16776        .map(|parameter| parameter.name.as_str())
16777        .collect::<HashSet<_>>();
16778    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
16779    patterns.iter().zip(arguments).all(|(pattern, argument)| {
16780        cpp_unify_template_term(
16781            &pattern.term,
16782            &argument.term,
16783            &parameter_names,
16784            &mut bindings,
16785        )
16786    })
16787}
16788
16789fn cpp_specialization_more_specialized(
16790    candidate: &CppTemplateMetadata,
16791    other: &CppTemplateMetadata,
16792) -> bool {
16793    cpp_specialization_pattern_accepts(other, candidate)
16794        && !cpp_specialization_pattern_accepts(candidate, other)
16795}
16796
16797fn cpp_specialization_pattern_accepts(
16798    broader: &CppTemplateMetadata,
16799    narrower: &CppTemplateMetadata,
16800) -> bool {
16801    cpp_specialization_terms_match(
16802        &broader.parameters,
16803        &broader.specialization_arguments,
16804        &narrower.specialization_arguments,
16805    )
16806}
16807
16808pub fn cpp_substitute_template_term(
16809    term: &CppTemplateTerm,
16810    bindings: &HashMap<String, CppTemplateTerm>,
16811) -> Option<CppTemplateTerm> {
16812    enum Work<'a> {
16813        Visit(&'a CppTemplateTerm),
16814        Build { kind: String, child_count: usize },
16815    }
16816
16817    let mut work = vec![Work::Visit(term)];
16818    let mut substituted = Vec::new();
16819    while let Some(next) = work.pop() {
16820        match next {
16821            Work::Visit(CppTemplateTerm::Parameter(name)) => {
16822                substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
16823            }
16824            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
16825                substituted.push(CppTemplateTerm::Atom {
16826                    kind: kind.clone(),
16827                    text: text.clone(),
16828                });
16829            }
16830            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
16831                work.push(Work::Build {
16832                    kind: kind.clone(),
16833                    child_count: children.len(),
16834                });
16835                work.extend(children.iter().rev().map(Work::Visit));
16836            }
16837            Work::Build { kind, child_count } => {
16838                let children = substituted.split_off(substituted.len() - child_count);
16839                substituted.push(CppTemplateTerm::Node { kind, children });
16840            }
16841        }
16842    }
16843    substituted.pop()
16844}
16845
16846pub fn cpp_substitute_template_arguments(
16847    arguments: &[CppTemplateExpression],
16848    bindings: &HashMap<String, CppTemplateTerm>,
16849) -> Option<Vec<CppTemplateExpression>> {
16850    let mut substituted = Vec::new();
16851    for argument in arguments {
16852        let CppTemplateTerm::Node { kind, children } = &argument.term else {
16853            substituted.push(CppTemplateExpression {
16854                text: argument.text.clone(),
16855                term: cpp_substitute_template_term(&argument.term, bindings)?,
16856            });
16857            continue;
16858        };
16859        if kind != "parameter_pack_expansion" {
16860            substituted.push(CppTemplateExpression {
16861                text: argument.text.clone(),
16862                term: cpp_substitute_template_term(&argument.term, bindings)?,
16863            });
16864            continue;
16865        }
16866        let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
16867            return None;
16868        };
16869        if ellipsis != "..." {
16870            return None;
16871        }
16872
16873        let mut pack_names = Vec::new();
16874        let mut work = vec![pattern];
16875        while let Some(term) = work.pop() {
16876            match term {
16877                CppTemplateTerm::Parameter(name)
16878                    if matches!(
16879                        bindings.get(name),
16880                        Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
16881                    ) =>
16882                {
16883                    if !pack_names.contains(name) {
16884                        pack_names.push(name.clone());
16885                    }
16886                }
16887                CppTemplateTerm::Node { children, .. } => work.extend(children),
16888                CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
16889            }
16890        }
16891        let first_pack = pack_names.first()?;
16892        let CppTemplateTerm::Node {
16893            children: first_elements,
16894            ..
16895        } = bindings.get(first_pack)?
16896        else {
16897            return None;
16898        };
16899        let pack_len = first_elements.len();
16900        for pack_name in &pack_names {
16901            let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16902                return None;
16903            };
16904            if children.len() != pack_len {
16905                return None;
16906            }
16907        }
16908        for index in 0..pack_len {
16909            let mut element_bindings = bindings.clone();
16910            for pack_name in &pack_names {
16911                let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16912                    return None;
16913                };
16914                element_bindings.insert(
16915                    pack_name.clone(),
16916                    cpp_clone_template_term_iterative(&children[index]),
16917                );
16918            }
16919            substituted.push(CppTemplateExpression {
16920                text: argument.text.clone(),
16921                term: cpp_substitute_template_term(pattern, &element_bindings)?,
16922            });
16923        }
16924    }
16925    Some(substituted)
16926}
16927
16928fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
16929    enum Work<'a> {
16930        Visit(&'a CppTemplateTerm),
16931        Build { kind: String, child_count: usize },
16932    }
16933
16934    let mut work = vec![Work::Visit(term)];
16935    let mut cloned = Vec::new();
16936    while let Some(next) = work.pop() {
16937        match next {
16938            Work::Visit(CppTemplateTerm::Parameter(name)) => {
16939                cloned.push(CppTemplateTerm::Parameter(name.clone()));
16940            }
16941            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
16942                cloned.push(CppTemplateTerm::Atom {
16943                    kind: kind.clone(),
16944                    text: text.clone(),
16945                });
16946            }
16947            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
16948                work.push(Work::Build {
16949                    kind: kind.clone(),
16950                    child_count: children.len(),
16951                });
16952                work.extend(children.iter().rev().map(Work::Visit));
16953            }
16954            Work::Build { kind, child_count } => {
16955                let children = cloned.split_off(cloned.len() - child_count);
16956                cloned.push(CppTemplateTerm::Node { kind, children });
16957            }
16958        }
16959    }
16960    cloned
16961        .pop()
16962        .expect("template term traversal emits one root")
16963}
16964
16965fn cpp_clone_template_expression_iterative(
16966    expression: &CppTemplateExpression,
16967) -> CppTemplateExpression {
16968    CppTemplateExpression {
16969        text: expression.text.clone(),
16970        term: cpp_clone_template_term_iterative(&expression.term),
16971    }
16972}
16973
16974pub fn cpp_unify_template_term(
16975    pattern: &CppTemplateTerm,
16976    argument: &CppTemplateTerm,
16977    parameters: &HashSet<&str>,
16978    bindings: &mut HashMap<String, CppTemplateTerm>,
16979) -> bool {
16980    let mut work = vec![(pattern, argument)];
16981    while let Some((pattern, argument)) = work.pop() {
16982        match pattern {
16983            CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
16984                if let Some(bound) = bindings.get(name) {
16985                    if !cpp_template_terms_equal(bound, argument) {
16986                        return false;
16987                    }
16988                } else {
16989                    bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
16990                }
16991            }
16992            CppTemplateTerm::Atom {
16993                kind: pattern_kind,
16994                text: pattern_text,
16995            } => {
16996                if !matches!(
16997                    argument,
16998                    CppTemplateTerm::Atom { kind, text }
16999                        if kind == pattern_kind && text == pattern_text
17000                ) {
17001                    return false;
17002                }
17003            }
17004            CppTemplateTerm::Node {
17005                kind: pattern_kind,
17006                children: pattern_children,
17007            } => {
17008                let CppTemplateTerm::Node { kind, children } = argument else {
17009                    return false;
17010                };
17011                if kind != pattern_kind || children.len() != pattern_children.len() {
17012                    return false;
17013                }
17014                work.extend(pattern_children.iter().zip(children).rev());
17015            }
17016            CppTemplateTerm::Parameter(_) => return false,
17017        }
17018    }
17019    true
17020}
17021
17022fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
17023    let mut work = vec![(left, right)];
17024    while let Some((left, right)) = work.pop() {
17025        match (left, right) {
17026            (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
17027                if left != right {
17028                    return false;
17029                }
17030            }
17031            (
17032                CppTemplateTerm::Atom {
17033                    kind: left_kind,
17034                    text: left_text,
17035                },
17036                CppTemplateTerm::Atom {
17037                    kind: right_kind,
17038                    text: right_text,
17039                },
17040            ) => {
17041                if left_kind != right_kind || left_text != right_text {
17042                    return false;
17043                }
17044            }
17045            (
17046                CppTemplateTerm::Node {
17047                    kind: left_kind,
17048                    children: left_children,
17049                },
17050                CppTemplateTerm::Node {
17051                    kind: right_kind,
17052                    children: right_children,
17053                },
17054            ) => {
17055                if left_kind != right_kind || left_children.len() != right_children.len() {
17056                    return false;
17057                }
17058                work.extend(left_children.iter().zip(right_children).rev());
17059            }
17060            _ => return false,
17061        }
17062    }
17063    true
17064}
17065
17066pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
17067    let mut components = Vec::new();
17068    let mut stack = vec![node];
17069    while let Some(current) = stack.pop() {
17070        match current.kind() {
17071            "identifier"
17072            | "field_identifier"
17073            | "namespace_identifier"
17074            | "type_identifier"
17075            | "operator_name"
17076            | "destructor_name" => components.push(current),
17077            "template_type" | "template_function" => {
17078                stack.push(current.child_by_field_name("name")?);
17079            }
17080            "dependent_name" => stack.push(current.named_child(0)?),
17081            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
17082                stack.push(current.child_by_field_name("name")?);
17083                if let Some(scope) = current.child_by_field_name("scope") {
17084                    stack.push(scope);
17085                }
17086            }
17087            "nested_namespace_specifier" => {
17088                for index in (0..current.named_child_count()).rev() {
17089                    stack.push(current.named_child(index)?);
17090                }
17091            }
17092            _ => return None,
17093        }
17094    }
17095    Some(components)
17096}
17097
17098pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
17099    node.child_by_field_name("scope").is_none()
17100        && node.child(0).is_some_and(|child| child.kind() == "::")
17101}
17102
17103fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
17104    let mut namespaces = Vec::new();
17105    for parent in enclosing_cpp_namespace_nodes(node) {
17106        if let Some(name) = parent.child_by_field_name("name") {
17107            let mut components = Vec::new();
17108            append_cpp_name_components(name, source, &mut components)?;
17109            namespaces.push(components);
17110        }
17111    }
17112    Some(namespaces.into_iter().flatten().collect())
17113}
17114
17115/// Whether a parser-derived namespace path can be reconciled with an indexed
17116/// owner scope without inventing an unrelated short-name binding.
17117///
17118/// Macro namespace sentinels can make tree-sitter omit one or more namespace
17119/// definitions from the ancestor chain. Preserve the order of every namespace
17120/// that did survive parsing, but allow indexed components between them. An
17121/// empty path is accepted only when the declarator itself supplies a nested
17122/// owner suffix such as `Outer::Inner`: together with the indexed enclosing
17123/// owner chain, that suffix is structural evidence that a namespace was lost.
17124/// A one-segment owner at the translation-unit root remains insufficient.
17125fn indexed_namespace_path_is_recoverable(
17126    lexical_scope: &[String],
17127    indexed_owner_scope: &[String],
17128    explicit_owner_component_count: usize,
17129) -> bool {
17130    if lexical_scope.is_empty() {
17131        return explicit_owner_component_count > 1;
17132    }
17133    if lexical_scope.len() >= indexed_owner_scope.len() {
17134        return false;
17135    }
17136    super::cpp_namespace_path_is_ordered_subsequence(lexical_scope, indexed_owner_scope)
17137}
17138
17139pub use brokk_bifrost_core::analyzer::tree_walk::has_ancestor_kind;
17140
17141/// Whether a declaration type is initialized with a pointer cast.
17142///
17143/// This structured shape has an independent qualified occurrence in addition
17144/// to the cast descriptor below it. Other declarations must keep their normal
17145/// full-range occurrence only.
17146pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
17147    let mut current = Some(node);
17148    while let Some(candidate) = current {
17149        if candidate.kind() == "declaration" {
17150            if !crate::declarations::cpp_type_field_contains_node(candidate, node) {
17151                return false;
17152            }
17153            let mut cursor = candidate.walk();
17154            return candidate.named_children(&mut cursor).any(|child| {
17155                child.kind() == "init_declarator"
17156                    && child
17157                        .child_by_field_name("value")
17158                        .is_some_and(|value| value.kind() == "cast_expression")
17159            });
17160        }
17161        current = candidate.parent();
17162    }
17163    false
17164}
17165
17166#[derive(Clone, Copy, PartialEq, Eq)]
17167pub(crate) enum QualifiedAliasReferenceKind {
17168    Ordinary,
17169    ConstructorWithExpressionArgument,
17170    ExhaustiveTemplate,
17171}
17172
17173/// Whether a qualified alias reference preserves the requested target.
17174///
17175/// The complete qualified spelling and its terminal identifier are both valid
17176/// occurrences when the visible alias path is structurally proven to name the
17177/// target. Template aliases use their bound arguments; ordinary aliases use
17178/// their structured primary chain.
17179pub(crate) fn qualified_alias_reference_preserves_target(
17180    node: Node<'_>,
17181    target: &CodeUnit,
17182    analyzer: &CppGraphSource<'_>,
17183    visibility: &VisibilityIndex<'_>,
17184    file: &ProjectFile,
17185    source: &str,
17186) -> Option<QualifiedAliasReferenceKind> {
17187    if !matches!(
17188        node.kind(),
17189        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
17190    ) {
17191        return None;
17192    }
17193    let components = cpp_type_name_components(node, source)?;
17194    let name = components.last()?;
17195    analyzer.type_alias_provider().and_then(|provider| {
17196        visibility
17197            .visible_identifier_candidates(file, name)
17198            .find_map(|candidate| {
17199                let proof = provider.is_type_alias(candidate)
17200                    && canonical_cpp_scope_components(candidate) == components
17201                    && visibility.external_type_candidate_visible_in_context(
17202                        analyzer, file, candidate, node,
17203                    )
17204                    && match cpp_template_reference_arguments(node, source) {
17205                        Some(arguments) => visibility.template_alias_arguments_preserve_target(
17206                            analyzer, file, candidate, &arguments, target,
17207                        ),
17208                        None => visibility.structured_alias_primary_preserves_target(
17209                            analyzer, file, candidate, target,
17210                        ),
17211                    };
17212                proof.then(|| {
17213                    if cpp_template_reference_arguments(node, source).is_some()
17214                        && visibility.is_exhaustive_same_fqn_type_declaration_family(
17215                            analyzer, file, candidate,
17216                        )
17217                    {
17218                        QualifiedAliasReferenceKind::ExhaustiveTemplate
17219                    } else if qualified_alias_constructor_has_expression_argument(node)
17220                        || qualified_alias_local_constructor_declaration(node)
17221                    {
17222                        QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
17223                    } else {
17224                        QualifiedAliasReferenceKind::Ordinary
17225                    }
17226                })
17227            })
17228    })
17229}
17230
17231pub(crate) fn qualified_alias_reference_requires_terminal(
17232    reference: Option<QualifiedAliasReferenceKind>,
17233) -> bool {
17234    matches!(
17235        reference,
17236        Some(
17237            QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
17238                | QualifiedAliasReferenceKind::ExhaustiveTemplate
17239        )
17240    )
17241}
17242
17243fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
17244    let Some(declaration) = node.parent().filter(|parent| {
17245        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
17246    }) else {
17247        return false;
17248    };
17249    let mut cursor = declaration.walk();
17250    declaration.named_children(&mut cursor).any(|child| {
17251        child.kind() == "init_declarator"
17252            && child
17253                .child_by_field_name("value")
17254                .filter(|value| value.kind() == "argument_list")
17255                .is_some_and(|arguments| {
17256                    let mut cursor = arguments.walk();
17257                    arguments.named_children(&mut cursor).any(|argument| {
17258                        let is_parameter = matches!(
17259                            argument.kind(),
17260                            "parameter_declaration" | "optional_parameter_declaration"
17261                        );
17262                        if is_parameter {
17263                            argument
17264                                .child_by_field_name("type")
17265                                .is_some_and(|type_node| {
17266                                    type_node.kind() == "type_identifier"
17267                                        && argument.child_by_field_name("declarator").is_none()
17268                                })
17269                        } else {
17270                            !argument.kind().ends_with("_literal")
17271                                && !matches!(argument.kind(), "true" | "false" | "nullptr")
17272                        }
17273                    })
17274                })
17275    })
17276}
17277
17278/// Tree-sitter represents a local C++ direct construction such as
17279/// `Alias value(argument)` as a function declarator. Restrict that recovery to
17280/// declarations inside a compound statement so namespace-scope function
17281/// declarations with the same qualified return type stay full-range only.
17282fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
17283    let Some(declaration) = node.parent().filter(|parent| {
17284        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
17285    }) else {
17286        return false;
17287    };
17288    if declaration
17289        .parent()
17290        .is_none_or(|parent| parent.kind() != "compound_statement")
17291    {
17292        return false;
17293    }
17294    let mut cursor = declaration.walk();
17295    declaration
17296        .named_children(&mut cursor)
17297        .any(|child| child.kind() == "function_declarator")
17298}
17299
17300/// Return the terminal identifier represented by a callable or type callee.
17301///
17302/// Qualified, scoped, template, and field wrappers are traversed through their
17303/// grammar fields so both function calls and type constructions emit the token
17304/// that names the referenced declaration.
17305pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
17306    loop {
17307        let next = match node.kind() {
17308            "qualified_identifier"
17309            | "scoped_identifier"
17310            | "template_method"
17311            | "template_function"
17312            | "template_type" => node.child_by_field_name("name"),
17313            "field_expression" => node.child_by_field_name("field"),
17314            _ => None,
17315        };
17316        let Some(next) = next else {
17317            return node;
17318        };
17319        node = next;
17320    }
17321}
17322
17323#[derive(Clone, Copy)]
17324pub struct RecoveredRelationalTemplateMemberCall<'tree> {
17325    pub receiver: Node<'tree>,
17326    pub member: Node<'tree>,
17327    pub arity: usize,
17328}
17329
17330/// Recover `receiver.member<argument>(call_arguments)` when tree-sitter chose
17331/// nested relational expressions instead of a `template_method` call.
17332///
17333/// The recovery uses only grammar fields: the selected field must be the left
17334/// side of `<`, that expression must be the left side of `>`, and the right
17335/// side of `>` must be the parenthesized call arguments. Semantic callers must
17336/// additionally prove the receiver owner and the member's template status.
17337pub fn recovered_relational_template_member_call(
17338    field: Node<'_>,
17339) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
17340    if field.kind() != "field_expression" {
17341        return None;
17342    }
17343    let receiver = field
17344        .child_by_field_name("argument")
17345        .or_else(|| field.child_by_field_name("object"))?;
17346    let member = field.child_by_field_name("field")?;
17347    let less = field.parent()?;
17348    if less.kind() != "binary_expression"
17349        || less.child_by_field_name("left") != Some(field)
17350        || less
17351            .child_by_field_name("operator")
17352            .is_none_or(|operator| operator.kind() != "<")
17353        || less.child_by_field_name("right").is_none()
17354    {
17355        return None;
17356    }
17357    let greater = less.parent()?;
17358    if greater.kind() != "binary_expression"
17359        || greater.child_by_field_name("left") != Some(less)
17360        || greater
17361            .child_by_field_name("operator")
17362            .is_none_or(|operator| operator.kind() != ">")
17363    {
17364        return None;
17365    }
17366    let arguments = greater.child_by_field_name("right")?;
17367    if arguments.kind() != "parenthesized_expression" {
17368        return None;
17369    }
17370    let arity = parenthesized_call_argument_arity(arguments)?;
17371    Some(RecoveredRelationalTemplateMemberCall {
17372        receiver,
17373        member,
17374        arity,
17375    })
17376}
17377
17378fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
17379    let expression = arguments.named_child(0)?;
17380    if expression.kind() != "comma_expression" {
17381        return Some(1);
17382    }
17383    let mut arity = 0usize;
17384    let mut stack = vec![expression];
17385    while let Some(node) = stack.pop() {
17386        if node.kind() == "comma_expression" {
17387            stack.push(node.child_by_field_name("right")?);
17388            stack.push(node.child_by_field_name("left")?);
17389        } else {
17390            arity += 1;
17391        }
17392    }
17393    Some(arity)
17394}
17395
17396/// Whether `node` is part of a call's callee expression, walking only through
17397/// the grammar wrappers that can structurally contain that callee.
17398pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
17399    while let Some(parent) = node.parent() {
17400        match parent.kind() {
17401            "call_expression" => {
17402                return parent
17403                    .child_by_field_name("function")
17404                    .or_else(|| parent.named_child(0))
17405                    == Some(node);
17406            }
17407            "qualified_identifier"
17408            | "scoped_identifier"
17409            | "template_function"
17410            | "template_type"
17411            | "field_expression" => node = parent,
17412            _ => return false,
17413        }
17414    }
17415    false
17416}
17417
17418/// Expand a C++ type-name component to the complete written reference that
17419/// owns it. Qualified path wrappers that stop before a later component remain
17420/// separate, so a type used as an owner keeps its prefix range.
17421pub fn type_reference_hit_node(mut node: Node<'_>) -> Node<'_> {
17422    loop {
17423        let Some(parent) = node.parent() else {
17424            return node;
17425        };
17426        if parent.kind() == "template_type"
17427            && parent
17428                .child_by_field_name("name")
17429                .is_some_and(|name| same_node(name, node))
17430        {
17431            node = parent;
17432            continue;
17433        }
17434        if matches!(
17435            parent.kind(),
17436            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
17437        ) && (parent
17438            .child_by_field_name("scope")
17439            .is_some_and(|scope| same_node(scope, node))
17440            || parent
17441                .child_by_field_name("name")
17442                .is_some_and(|name| same_node(name, node)))
17443            && parent.end_byte() == node.end_byte()
17444        {
17445            node = parent;
17446            continue;
17447        }
17448        return node;
17449    }
17450}
17451
17452pub fn normalize_type_text(value: &str) -> String {
17453    strip_tag_type_prefix(
17454        normalize_cpp_whitespace(value)
17455            .trim_start_matches("const ")
17456            .trim_end_matches('*')
17457            .trim_end_matches('&')
17458            .trim(),
17459    )
17460    .to_string()
17461}
17462
17463use crate::declarations::strip_tag_type_prefix;
17464
17465pub fn normalize_reference_name(value: &str) -> Option<String> {
17466    let normalized = normalize_cpp_reference_text(value);
17467    (!normalized.is_empty()).then_some(normalized)
17468}
17469
17470pub fn normalize_cpp_reference_text(value: &str) -> String {
17471    let mut text = normalize_cpp_whitespace(value)
17472        .trim_start_matches("new ")
17473        .trim()
17474        .to_string();
17475    if let Some(index) = text.find(['(', '{']) {
17476        text.truncate(index);
17477    }
17478    if let Some(index) = text.find('<') {
17479        text.truncate(index);
17480    }
17481    let normalized = text
17482        .trim()
17483        .trim_start_matches("const ")
17484        .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
17485        .trim_matches(':')
17486        .trim();
17487    strip_tag_type_prefix(normalized).to_string()
17488}
17489
17490pub fn cpp_name_for(unit: &CodeUnit) -> String {
17491    let short = unit.short_name().replace(['.', '$'], "::");
17492    if unit.package_name().is_empty() {
17493        short
17494    } else {
17495        format!("{}::{}", unit.package_name(), short)
17496    }
17497}
17498
17499/// Render an indexed C++ qualified name from its authoritative FqName
17500/// segments. Unlike the legacy `cpp_name_for` renderer, this preserves dots
17501/// that belong to a template argument (for example `Args...`).
17502fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
17503    let fq = unit.fq();
17504    if fq.is_empty() {
17505        return None;
17506    }
17507    let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
17508    Some(
17509        fq.segments()
17510            .iter()
17511            .map(|&segment| interner.resolve(segment).0)
17512            .collect::<Vec<_>>()
17513            .join("::"),
17514    )
17515}
17516
17517fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
17518    canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
17519        || unit.fq().is_empty() && cpp_name_for(unit) == expected
17520}
17521
17522/// Return the indexed C++ owner scope without reparsing its rendered name.
17523///
17524/// Template spellings are opaque within an indexed `FqName` segment.  In
17525/// particular, the ellipsis in a parameter pack (`Args...`) is part of the
17526/// `AtomicHook<...>` type segment; feeding the legacy all-`::` rendering back
17527/// through `parse_symbol_path` would mistake those dots for component
17528/// separators.  Cache-loaded/legacy units may still have an empty structured
17529/// name, so retain the parser only as that explicit fallback.
17530pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
17531    let fq = unit.fq();
17532    if !fq.is_empty() {
17533        let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
17534        let scope = fq
17535            .segments()
17536            .iter()
17537            .filter_map(|&segment| {
17538                let (text, kind) = interner.resolve(segment);
17539                matches!(
17540                    kind,
17541                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
17542                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
17543                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
17544                )
17545                .then(|| text.to_string())
17546            })
17547            .collect();
17548        return scope;
17549    }
17550    brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
17551        brokk_bifrost_core::analyzer::Language::Cpp,
17552        &cpp_name_for(unit),
17553    )
17554}
17555
17556// fqname-M4: the second stage splits on the individual chars '.', '-', '>'
17557// (not the substring "->"), which deliberately reduces an `operator->`-style
17558// terminal segment to an empty tail rather than keeping it intact; the shared
17559// structured splitter's cpp operator-token merge would keep `operator->`
17560// whole instead, changing this function's result — `name_matches_callable`'s
17561// `expected.starts_with("operator")` fallback exists specifically to
17562// compensate for that reduction, and a pinned regression test
17563// (`operator-> must not be reduced with terminal_name-style punctuation
17564// splitting`) asserts today's char-class behavior. Not equivalence-provable;
17565// revisit alongside that pinned test if it is ever relaxed.
17566pub fn terminal_name(value: &str) -> &str {
17567    value
17568        .rsplit("::")
17569        .next()
17570        .unwrap_or(value)
17571        .rsplit(['.', '-', '>'])
17572        .next()
17573        .unwrap_or(value)
17574        .trim()
17575}
17576
17577pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
17578    terminal_name(&normalize_cpp_reference_text(value)) == expected
17579}
17580
17581pub fn name_matches_callable(value: &str, expected: &str) -> bool {
17582    name_matches_terminal(value, expected)
17583        || expected.starts_with("operator")
17584            && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
17585}
17586
17587pub fn name_mentions(value: &str, expected: &str) -> bool {
17588    normalize_cpp_reference_text(value)
17589        .split("::")
17590        .any(|part| part == expected)
17591}
17592
17593pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
17594    let cpp_name = cpp_name_for(unit);
17595    if reference.contains("::") {
17596        return reference == cpp_name;
17597    }
17598    reference == cpp_name
17599        || terminal_name(reference) == unit.identifier()
17600            && (unit.package_name().is_empty() || reference == unit.identifier())
17601}
17602
17603pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
17604    match kind {
17605        TargetKind::Type
17606        | TargetKind::Constructor
17607        | TargetKind::Method
17608        | TargetKind::MemberField => true,
17609        TargetKind::FreeFunction => unit.is_function(),
17610        TargetKind::GlobalField => unit.is_field(),
17611        TargetKind::Macro => unit.is_macro(),
17612    }
17613}
17614
17615fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
17616    let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
17617    let target_name = cpp_name_for(target);
17618    if normalized.contains("::") {
17619        return normalized == target_name;
17620    }
17621    if let Some(namespace) = alias.namespace.as_deref() {
17622        return namespace_prefixes(namespace)
17623            .into_iter()
17624            .any(|prefix| format!("{prefix}::{normalized}") == target_name);
17625    }
17626    target.package_name().is_empty() && normalized == target.identifier()
17627}
17628
17629/// The declared return type text of a C++ function unit, with leading declaration specifiers
17630/// stripped, e.g. `T*` for `T* operator->()`.
17631pub fn cpp_function_return_type_text(
17632    analyzer: &CppGraphSource<'_>,
17633    function: &CodeUnit,
17634) -> Option<String> {
17635    let metadata = analyzer.signature_metadata(function);
17636    let first = metadata.first()?.return_type_text()?;
17637    metadata
17638        .iter()
17639        .all(|metadata| metadata.return_type_text() == Some(first))
17640        .then(|| first.to_owned())
17641}
17642
17643pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
17644    // fqname-M4: `cpp_name_for` is a bespoke all-`::` rendering of the unit's
17645    // name (it replaces every `.`/`$` in `short_name` with `::`), which is NOT
17646    // the same string `default_parent_fq_name`/`fq().parent()` would render:
17647    // the structured `FqName`'s native cpp display deliberately keeps `.` (not
17648    // `::`) between a trailing `Package` segment and a following `Type`
17649    // segment (see `separator` in `fq_name.rs`, landed for issue #1163), so
17650    // popping the unit's own `fq()` segment would NOT reproduce this
17651    // fully-`::`-joined string. Left as a split on the locally-built
17652    // all-colon string rather than the unit's structured name.
17653    cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
17654        namespace
17655            .strip_prefix("anonymous_namespace::")
17656            .unwrap_or(namespace)
17657            .to_string()
17658    })
17659}
17660
17661/// The namespace lookup tiers, from the innermost namespace outward.
17662pub fn namespace_prefixes(namespace: &str) -> Vec<String> {
17663    // `namespace` is built by `cpp_name_for`/`cpp_namespace_for` with every
17664    // non-`::` separator already converted to `::`, so re-tokenizing it with
17665    // the shared structured splitter and progressively popping the last
17666    // component reproduces the `rsplit_once("::")` outward walk exactly (same
17667    // shape as `cpp_qualifier_lookup_tiers`'s namespace-chain walk).
17668    let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
17669        brokk_bifrost_core::analyzer::Language::Cpp,
17670        namespace,
17671    );
17672    let mut prefixes = Vec::new();
17673    while !parts.is_empty() {
17674        prefixes.push(parts.join("::"));
17675        parts.pop();
17676    }
17677    prefixes
17678}
17679
17680fn nearest_namespace_candidates(
17681    candidates: Vec<CodeUnit>,
17682    normalized: &str,
17683    lexical_namespace: Option<&str>,
17684) -> Vec<CodeUnit> {
17685    if normalized.contains("::") {
17686        return candidates;
17687    }
17688    if let Some(namespace) = lexical_namespace {
17689        for prefix in namespace_prefixes(namespace) {
17690            let scoped = candidates
17691                .iter()
17692                .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
17693                .cloned()
17694                .collect::<Vec<_>>();
17695            if !scoped.is_empty() {
17696                return scoped;
17697            }
17698        }
17699    }
17700    candidates
17701        .into_iter()
17702        .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
17703        .collect()
17704}
17705
17706pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
17707    let mut namespaces = Vec::new();
17708    for parent in enclosing_cpp_namespace_nodes(node) {
17709        if let Some(name) = parent.child_by_field_name("name") {
17710            let namespace = normalize_cpp_reference_text(node_text(name, source));
17711            if !namespace.is_empty() {
17712                namespaces.push(namespace);
17713            }
17714        }
17715    }
17716    if namespaces.is_empty() {
17717        None
17718    } else {
17719        Some(namespaces.join("::"))
17720    }
17721}
17722
17723/// Like [`precise_parent_of`], but drops module (namespace) parents. A namespace is a scope, not a
17724/// type or receiver, so namespace-scoped functions and constants resolve as free functions and
17725/// globals rather than members.
17726pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
17727    type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
17728}
17729
17730fn type_owner_resolution(
17731    analyzer: &CppGraphSource<'_>,
17732    code_unit: &CodeUnit,
17733) -> Option<ResolvedTypeOwner> {
17734    precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
17735}
17736
17737fn target_type_owner_resolution(
17738    analyzer: &CppGraphSource<'_>,
17739    code_unit: &CodeUnit,
17740) -> Option<ResolvedTypeOwner> {
17741    match type_owner_resolution(analyzer, code_unit) {
17742        Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
17743        Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
17744    }
17745}
17746
17747/// Recover method identity for an indexed out-of-line definition when the
17748/// ordinary parent edge is absent. Prefer the unique include-visible forward
17749/// declaration, then classify exact-FQN class declarations elsewhere in the
17750/// workspace. A unique complete declaration wins; otherwise multiple forward
17751/// declarations are one owner only when they all share one logical identity.
17752/// The qualified callable FQN proves that owner spelling even when its defining
17753/// header is outside the scan file's include closure, while unknown or competing
17754/// complete declarations remain ambiguous.
17755/// This is deliberately target-only: canonical declaration resolution must
17756/// continue to prefer the callable definition rather than replacing it with
17757/// the recovered owner.
17758fn target_forward_owner_resolution(
17759    analyzer: &CppGraphSource<'_>,
17760    code_unit: &CodeUnit,
17761) -> Option<ResolvedTypeOwner> {
17762    if !code_unit.is_function() {
17763        return None;
17764    }
17765    // A top-level free function has no owner at all, and `FqName::parent`
17766    // answers the empty name rather than `None` for a one-segment identity.
17767    // `default_parent_fq_name`, which this replaced, filtered that case out;
17768    // asking the relational store for the empty name is a batch error that
17769    // fails the whole target frontier.
17770    let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
17771    let cpp = analyzer.cpp?;
17772    let mut visible_files = HashSet::default();
17773    collect_include_closure(
17774        analyzer,
17775        cpp.include_target_index(),
17776        code_unit.source(),
17777        &mut visible_files,
17778        None,
17779    );
17780    let candidates = analyzer.workspace_definitions().exact(&owner_name);
17781    let visible_candidates = candidates
17782        .iter()
17783        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
17784        .cloned()
17785        .collect::<Vec<_>>();
17786    match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
17787        DirectOwnerResolution::UniqueFull(unit) => {
17788            return Some(ResolvedTypeOwner {
17789                unit,
17790                is_forward_declaration: false,
17791            });
17792        }
17793        DirectOwnerResolution::ForwardsOnly(forwards) => {
17794            return (forwards.len() == 1).then(|| ResolvedTypeOwner {
17795                unit: forwards.into_iter().next().unwrap(),
17796                is_forward_declaration: true,
17797            });
17798        }
17799        DirectOwnerResolution::Ambiguous => return None,
17800        DirectOwnerResolution::None => {}
17801    }
17802
17803    let candidates = candidates
17804        .into_iter()
17805        .filter(|candidate| candidate.is_class())
17806        .collect::<Vec<_>>();
17807    let (unit, is_forward_declaration) =
17808        match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
17809            DirectOwnerResolution::UniqueFull(unit) => (unit, false),
17810            DirectOwnerResolution::ForwardsOnly(forwards) => {
17811                (unique_logical_forward_owner(forwards)?, true)
17812            }
17813            DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
17814        };
17815    Some(ResolvedTypeOwner {
17816        unit,
17817        is_forward_declaration,
17818    })
17819}
17820
17821pub fn precise_parent_of(
17822    analyzer: &CppGraphSource<'_>,
17823    visibility: &VisibilityIndex<'_>,
17824    code_unit: &CodeUnit,
17825) -> Option<CodeUnit> {
17826    visibility.cached_precise_parent_of(analyzer, code_unit)
17827}
17828
17829fn precise_parent_resolution(
17830    analyzer: &CppGraphSource<'_>,
17831    code_unit: &CodeUnit,
17832) -> Option<ResolvedTypeOwner> {
17833    #[cfg(any(test, feature = "test-support"))]
17834    if let Some(cpp) = analyzer.cpp {
17835        cpp.record_cpp_parent_resolution_for_test();
17836    }
17837    if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
17838        return Some(ResolvedTypeOwner {
17839            unit,
17840            is_forward_declaration: false,
17841        });
17842    }
17843    let fallback = analyzer.parent_of(code_unit);
17844    if !code_unit.owner_is_type_scope() {
17845        return fallback.map(|unit| ResolvedTypeOwner {
17846            unit,
17847            is_forward_declaration: false,
17848        });
17849    }
17850    let owner_fq = code_unit
17851        .fq()
17852        .parent()
17853        .expect("a unit with an owner identifier has a structured parent");
17854    let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
17855    match same_source_owner(analyzer, code_unit, &owner_candidates) {
17856        DirectOwnerResolution::UniqueFull(owner) => {
17857            return Some(ResolvedTypeOwner {
17858                unit: owner,
17859                is_forward_declaration: false,
17860            });
17861        }
17862        DirectOwnerResolution::Ambiguous => return None,
17863        DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
17864    }
17865    match directly_included_owner(analyzer, code_unit, &owner_candidates) {
17866        DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
17867            unit: owner,
17868            is_forward_declaration: false,
17869        }),
17870        DirectOwnerResolution::Ambiguous => None,
17871        DirectOwnerResolution::ForwardsOnly(forwards) => {
17872            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17873                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17874                    unit: owner,
17875                    is_forward_declaration: false,
17876                }),
17877                FullOwnerResolution::None => {
17878                    unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
17879                        unit,
17880                        is_forward_declaration: true,
17881                    })
17882                }
17883                FullOwnerResolution::Ambiguous => None,
17884            }
17885        }
17886        DirectOwnerResolution::None => {
17887            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17888                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17889                    unit: owner,
17890                    is_forward_declaration: false,
17891                }),
17892                FullOwnerResolution::Ambiguous => None,
17893                FullOwnerResolution::None => fallback
17894                    .filter(|parent| {
17895                        parent.source() == code_unit.source()
17896                            && parent.fq() == &owner_fq
17897                            && (!parent.is_class()
17898                                || cpp_class_declaration_strength(analyzer, parent)
17899                                    == CppClassDeclarationStrength::Full)
17900                    })
17901                    .map(|unit| ResolvedTypeOwner {
17902                        unit,
17903                        is_forward_declaration: false,
17904                    }),
17905            }
17906        }
17907    }
17908}
17909
17910fn exact_structural_type_parent(
17911    analyzer: &CppGraphSource<'_>,
17912    code_unit: &CodeUnit,
17913) -> Option<CodeUnit> {
17914    if !code_unit.is_function() && !code_unit.is_field() {
17915        return None;
17916    }
17917    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
17918    let cpp = analyzer.cpp?;
17919    let parent = cpp.structural_parent_of(code_unit)?;
17920    (!parent.is_module()
17921        && parent.source() == code_unit.source()
17922        && parent.package_name() == code_unit.package_name()
17923        && parent.short_name() == encoded_owner)
17924        .then_some(parent)
17925}
17926
17927fn same_source_owner(
17928    analyzer: &CppGraphSource<'_>,
17929    code_unit: &CodeUnit,
17930    owner_candidates: &[CodeUnit],
17931) -> DirectOwnerResolution {
17932    let candidates = owner_candidates
17933        .iter()
17934        .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
17935        .cloned()
17936        .collect::<Vec<_>>();
17937    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17938    classify_direct_owner_candidates(analyzer, candidates.into_iter())
17939}
17940
17941fn visible_full_cpp_owner(
17942    analyzer: &CppGraphSource<'_>,
17943    code_unit: &CodeUnit,
17944    owner_candidates: &[CodeUnit],
17945) -> FullOwnerResolution {
17946    let Some(cpp) = analyzer.cpp else {
17947        return FullOwnerResolution::None;
17948    };
17949    let mut visible_files = HashSet::default();
17950    collect_include_closure(
17951        analyzer,
17952        cpp.include_target_index(),
17953        code_unit.source(),
17954        &mut visible_files,
17955        None,
17956    );
17957    let candidates = owner_candidates
17958        .iter()
17959        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
17960        .cloned()
17961        .collect::<Vec<_>>();
17962    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17963    match collapse_owner_candidates(candidates.into_iter().map(|candidate| {
17964        let strength = cpp_class_declaration_strength(analyzer, &candidate);
17965        (candidate, strength)
17966    })) {
17967        DirectOwnerResolution::UniqueFull(owner) => FullOwnerResolution::Unique(owner),
17968        DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {
17969            FullOwnerResolution::None
17970        }
17971        DirectOwnerResolution::Ambiguous => FullOwnerResolution::Ambiguous,
17972    }
17973}
17974
17975pub enum DirectOwnerResolution {
17976    None,
17977    ForwardsOnly(Vec<CodeUnit>),
17978    UniqueFull(CodeUnit),
17979    Ambiguous,
17980}
17981
17982enum FullOwnerResolution {
17983    None,
17984    Unique(CodeUnit),
17985    Ambiguous,
17986}
17987
17988fn directly_included_owner(
17989    analyzer: &CppGraphSource<'_>,
17990    code_unit: &CodeUnit,
17991    owner_candidates: &[CodeUnit],
17992) -> DirectOwnerResolution {
17993    let Some(cpp) = analyzer.cpp else {
17994        return DirectOwnerResolution::None;
17995    };
17996    let direct_includes: HashSet<ProjectFile> = analyzer
17997        .canonical_include_paths(code_unit.source())
17998        .unwrap_or_default()
17999        .into_iter()
18000        .flat_map(|include| {
18001            resolve_include_targets_with_index(
18002                code_unit.source(),
18003                &include,
18004                cpp.include_target_index(),
18005            )
18006        })
18007        .collect();
18008    let candidates = owner_candidates
18009        .iter()
18010        .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
18011        .cloned()
18012        .collect::<Vec<_>>();
18013    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
18014    classify_direct_owner_candidates(analyzer, candidates.into_iter())
18015}
18016
18017fn prefer_member_declaring_owners(
18018    analyzer: &CppGraphSource<'_>,
18019    member: &CodeUnit,
18020    candidates: Vec<CodeUnit>,
18021) -> Vec<CodeUnit> {
18022    let matching = candidates
18023        .iter()
18024        .filter(|owner| owner_declares_member(analyzer, owner, member))
18025        .cloned()
18026        .collect::<Vec<_>>();
18027    if matching.is_empty() {
18028        candidates
18029    } else {
18030        matching
18031    }
18032}
18033
18034fn owner_declares_member(
18035    analyzer: &CppGraphSource<'_>,
18036    owner: &CodeUnit,
18037    member: &CodeUnit,
18038) -> bool {
18039    analyzer.direct_children(owner).into_iter().any(|child| {
18040        child.kind() == member.kind()
18041            && child.identifier() == member.identifier()
18042            && child.signature() == member.signature()
18043    })
18044}
18045
18046fn classify_direct_owner_candidates(
18047    analyzer: &CppGraphSource<'_>,
18048    candidates: impl Iterator<Item = CodeUnit>,
18049) -> DirectOwnerResolution {
18050    collapse_owner_candidates(candidates.map(|candidate| {
18051        let strength = cpp_class_declaration_strength(analyzer, &candidate);
18052        (candidate, strength)
18053    }))
18054}
18055
18056pub fn collapse_owner_candidates(
18057    candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
18058) -> DirectOwnerResolution {
18059    let mut full_definition = None;
18060    let mut forwards = Vec::new();
18061    for (candidate, strength) in candidates {
18062        match strength {
18063            CppClassDeclarationStrength::Full if full_definition.is_some() => {
18064                return DirectOwnerResolution::Ambiguous;
18065            }
18066            CppClassDeclarationStrength::Full => full_definition = Some(candidate),
18067            CppClassDeclarationStrength::Forward => forwards.push(candidate),
18068            CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
18069        }
18070    }
18071    if let Some(owner) = full_definition {
18072        DirectOwnerResolution::UniqueFull(owner)
18073    } else if !forwards.is_empty() {
18074        DirectOwnerResolution::ForwardsOnly(forwards)
18075    } else {
18076        DirectOwnerResolution::None
18077    }
18078}
18079
18080#[cfg(any(test, feature = "test-support"))]
18081pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
18082    unique_logical_forward_owner(forwards)
18083}
18084
18085fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
18086    let first = forwards.pop()?;
18087    forwards
18088        .iter()
18089        .all(|forward| same_logical_symbol(forward, &first))
18090        .then_some(first)
18091}
18092
18093pub fn cpp_class_declaration_strength(
18094    analyzer: &CppGraphSource<'_>,
18095    candidate: &CodeUnit,
18096) -> CppClassDeclarationStrength {
18097    // The answer is a pure function of the unit's ranges and its file's tree,
18098    // and the inverse scan asks it once per declaration seed. On a translation
18099    // unit the parser could not fully recover, each ask re-derives the
18100    // export-macro recovery shapes from the file's `ERROR` subtrees, so without
18101    // this memo one file's scan is quadratic in its own size: 97% of Catch2's
18102    // 284 s inverse scan of `extras/catch_amalgamated.cpp` was in this call
18103    // (#1496).
18104    let Some(cpp) = analyzer.cpp else {
18105        return uncached_cpp_class_declaration_strength(analyzer, candidate);
18106    };
18107    if let Some(strength) = cpp.cached_class_declaration_strength(candidate) {
18108        return strength;
18109    }
18110    let strength = uncached_cpp_class_declaration_strength(analyzer, candidate);
18111    cpp.cache_class_declaration_strength(candidate, strength);
18112    strength
18113}
18114
18115fn uncached_cpp_class_declaration_strength(
18116    analyzer: &CppGraphSource<'_>,
18117    candidate: &CodeUnit,
18118) -> CppClassDeclarationStrength {
18119    if let Some(facts) = analyzer.declaration_source_properties(candidate) {
18120        if facts
18121            .iter()
18122            .any(|fact| fact.class_strength == CppClassDeclarationStrength::Full)
18123        {
18124            return CppClassDeclarationStrength::Full;
18125        }
18126        if facts
18127            .iter()
18128            .any(|fact| fact.class_strength == CppClassDeclarationStrength::Forward)
18129        {
18130            return CppClassDeclarationStrength::Forward;
18131        }
18132    }
18133    if let Some(cpp) = analyzer.cpp
18134        && let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source())
18135    {
18136        return cpp_class_declaration_strength_in_tree(
18137            analyzer,
18138            &cpp.recovered_export_class_index(analyzer.token, candidate.source()),
18139            candidate,
18140            prepared.source(),
18141            prepared.tree().root_node(),
18142        );
18143    }
18144    let Some(source) = analyzer.indexed_source(candidate.source()) else {
18145        return CppClassDeclarationStrength::Unknown;
18146    };
18147    let mut parser = Parser::new();
18148    if parser
18149        .set_language(&tree_sitter_cpp::LANGUAGE.into())
18150        .is_err()
18151    {
18152        return CppClassDeclarationStrength::Unknown;
18153    }
18154    let Some(tree) = parser.parse(&source, None) else {
18155        return CppClassDeclarationStrength::Unknown;
18156    };
18157    // This branch reparses a file the analyzer has no prepared tree for, so its
18158    // recovery index is that one tree's and cannot be shared.
18159    let recovered_export_classes =
18160        CppRecoveredExportClassIndex::build(tree.root_node(), source.as_str());
18161    cpp_class_declaration_strength_in_tree(
18162        analyzer,
18163        &recovered_export_classes,
18164        candidate,
18165        &source,
18166        tree.root_node(),
18167    )
18168}
18169
18170fn cpp_class_declaration_strength_in_tree(
18171    analyzer: &CppGraphSource<'_>,
18172    recovered_export_classes: &CppRecoveredExportClassIndex,
18173    candidate: &CodeUnit,
18174    source: &str,
18175    root: Node<'_>,
18176) -> CppClassDeclarationStrength {
18177    let ranges = analyzer.ranges(candidate);
18178    let mut saw_forward = false;
18179    for range in ranges {
18180        // The recovered export-macro shapes answer for their own ranges; only a
18181        // range no recovery claims is read as a plain specifier.
18182        match recovered_class_body_at(
18183            recovered_export_classes,
18184            root,
18185            source,
18186            candidate.identifier(),
18187            &range,
18188        ) {
18189            Some(true) => return CppClassDeclarationStrength::Full,
18190            Some(false) => {
18191                saw_forward = true;
18192                continue;
18193            }
18194            None => {}
18195        }
18196        // Only a node covering the range's start byte can be the specifier for
18197        // this range, so apply that test where nodes enter the stack rather
18198        // than where they leave it. Pushing first meant one ask enqueued every
18199        // sibling at every level it descended, which on a translation unit with
18200        // thousands of top-level declarations is a per-ask cost proportional to
18201        // the file (#1496).
18202        let covers_range_start = |node: &Node<'_>| {
18203            node.start_byte() <= range.start_byte && node.end_byte() >= range.start_byte
18204        };
18205        let mut stack = Vec::new();
18206        if covers_range_start(&root) {
18207            stack.push(root);
18208        }
18209        while let Some(node) = stack.pop() {
18210            if node.start_byte() == range.start_byte
18211                && node.end_byte() == range.end_byte
18212                && matches!(
18213                    node.kind(),
18214                    "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
18215                )
18216            {
18217                if cpp_class_node_has_body(node) {
18218                    return CppClassDeclarationStrength::Full;
18219                }
18220                saw_forward = true;
18221            }
18222            let mut cursor = node.walk();
18223            stack.extend(node.named_children(&mut cursor).filter(covers_range_start));
18224        }
18225    }
18226    if saw_forward {
18227        CppClassDeclarationStrength::Forward
18228    } else {
18229        CppClassDeclarationStrength::Unknown
18230    }
18231}
18232
18233fn cpp_class_node_has_body(node: Node<'_>) -> bool {
18234    child_by_field_or_named_child_of_kinds(
18235        node,
18236        "body",
18237        &[
18238            "declaration_list",
18239            "field_declaration_list",
18240            "enumerator_list",
18241        ],
18242    )
18243    .is_some()
18244}
18245
18246#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18247enum CppCTagKind {
18248    Struct,
18249    Union,
18250}
18251
18252fn indexed_c_tag_kind(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CppCTagKind> {
18253    let declaration = analyzer.get_source(code_unit, false)?;
18254    let mut parser = Parser::new();
18255    parser
18256        .set_language(&tree_sitter_cpp::LANGUAGE.into())
18257        .ok()?;
18258    let tree = parser.parse(&declaration, None)?;
18259    let mut stack = vec![tree.root_node()];
18260    while let Some(node) = stack.pop() {
18261        let kind = match node.kind() {
18262            "struct_specifier" => CppCTagKind::Struct,
18263            "union_specifier" => CppCTagKind::Union,
18264            _ => {
18265                let mut cursor = node.walk();
18266                stack.extend(node.named_children(&mut cursor));
18267                continue;
18268            }
18269        };
18270        if node
18271            .child_by_field_name("name")
18272            .is_some_and(|name| node_text(name, &declaration) == code_unit.identifier())
18273        {
18274            return Some(kind);
18275        }
18276        let mut cursor = node.walk();
18277        stack.extend(node.named_children(&mut cursor));
18278    }
18279    None
18280}
18281
18282pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
18283    if !code_unit.owner_is_type_scope() {
18284        return None;
18285    }
18286    let owner_fq = code_unit.fq().parent()?;
18287    ctx.analyzer
18288        .workspace_definitions()
18289        .exact(&owner_fq)
18290        .into_iter()
18291        .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
18292}
18293
18294pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18295    same_logical_symbol(left, right) && left.source() == right.source()
18296}
18297
18298pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18299    same_symbol(left, right) || same_logical_symbol(left, right)
18300}
18301
18302pub fn same_visible_global_field_symbol(
18303    analyzer: &CppGraphSource<'_>,
18304    internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
18305    left: &CodeUnit,
18306    right: &CodeUnit,
18307) -> bool {
18308    if same_symbol(left, right) {
18309        return true;
18310    }
18311    if !same_logical_symbol(left, right) {
18312        return false;
18313    }
18314    if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
18315        || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
18316    {
18317        left.source() == right.source()
18318    } else {
18319        true
18320    }
18321}
18322
18323fn cpp_global_field_has_internal_linkage_cached(
18324    analyzer: &CppGraphSource<'_>,
18325    cache: &mut HashMap<CodeUnit, bool>,
18326    candidate: &CodeUnit,
18327) -> bool {
18328    if let Some(internal) = cache.get(candidate) {
18329        return *internal;
18330    }
18331    #[cfg(any(test, feature = "test-support"))]
18332    note_cpp_global_field_internal_linkage_classification_for_test();
18333    let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
18334    cache.insert(candidate.clone(), internal);
18335    internal
18336}
18337
18338#[cfg(any(test, feature = "test-support"))]
18339thread_local! {
18340    static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
18341}
18342
18343#[cfg(any(test, feature = "test-support"))]
18344fn note_cpp_global_field_internal_linkage_classification_for_test() {
18345    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
18346        count.set(count.get() + 1);
18347    });
18348}
18349
18350#[cfg(any(test, feature = "test-support"))]
18351pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
18352    body: impl FnOnce() -> T,
18353) -> (T, usize) {
18354    with_thread_local_counter_for_test(
18355        &CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST,
18356        body,
18357    )
18358}
18359
18360#[cfg(any(test, feature = "test-support"))]
18361fn with_thread_local_counter_for_test<T>(
18362    counter: &'static std::thread::LocalKey<Cell<usize>>,
18363    body: impl FnOnce() -> T,
18364) -> (T, usize) {
18365    counter.with(|count| {
18366        count.set(0);
18367        let result = body();
18368        let observed = count.get();
18369        count.set(0);
18370        (result, observed)
18371    })
18372}
18373
18374pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18375    left.kind() == right.kind()
18376        && left.fq_name() == right.fq_name()
18377        && left.signature() == right.signature()
18378}
18379
18380/// Select one visible spelling from an alias family only when its members
18381/// agree on one structured target. A single candidate needs no target proof.
18382pub fn select_alias_spelling_with_agreed_targets<'a, T: Eq>(
18383    candidates: &[&'a CodeUnit],
18384    mut structured_target: impl FnMut(&CodeUnit) -> Option<T>,
18385) -> Option<&'a CodeUnit> {
18386    let first = *candidates.first()?;
18387    if candidates.len() == 1 {
18388        return Some(first);
18389    }
18390    let first_target = structured_target(first)?;
18391    candidates
18392        .iter()
18393        .skip(1)
18394        .all(|candidate| structured_target(candidate).is_some_and(|target| target == first_target))
18395        .then_some(first)
18396}
18397
18398pub fn cpp_global_field_has_internal_linkage(
18399    analyzer: &CppGraphSource<'_>,
18400    candidate: &CodeUnit,
18401) -> bool {
18402    if !candidate.is_field() || candidate.short_name().contains('.') {
18403        return false;
18404    }
18405    let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
18406        return false;
18407    };
18408    match local_linkage {
18409        CppFieldLinkage::Internal => true,
18410        CppFieldLinkage::External => false,
18411        CppFieldLinkage::InternalUnlessExternalPeer => {
18412            !cpp_global_field_linkage_peers(analyzer, candidate)
18413                .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
18414                .any(|linkage| matches!(linkage, CppFieldLinkage::External))
18415        }
18416    }
18417}
18418
18419fn cpp_global_field_linkage_peers<'a>(
18420    analyzer: &CppGraphSource<'a>,
18421    candidate: &'a CodeUnit,
18422) -> impl Iterator<Item = CodeUnit> + 'a {
18423    let name = candidate.fq().clone();
18424    analyzer
18425        .workspace_definitions()
18426        .exact(&name)
18427        .into_iter()
18428        .filter(move |peer| {
18429            if peer == candidate {
18430                return false;
18431            }
18432            #[cfg(any(test, feature = "test-support"))]
18433            note_cpp_global_field_linkage_peer_inspection_for_test();
18434            same_logical_symbol(peer, candidate)
18435        })
18436}
18437
18438#[cfg(any(test, feature = "test-support"))]
18439thread_local! {
18440    static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
18441}
18442
18443#[cfg(any(test, feature = "test-support"))]
18444fn note_cpp_global_field_linkage_peer_inspection_for_test() {
18445    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
18446        count.set(count.get() + 1);
18447    });
18448}
18449
18450#[cfg(any(test, feature = "test-support"))]
18451pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
18452    body: impl FnOnce() -> T,
18453) -> (T, usize) {
18454    with_thread_local_counter_for_test(&CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST, body)
18455}
18456
18457fn cpp_global_field_declaration_linkage(
18458    analyzer: &CppGraphSource<'_>,
18459    candidate: &CodeUnit,
18460) -> Option<CppFieldLinkage> {
18461    analyzer.cpp_field_linkage(candidate)
18462}
18463
18464/// Agreed alias target spelling captured from the declaring AST type field.
18465pub fn cpp_canonical_alias_target_text(
18466    analyzer: &CppGraphSource<'_>,
18467    unit: &CodeUnit,
18468) -> Option<String> {
18469    let facts = analyzer.declaration_source_properties(unit)?;
18470    let first = facts.first()?.alias_target_text.as_ref()?;
18471    facts
18472        .iter()
18473        .all(|fact| fact.alias_target_text.as_ref() == Some(first))
18474        .then(|| first.clone())
18475}
18476
18477#[cfg(test)]
18478mod tests {
18479    #[test]
18480    #[hardwon("05fbd05c6bd7: C++ declaration recovery regressed for this structured syntax edge")]
18481    fn issue_3089_statement_formal_at_end_of_replacement() {
18482        let parameters = vec!["handle".to_owned(), "block".to_owned()];
18483        for replacement in [
18484            "do { header_event_t* event; if ((handle)->active) block } while (0)",
18485            "do { header_event_t* event; block } while (0)",
18486        ] {
18487            assert!(
18488                super::VisibilityIndex::parse_macro_replacement_body(replacement, &parameters)
18489                    .is_some(),
18490                "{replacement}"
18491            );
18492        }
18493    }
18494    use super::*;
18495    use brokk_bifrost_test_macros::hardwon;
18496
18497    #[test]
18498    #[hardwon("2e5169325a14: C++ declaration recovery regressed for this structured syntax edge")]
18499    fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
18500        let source = "int size(void) { return sizeof(((Payload))); }\n";
18501        let mut parser = Parser::new();
18502        parser
18503            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18504            .expect("C++ grammar");
18505        let tree = parser.parse(source, None).expect("fixture tree");
18506        let start = source.find("Payload").expect("sizeof operand");
18507        let node = tree
18508            .root_node()
18509            .named_descendant_for_byte_range(start, start + "Payload".len())
18510            .expect("focused operand");
18511        let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
18512        let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
18513
18514        assert_eq!(node.kind(), "identifier");
18515        assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
18516        assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
18517    }
18518
18519    fn parse_cpp(source: &str) -> Tree {
18520        crate::diagnostics::parse_cpp_tree(source)
18521    }
18522
18523    #[test]
18524    #[hardwon("#4271: source splitting included a new-array declarator in the allocation type")]
18525    fn cpp_new_expression_type_comes_from_the_structured_type_field() {
18526        let source = "auto value = new  ns::Box<int> [ 3 ];";
18527        let tree = parse_cpp(source);
18528        let mut pending = vec![tree.root_node()];
18529        let allocation = loop {
18530            let node = pending.pop().expect("new expression in fixture");
18531            if node.kind() == "new_expression" {
18532                break node;
18533            }
18534            let mut cursor = node.walk();
18535            pending.extend(
18536                node.named_children(&mut cursor)
18537                    .collect::<Vec<_>>()
18538                    .into_iter()
18539                    .rev(),
18540            );
18541        };
18542
18543        let type_node = cpp_new_expression_type_node(allocation).expect("structured type field");
18544        assert_eq!(allocation.child_by_field_name("type"), Some(type_node));
18545        assert_eq!(node_text(type_node, source), "ns::Box<int>");
18546        assert_eq!(
18547            allocation
18548                .child_by_field_name("declarator")
18549                .expect("array declarator")
18550                .kind(),
18551            "new_declarator"
18552        );
18553
18554        let old_split = normalize_cpp_whitespace(node_text(allocation, source));
18555        let old_split = old_split
18556            .strip_prefix("new ")
18557            .unwrap_or(old_split.as_str())
18558            .split(['(', '{'])
18559            .next()
18560            .expect("old split result");
18561        assert_ne!(old_split.trim(), node_text(type_node, source));
18562    }
18563
18564    fn named_node_at<'tree>(tree: &'tree Tree, source: &str, needle: &str) -> Node<'tree> {
18565        let start = source.find(needle).expect("fixture needle");
18566        tree.root_node()
18567            .named_descendant_for_byte_range(start, start + needle.len())
18568            .expect("node at needle")
18569    }
18570
18571    fn prepared_cpp(source: &str) -> PreparedSyntaxTree {
18572        let tree = crate::diagnostics::parse_cpp_tree(source);
18573        PreparedSyntaxTree::new(
18574            PreparedSyntaxSource::Exact(Arc::from(source)),
18575            tree,
18576            compute_line_starts(source),
18577            LanguageDialect::Standard(Language::Cpp),
18578            PreparedSourceOrigin::Disk,
18579            None,
18580        )
18581    }
18582
18583    fn unresolved_include_before(source: &str, reference: &str) -> bool {
18584        let file = ProjectFile::new(std::env::temp_dir(), "issue-3078.cpp");
18585        let prepared = prepared_cpp(source);
18586        let facts = collect_structured_include_facts(&prepared);
18587        let include_targets = IncludeTargetIndex::build([&file]);
18588        has_unresolved_include_visible_before_in_prepared(
18589            &file,
18590            &prepared,
18591            &include_targets,
18592            &facts,
18593            source.find(reference).expect("reference fixture"),
18594        )
18595    }
18596
18597    #[test]
18598    #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
18599    fn unresolved_include_before_reference_is_visible() {
18600        let source = "#include \"missing.h\"\nint use = Missing;\n";
18601        assert!(unresolved_include_before(source, "Missing"));
18602    }
18603
18604    #[test]
18605    #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
18606    fn unresolved_include_after_reference_is_not_visible() {
18607        let source = "int use = Missing;\n#include \"missing.h\"\n";
18608        assert!(!unresolved_include_before(source, "Missing"));
18609    }
18610
18611    #[test]
18612    #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
18613    fn unresolved_include_in_incompatible_sibling_branch_is_not_visible() {
18614        let source = "#if FEATURE\n#include \"missing.h\"\n#else\nint use = Missing;\n#endif\n";
18615        assert!(!unresolved_include_before(source, "Missing"));
18616    }
18617
18618    #[test]
18619    #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
18620    fn unresolved_include_in_current_branch_is_visible() {
18621        let source =
18622            "#if FEATURE\n#include \"missing.h\"\nint use = Missing;\n#else\nint other;\n#endif\n";
18623        assert!(unresolved_include_before(source, "Missing"));
18624    }
18625
18626    /// Two macro-decorated class heads make tree-sitter close `detail` at the
18627    /// first class's `}`, `matchers` at the second's, and `app` at `detail`'s
18628    /// real `}`; the tail parses at translation-unit level and the two real
18629    /// closes for `matchers` and `app` land in a trailing ERROR (#1537).
18630    const STOLEN_BRACE_CASCADE: &str = r#"namespace app {
18631namespace matchers {
18632    namespace detail {
18633        class API [[nodiscard]] First {
18634        public:
18635            int value() const { return count_ + 1; }
18636        private:
18637            int count_;
18638        };
18639        class API [[nodiscard]] Second {
18640        public:
18641            int value() const { return count_ + 2; }
18642        private:
18643            int count_;
18644        };
18645    } // namespace detail
18646
18647    template <typename T>
18648    void tail_function(MatcherBase<T> const& value);
18649
18650    class TailClass {};
18651} // namespace matchers
18652} // namespace app
18653
18654struct AfterAll {};
18655"#;
18656
18657    #[test]
18658    #[hardwon("c820d8c6a8e3: C++ declaration recovery regressed for this structured syntax edge")]
18659    fn orphaned_namespace_scope_index_restores_a_stolen_brace_cascade() {
18660        let source = STOLEN_BRACE_CASCADE;
18661        let tree = parse_cpp(source);
18662        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
18663
18664        let tail_class = named_node_at(&tree, source, "TailClass");
18665        assert!(
18666            !has_ancestor_kind(tail_class, "namespace_definition"),
18667            "the fixture must reproduce the recovery: the tail has no namespace ancestor"
18668        );
18669        let displaced = named_node_at(&tree, source, "Second");
18670        assert_eq!(
18671            enclosing_namespace_components(displaced, source),
18672            Some(vec!["app".to_string(), "matchers".to_string()]),
18673            "the fixture must displace the second class out of detail"
18674        );
18675
18676        let components = |needle: &str| {
18677            index.enclosing_namespace_components(named_node_at(&tree, source, needle), source)
18678        };
18679        assert_eq!(components("First"), ["app", "matchers", "detail"]);
18680        assert_eq!(components("Second"), ["app", "matchers", "detail"]);
18681        assert_eq!(components("MatcherBase<T>"), ["app", "matchers"]);
18682        assert_eq!(components("tail_function"), ["app", "matchers"]);
18683        assert_eq!(components("TailClass"), ["app", "matchers"]);
18684        assert!(components("AfterAll").is_empty());
18685    }
18686
18687    #[test]
18688    #[hardwon("c820d8c6a8e3: C++ declaration recovery regressed for this structured syntax edge")]
18689    fn orphaned_namespace_scope_index_is_empty_without_lost_scopes() {
18690        let clean = "namespace a { namespace b { class C {}; } class D {}; }\n";
18691        let tree = parse_cpp(clean);
18692        assert!(!tree.root_node().has_error());
18693        assert!(OrphanedNamespaceScopeIndex::build(tree.root_node(), clean).is_empty());
18694
18695        // A namespace that merely contains a parse error closes where its
18696        // brace says; the declarations after it keep their parsed scope.
18697        let damaged = "namespace a { namespace b { UNKNOWN_MACRO(x) } class C {}; }\n";
18698        let tree = parse_cpp(damaged);
18699        assert!(tree.root_node().has_error());
18700        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), damaged);
18701        assert_eq!(
18702            index.enclosing_namespace_components(named_node_at(&tree, damaged, "class C"), damaged),
18703            ["a"]
18704        );
18705    }
18706
18707    /// A function-like `#define` in a class body, whose replacement returns a
18708    /// SFINAE type, makes tree-sitter give up on the whole enclosing namespace:
18709    /// `namespace app {` becomes an `ERROR` whose own `namespace`, name and `{`
18710    /// tokens are the only trace of the head, and every declaration the
18711    /// namespace holds becomes their flat sibling. This is Catch2's
18712    /// `catch_decomposer.hpp`, reduced (issue #3084).
18713    const COLLAPSED_NAMESPACE_HEAD: &str = r#"namespace app {
18714
18715    class Target {
18716        int value_;
18717    };
18718
18719    template <typename T>
18720    class Holder {
18721    public:
18722        explicit constexpr Holder( T lhs ): m_lhs( lhs ) {}
18723
18724#define HOLDER_DEFINE_OP( id, op )                                             \
18725    template <typename U>                                                      \
18726    constexpr friend auto operator op( Holder&& lhs, U&& rhs )                 \
18727        -> std::enable_if_t<is_##id##_comparable<T, U>::value, Target> {       \
18728        return Target{};                                                       \
18729    }
18730
18731        HOLDER_DEFINE_OP( equal, == )
18732#undef HOLDER_DEFINE_OP
18733        T m_lhs;
18734    };
18735
18736    class Tail {};
18737}
18738"#;
18739
18740    #[test]
18741    #[hardwon("a438f125e172: C++ declaration recovery regressed for this structured syntax edge")]
18742    fn orphaned_namespace_scope_index_names_a_collapsed_namespace_head() {
18743        let source = COLLAPSED_NAMESPACE_HEAD;
18744        let tree = parse_cpp(source);
18745        let target = named_node_at(&tree, source, "class Target");
18746
18747        assert!(
18748            !has_ancestor_kind(target, "namespace_definition"),
18749            "the fixture must reproduce the collapse: the class has no namespace ancestor"
18750        );
18751        let head = target.parent().expect("the collapsed namespace envelope");
18752        assert_eq!(
18753            head.kind(),
18754            "ERROR",
18755            "the fixture must keep the namespace head in an ERROR node"
18756        );
18757
18758        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
18759        assert_eq!(
18760            index.enclosing_namespace_components(target, source),
18761            ["app"]
18762        );
18763        assert_eq!(
18764            index.enclosing_namespace_levels(target, source),
18765            vec![(
18766                "app".to_string(),
18767                Range {
18768                    start_byte: 0,
18769                    end_byte: source.len() - 1,
18770                    start_line: 1,
18771                    end_line: 25,
18772                },
18773                Range {
18774                    start_byte: 10,
18775                    end_byte: 13,
18776                    start_line: 1,
18777                    end_line: 1,
18778                },
18779            )],
18780            "the recovered namespace owns its full construct, not its first member or just its header"
18781        );
18782    }
18783
18784    /// A macro that opens a block: `APP_IF_ACTIVE` expands to `if (active) {`,
18785    /// so the `{` of a scope the source really opens is part of the macro's
18786    /// `preproc_arg` and never becomes a `{` token. The tree therefore holds
18787    /// one more real `}` than real `{`, and a file-global stack has to assign
18788    /// those closes to the wrong opens: the `}` on line 9 takes `check`'s body,
18789    /// `check`'s own `}` on line 11 takes `detail`, line 15 takes `app`, and
18790    /// line 16 arrives with nothing open. This is the state simdjson's
18791    /// `compile_time_json-inl.h` reaches eight times over (#3328).
18792    const MACRO_OPENED_SCOPE: &str = r#"#define APP_IF_ACTIVE if (active) {
18793
18794namespace app {
18795namespace detail {
18796
18797bool check(bool active) {
18798  APP_IF_ACTIVE
18799    return true;
18800  }
18801  return false;
18802}
18803
18804class Tail {};
18805
18806} // namespace detail
18807} // namespace app
18808"#;
18809
18810    /// simdjson's `include/simdjson/compile_time_json-inl.h` at `9b33047a`,
18811    /// reduced. ABI 15 with runtime 0.25.10 now keeps each case-label character
18812    /// literal and the nested namespace definitions. The malformed function
18813    /// body still causes a missing outer namespace close. Keep the original
18814    /// source as a positive for real inner closes, without inventing the outer
18815    /// close that the parser could not read (#3328).
18816    const REFLECTION_CASE_LABEL_RECOVERY: &str = r#"namespace simdjson {
18817namespace compile_time {
18818namespace number_parsing {
18819consteval int leading_zeroes(uint64_t input_num, int last_bit = 0) {
18820    auto hex_to_u32 = [&] [[nodiscard]] () -> uint32_t {
18821      auto digit = [](uint8_t c) -> uint32_t {
18822      }
18823    }
18824    if (a) {
18825      while (a) {
18826      }
18827    }
18828    switch (c) {
18829    case '{': {
18830    }
18831    }
18832  }
18833  while (a) {
18834    cursor += field.second;
18835    if (a) {
18836    case '{': {
18837      if (a) {
18838      }
18839    }
18840    case 'n': {
18841      if (a) {
18842      }
18843    }
18844    }
18845  }
18846  if (a) {
18847    simdjson_consteval_error("Expected '}'");
18848  }
18849}
18850}
18851}
18852"#;
18853
18854    /// Real `{` and `}` tokens of a tree, the stream the index pairs.
18855    fn real_brace_token_counts(root: Node<'_>) -> (usize, usize) {
18856        let mut stack = vec![root];
18857        let mut counts = (0, 0);
18858        while let Some(node) = stack.pop() {
18859            match (node.kind(), node.is_missing()) {
18860                ("{", false) => counts.0 += 1,
18861                ("}", false) => counts.1 += 1,
18862                _ => {}
18863            }
18864            let mut cursor = node.walk();
18865            stack.extend(node.children(&mut cursor));
18866        }
18867        counts
18868    }
18869
18870    /// The open brace of `head`, which is the key its close lands under.
18871    fn namespace_open_byte(source: &str, head: &str) -> usize {
18872        let at = source.find(head).expect("fixture namespace head");
18873        at + head.len() - 1
18874    }
18875
18876    fn assert_namespace_heads_have_no_matching_close(root: Node<'_>, source: &str, heads: &[&str]) {
18877        let index = OrphanedNamespaceScopeIndex::build(root, source);
18878        for head in heads {
18879            assert_eq!(
18880                index.matching_close_brace(namespace_open_byte(source, head)),
18881                None,
18882                "{head} must not claim a close belonging to an inner scope"
18883            );
18884        }
18885    }
18886
18887    #[test]
18888    #[hardwon("1352f739be3d: C++ declaration recovery regressed for this structured syntax edge")]
18889    fn orphaned_namespace_scope_index_pairs_nothing_when_a_scope_open_is_lost() {
18890        let source = MACRO_OPENED_SCOPE;
18891        let tree = parse_cpp(source);
18892        let root = tree.root_node();
18893        assert_eq!(
18894            real_brace_token_counts(root),
18895            (4, 5),
18896            "the fixture must reproduce the loss: one scope the source opens \
18897             has no `{{` token, so the stream holds an extra real close"
18898        );
18899
18900        assert_namespace_heads_have_no_matching_close(
18901            root,
18902            source,
18903            &["namespace app {", "namespace detail {"],
18904        );
18905    }
18906
18907    #[test]
18908    #[hardwon("5312adbcf9bf: C++ declaration recovery regressed for this structured syntax edge")]
18909    fn orphaned_namespace_scope_index_leaves_a_declaration_after_every_close_at_file_scope() {
18910        // The stack proves no pairing in this file, so every restored level
18911        // keeps its best-effort scope. None of it may reach a declaration the
18912        // file writes after its last close.
18913        let source = format!("{MACRO_OPENED_SCOPE}struct AfterAll {{}};\n");
18914        let tree = parse_cpp(&source);
18915        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), &source);
18916        let after_all = named_node_at(&tree, &source, "struct AfterAll");
18917        assert!(
18918            index
18919                .enclosing_namespace_components(after_all, &source)
18920                .is_empty()
18921        );
18922    }
18923
18924    #[test]
18925    #[hardwon("1352f739be3d: C++ declaration recovery regressed for this structured syntax edge")]
18926    fn orphaned_namespace_scope_index_preserves_structured_reflection_closes() {
18927        let source = REFLECTION_CASE_LABEL_RECOVERY;
18928        let tree = parse_cpp(source);
18929        let root = tree.root_node();
18930        let mut stack = vec![root];
18931        let mut mis_lexed_opens = Vec::new();
18932        while let Some(node) = stack.pop() {
18933            if node.kind() == "character"
18934                && &source[node.start_byte()..node.end_byte()] == "{"
18935                && node
18936                    .parent()
18937                    .is_some_and(|parent| parent.kind() != "char_literal")
18938            {
18939                mis_lexed_opens.push(node.start_position().row + 1);
18940            }
18941            let mut cursor = node.walk();
18942            stack.extend(node.children(&mut cursor));
18943        }
18944        assert!(
18945            mis_lexed_opens.is_empty(),
18946            "ABI15 should keep each case-label `{{` inside its `char_literal`; \
18947             the old ABI14 mis-lex is no longer a valid recovery witness"
18948        );
18949        assert_eq!(
18950            real_brace_token_counts(root),
18951            (17, 17),
18952            "the improved parse retains balanced real brace tokens"
18953        );
18954        assert_eq!(root.kind(), "translation_unit");
18955        assert!(root.has_error(), "the malformed body remains incomplete");
18956        let index = OrphanedNamespaceScopeIndex::build(root, source);
18957        assert_eq!(
18958            index.matching_close_brace(namespace_open_byte(source, "namespace simdjson {")),
18959            None,
18960            "a missing outer close must not borrow an inner namespace's real close"
18961        );
18962        for (head, close) in [
18963            ("namespace compile_time {", source.len() - 2),
18964            ("namespace number_parsing {", source.len() - 4),
18965        ] {
18966            assert_eq!(
18967                index
18968                    .matching_close_brace(namespace_open_byte(source, head))
18969                    .map(|range| range.start_byte),
18970                Some(close),
18971                "{head} retains its own real close"
18972            );
18973        }
18974    }
18975
18976    #[test]
18977    #[hardwon("#2243: an empty namespace could hide a nested indexed owner suffix")]
18978    fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
18979        let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
18980        assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
18981        assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
18982        assert!(indexed_namespace_path_is_recoverable(
18983            &["cache".to_string()],
18984            &indexed,
18985            1,
18986        ));
18987    }
18988
18989    #[test]
18990    #[hardwon("#1876: lookup ordering was partial and changed with unrelated workspace content")]
18991    fn sort_lookup_units_totally_orders_every_identity_field() {
18992        let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
18993        let base = CodeUnit::with_signature(
18994            file.clone(),
18995            CodeUnitType::Function,
18996            "scope",
18997            "value",
18998            Some("()".to_string()),
18999            false,
19000        );
19001        let different_kind = CodeUnit::with_signature(
19002            file.clone(),
19003            CodeUnitType::Field,
19004            "scope",
19005            "value",
19006            Some("()".to_string()),
19007            false,
19008        );
19009        let synthetic = base.with_synthetic(true);
19010
19011        let interner = segment_interner();
19012        let mut member_fq = FqName::new();
19013        member_fq.push(interner.intern("scope", SegmentKind::Package));
19014        member_fq.push(interner.intern("value", SegmentKind::Member));
19015        let different_package_boundary = CodeUnit::from_fq(
19016            file.clone(),
19017            CodeUnitType::Function,
19018            member_fq,
19019            0,
19020            Some("()".to_string()),
19021            false,
19022        );
19023
19024        let mut unknown_fq = FqName::new();
19025        unknown_fq.push(interner.intern("scope", SegmentKind::Package));
19026        unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
19027        let different_segment_kind = CodeUnit::from_fq(
19028            file,
19029            CodeUnitType::Function,
19030            unknown_fq,
19031            1,
19032            Some("()".to_string()),
19033            false,
19034        );
19035
19036        let input = vec![
19037            base,
19038            different_kind,
19039            synthetic,
19040            different_package_boundary,
19041            different_segment_kind,
19042        ];
19043        let mut expected = input.clone();
19044        sort_lookup_units(&mut expected);
19045        assert!(expected.windows(2).all(|pair| {
19046            let mut ordered = pair.to_vec();
19047            sort_lookup_units(&mut ordered);
19048            ordered == pair && pair[0] != pair[1]
19049        }));
19050
19051        let mut reversed = input.clone();
19052        reversed.reverse();
19053        sort_lookup_units(&mut reversed);
19054        assert_eq!(reversed, expected);
19055
19056        let mut rotated = input;
19057        rotated.rotate_left(2);
19058        sort_lookup_units(&mut rotated);
19059        assert_eq!(rotated, expected);
19060    }
19061
19062    #[test]
19063    #[hardwon("6ffe85773652: C++ declaration recovery regressed for this structured syntax edge")]
19064    fn structured_conditional_array_bound_preserves_the_real_guard() {
19065        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";
19066        let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
19067        let parse = |source: &str| {
19068            let mut parser = Parser::new();
19069            parser
19070                .set_language(&tree_sitter_cpp::LANGUAGE.into())
19071                .expect("C++ grammar");
19072            parser.parse(source, None).expect("fixture tree")
19073        };
19074
19075        let tree = parse(damaged);
19076        let root = tree.root_node();
19077        assert!(
19078            !root.has_error(),
19079            "the native conditional-array grammar now reads the original recovery witness cleanly: {}",
19080            root.to_sexp()
19081        );
19082        let target = damaged.find("target").expect("target byte");
19083        let declaration = root
19084            .descendant_for_byte_range(target, target + "target".len())
19085            .and_then(|mut node| {
19086                loop {
19087                    if node.kind() == "declaration" {
19088                        break Some(node);
19089                    }
19090                    node = node.parent()?;
19091                }
19092            })
19093            .expect("declaration after the displaced terminator");
19094        let outer = declaration
19095            .parent()
19096            .filter(|node| node.kind() == "preproc_ifdef")
19097            .expect("ordinary outer include guard");
19098        let bound = outer
19099            .named_children(&mut outer.walk())
19100            .filter(|node| node.kind() == "declaration")
19101            .filter_map(|node| node.child_by_field_name("declarator"))
19102            .find(|node| node.kind() == "array_declarator")
19103            .and_then(|node| node.child_by_field_name("size"))
19104            .expect("structured conditional array bound");
19105        assert_eq!(bound.kind(), "preproc_array_size");
19106        let conditional = bound
19107            .named_children(&mut bound.walk())
19108            .find(|node| node.kind() == "preproc_ifdef")
19109            .expect("array-size conditional");
19110        assert!(conditional.end_byte() <= declaration.start_byte());
19111        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
19112        assert!(
19113            !std::iter::successors(Some(declaration), |node| node.parent())
19114                .any(|ancestor| same_node(ancestor, conditional)),
19115            "the declaration must not inherit the array-size feature guard"
19116        );
19117        assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
19118        assert!(preprocessor_conditional_contains_descendant(
19119            outer,
19120            declaration
19121        ));
19122
19123        let tree = parse(guarded);
19124        let conditional = tree
19125            .root_node()
19126            .named_child(0)
19127            .filter(|node| node.kind() == "preproc_ifdef")
19128            .expect("ordinary conditional");
19129        let declaration = conditional
19130            .named_children(&mut conditional.walk())
19131            .find(|node| node.kind() == "declaration")
19132            .expect("guarded declaration");
19133        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
19134        assert!(preprocessor_conditional_contains_descendant(
19135            conditional,
19136            declaration
19137        ));
19138
19139        let damaged_alternative = format!(
19140            "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
19141            "UNUSED(value)\n".repeat(64)
19142        );
19143        let tree = parse(&damaged_alternative);
19144        let conditional = tree
19145            .root_node()
19146            .named_child(0)
19147            .filter(|node| node.kind() == "preproc_ifdef")
19148            .expect("outer conditional with an alternative");
19149        assert!(conditional.has_error());
19150        assert!(conditional.child_by_field_name("alternative").is_some());
19151        assert!(
19152            conditional
19153                .child(conditional.child_count() - 1)
19154                .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
19155        );
19156        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
19157
19158        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";
19159        let tree = parse(split_declaration);
19160        let root = tree.root_node();
19161        let conditional = root
19162            .named_children(&mut root.walk())
19163            .find(|node| node.kind() == "preproc_ifdef" && node.start_position().row == 3)
19164            .expect("split declaration conditional");
19165        let target = split_declaration
19166            .find("static int target")
19167            .expect("target byte");
19168        let boundary =
19169            cpp_displaced_preprocessor_boundary(conditional).expect("split declaration boundary");
19170        assert!(boundary.end_byte <= target, "{boundary:?}");
19171        assert_eq!(boundary.end_line, 9, "{boundary:?}");
19172        let target_node = root
19173            .descendant_for_byte_range(target, target + "static".len())
19174            .expect("target node");
19175        assert!(!preprocessor_conditional_contains_descendant(
19176            conditional,
19177            target_node
19178        ));
19179    }
19180
19181    #[test]
19182    #[hardwon("6ba7704de3ed: C++ declaration recovery regressed for this structured syntax edge")]
19183    fn fragmented_reference_guard_is_recovered() {
19184        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";
19185        let mut parser = Parser::new();
19186        parser
19187            .set_language(&tree_sitter_cpp::LANGUAGE.into())
19188            .expect("C++ grammar");
19189        let tree = parser.parse(source, None).expect("fixture tree");
19190        let start = source.rfind("helper").expect("reference byte");
19191        let node = tree
19192            .root_node()
19193            .descendant_for_byte_range(start, start + "helper".len())
19194            .expect("reference node");
19195        let mut expected = HashSet::default();
19196        expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
19197            vec![
19198                BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
19199                BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
19200            ],
19201        )));
19202        assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
19203    }
19204
19205    #[test]
19206    fn expression_defined_and_ifndef_guards_are_incompatible() {
19207        let source = "#if defined(WIN_MODE)\nint selected;\n#endif\n#ifndef WIN_MODE\nint rejected;\n#endif\n";
19208        let mut parser = Parser::new();
19209        parser
19210            .set_language(&tree_sitter_cpp::LANGUAGE.into())
19211            .expect("C++ grammar");
19212        let tree = parser.parse(source, None).expect("fixture tree");
19213        let root = tree.root_node();
19214        let selected_start = source.find("selected").expect("selected declaration");
19215        let rejected_start = source.find("rejected").expect("rejected declaration");
19216        let selected = root
19217            .descendant_for_byte_range(selected_start, selected_start + "selected".len())
19218            .expect("selected node");
19219        let rejected = root
19220            .descendant_for_byte_range(rejected_start, rejected_start + "rejected".len())
19221            .expect("rejected node");
19222        let selected_guards =
19223            preprocessor_guard_environment(selected, source).expect("selected guards");
19224        let rejected_guards =
19225            preprocessor_guard_environment(rejected, source).expect("rejected guards");
19226
19227        assert!(
19228            merge_preprocessor_guards(&selected_guards, &rejected_guards).is_none(),
19229            "opposite spellings of one macro guard must contradict"
19230        );
19231    }
19232
19233    #[test]
19234    #[hardwon("02b3765c2a06: C++ declaration recovery regressed for this structured syntax edge")]
19235    fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
19236        let source = r#"#ifdef _WIN32
19237#if defined(__cplusplus)
19238extern "C"
19239#endif
19240int platform_api(void);
19241#endif
19242
19243#ifdef _WIN32
19244static int entropy_target(void) { return 0; }
19245#else
19246#ifdef HAVE_COMMON_RANDOM
19247static int other_target(void) { return 0; }
19248#elif defined(HAVE_GETENTROPY)
19249static int entropy_target(void) { return 1; }
19250static int use_entropy(void) { return entropy_target(); }
19251#endif
19252#endif
19253"#;
19254        let mut parser = Parser::new();
19255        parser
19256            .set_language(&tree_sitter_cpp::LANGUAGE.into())
19257            .expect("C++ grammar");
19258        let tree = parser.parse(source, None).expect("fixture tree");
19259        let start = source.rfind("entropy_target()").expect("reference");
19260        let node = tree
19261            .root_node()
19262            .descendant_for_byte_range(start, start + "entropy_target".len())
19263            .expect("reference node");
19264        let guards = preprocessor_guard_environment(node, source).expect("active C branch");
19265        assert!(
19266            guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
19267            "{guards:#?}"
19268        );
19269        assert!(
19270            guards.contains(&PreprocessorGuard::Undefined(
19271                "HAVE_COMMON_RANDOM".to_string()
19272            )),
19273            "{guards:#?}"
19274        );
19275        assert!(
19276            guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
19277            "{guards:#?}"
19278        );
19279        assert!(
19280            !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
19281            "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
19282        );
19283    }
19284
19285    #[test]
19286    #[hardwon("1e74d25492c1: C++ declaration recovery regressed for this structured syntax edge")]
19287    fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
19288        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";
19289        let mut parser = Parser::new();
19290        parser
19291            .set_language(&tree_sitter_cpp::LANGUAGE.into())
19292            .expect("C++ grammar");
19293        let tree = parser.parse(source, None).expect("fixture tree");
19294        let root = tree.root_node();
19295        let node_at = |text: &str, start: usize| {
19296            root.descendant_for_byte_range(start, start + text.len())
19297                .expect("token node")
19298        };
19299
19300        let key_start = source.find("case KEY").expect("case label") + "case ".len();
19301        let guard_start = source.find("ENABLE_KEYS").expect("guard name");
19302        assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
19303        assert!(!is_ordinary_macro_reference_node(node_at(
19304            "ENABLE_KEYS",
19305            guard_start,
19306        )));
19307    }
19308
19309    #[test]
19310    #[hardwon("31913fee42a0: C++ declaration recovery regressed for this structured syntax edge")]
19311    fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
19312        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";
19313        let mut parser = Parser::new();
19314        parser
19315            .set_language(&tree_sitter_cpp::LANGUAGE.into())
19316            .expect("C++ grammar");
19317        let tree = parser.parse(source, None).expect("fixture tree");
19318        let root = tree.root_node();
19319        let definition_start = source.find("target(void)").expect("definition");
19320        let reference_start = source.rfind("target()").expect("reference");
19321        let definition = root
19322            .descendant_for_byte_range(definition_start, definition_start + "target".len())
19323            .expect("definition node");
19324        let reference = root
19325            .descendant_for_byte_range(reference_start, reference_start + "target".len())
19326            .expect("reference node");
19327        let required =
19328            preprocessor_guard_environment(definition, source).expect("definition guard");
19329        let active = preprocessor_guard_environment(reference, source).expect("reference guard");
19330        assert!(guard_requirements_hold_at_reference(
19331            &required,
19332            Some(&active)
19333        ));
19334    }
19335
19336    #[test]
19337    #[hardwon("31913fee42a0: C++ declaration recovery regressed for this structured syntax edge")]
19338    fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
19339        let source = "g_autoptr(FuChunkArray) self = make_array();";
19340        let mut parser = Parser::new();
19341        parser
19342            .set_language(&tree_sitter_cpp::LANGUAGE.into())
19343            .expect("C++ grammar");
19344        let tree = parser.parse(source, None).expect("fixture tree");
19345        let statement = tree.root_node().named_child(0).expect("statement");
19346        let binding =
19347            recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
19348        assert_eq!(binding.name, "self");
19349        assert_eq!(binding.type_name, "FuChunkArray");
19350        assert_eq!(binding.pointer_depth, 1);
19351
19352        let near_miss = "holder(FuChunkArray) self = make_array();";
19353        let tree = parser.parse(near_miss, None).expect("near-miss tree");
19354        let statement = tree.root_node().named_child(0).expect("statement");
19355        assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
19356    }
19357
19358    #[test]
19359    #[hardwon("dc1b8c5a6ee8: C++ declaration recovery regressed for this structured syntax edge")]
19360    fn boolean_guard_normalization_proves_equivalence_and_implication() {
19361        let windows = BooleanGuardExpression::Defined("WIN32".to_string());
19362        let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
19363        let negated_windows_branch =
19364            BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
19365        let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
19366        assert_eq!(negated_windows_branch, portable);
19367
19368        let missing_a = BooleanGuardExpression::Undefined("A".to_string());
19369        let missing_b = BooleanGuardExpression::Undefined("B".to_string());
19370        let missing_c = BooleanGuardExpression::Undefined("C".to_string());
19371        let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
19372        let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
19373        assert!(fallback_branch.implies(&fallback_declaration));
19374        assert!(
19375            BooleanGuardExpression::Truthy("FEATURE".to_string())
19376                .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
19377        );
19378        assert!(
19379            BooleanGuardExpression::Undefined("FEATURE".to_string())
19380                .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
19381        );
19382        assert!(
19383            !BooleanGuardExpression::Defined("FEATURE".to_string())
19384                .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
19385        );
19386        assert!(!fallback_declaration.implies(&fallback_branch));
19387    }
19388
19389    #[test]
19390    #[hardwon("15e5ef579cdd: C++ declaration recovery regressed for this structured syntax edge")]
19391    fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
19392        let source = "static int helper(const char *left, wchar_t *right) { return 0; }\nint caller(wchar_t *template) {\n    if (1) {\n        return helper(NULL, template); /* nested bound */\n    }\n    return helper(NULL, template); /* bound */\n}\nint lambda_scope(wchar_t *template) {\n    return [=]() { return helper(NULL, template); }(); /* lambda scope */\n}\nint unbound(void) {\n    return helper(NULL, template); /* unbound */\n}\n";
19393        let mut parser = Parser::new();
19394        parser
19395            .set_language(&tree_sitter_cpp::LANGUAGE.into())
19396            .expect("C++ grammar");
19397        let tree = parser.parse(source, None).expect("fixture tree");
19398        let root = tree.root_node();
19399        let call = |marker: &str| {
19400            let start = source.find(marker).expect("call marker");
19401            let mut node = root
19402                .descendant_for_byte_range(start, start + "helper".len())
19403                .expect("call name node");
19404            loop {
19405                if node.kind() == "call_expression" {
19406                    break node;
19407                }
19408                node = node.parent().expect("call expression ancestor");
19409            }
19410        };
19411        let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
19412        let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
19413        let keyword_call = call("helper(NULL, template); /* bound */");
19414        let keyword_arguments = keyword_call
19415            .child_by_field_name("arguments")
19416            .expect("keyword argument list");
19417        assert_eq!(
19418            recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
19419            1
19420        );
19421
19422        let nested_call = call("helper(NULL, template); /* nested bound */");
19423        let nested_arguments = nested_call
19424            .child_by_field_name("arguments")
19425            .expect("nested keyword argument list");
19426        assert_eq!(
19427            recovered_c_keyword_argument_count(&c_file, nested_call, nested_arguments, source),
19428            1
19429        );
19430
19431        let lambda_call = call("helper(NULL, template); }(); /* lambda scope */");
19432        let lambda_arguments = lambda_call
19433            .child_by_field_name("arguments")
19434            .expect("lambda keyword argument list");
19435        assert_eq!(
19436            recovered_c_keyword_argument_count(&c_file, lambda_call, lambda_arguments, source),
19437            0,
19438            "a lambda body must not borrow the enclosing collapsed C parameter"
19439        );
19440
19441        assert_eq!(
19442            recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
19443            0
19444        );
19445
19446        let unbound_call = call("helper(NULL, template); /* unbound */");
19447        let unbound_arguments = unbound_call
19448            .child_by_field_name("arguments")
19449            .expect("unbound argument list");
19450        assert_eq!(
19451            recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
19452            0
19453        );
19454    }
19455
19456    #[test]
19457    #[brokk_bifrost_test_macros::hardwon(
19458        "#4407: collapsed C keyword parameters need structural position proof"
19459    )]
19460    fn c_keyword_argument_recovery_rejects_non_parameter_keyword_positions() {
19461        let source = r#"static int helper(const char *left, wchar_t *right) { return 0; }
19462int type_keyword(template *value) {
19463    return helper(NULL, template); /* type */
19464}
19465int qualifier_keyword(const *template) {
19466    return helper(NULL, template); /* qualifier */
19467}
19468int annotation_keyword(wchar_t *value __attribute__((template))) {
19469    return helper(NULL, template); /* annotation */
19470}
19471int default_keyword(wchar_t *value = template) {
19472    return helper(NULL, template); /* default */
19473}
19474int nested_expression(wchar_t *value, (template)) {
19475    return helper(NULL, template); /* nested */
19476}
19477int malformed_head = (wchar_t *template) {
19478    return helper(NULL, template); /* malformed */
19479};
19480"#;
19481        let tree = parse_cpp(source);
19482        let root = tree.root_node();
19483        let call = |marker: &str| {
19484            let start = source.find(marker).expect("call marker");
19485            let mut node = root
19486                .descendant_for_byte_range(start, start + "helper".len())
19487                .expect("call name node");
19488            loop {
19489                if node.kind() == "call_expression" {
19490                    break node;
19491                }
19492                node = node.parent().expect("call expression ancestor");
19493            }
19494        };
19495        let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-near-miss.c");
19496        for marker in [
19497            "helper(NULL, template); /* type */",
19498            "helper(NULL, template); /* qualifier */",
19499            "helper(NULL, template); /* annotation */",
19500            "helper(NULL, template); /* default */",
19501            "helper(NULL, template); /* nested */",
19502            "helper(NULL, template); /* malformed */",
19503        ] {
19504            let keyword_call = call(marker);
19505            let keyword_arguments = keyword_call
19506                .child_by_field_name("arguments")
19507                .expect("keyword argument list");
19508            assert_eq!(
19509                recovered_c_keyword_argument_count(
19510                    &c_file,
19511                    keyword_call,
19512                    keyword_arguments,
19513                    source,
19514                ),
19515                0,
19516                "keyword in {marker} must not prove a parameter binding"
19517            );
19518        }
19519    }
19520
19521    #[test]
19522    #[hardwon("31982bd4d78a: C++ declaration recovery regressed for this structured syntax edge")]
19523    fn c_function_declarator_recovery_accepts_invocations_not_binders() {
19524        let source = r#"#define MAKE(type) type *value
19525MAKE(int *);
19526typedef struct Item Item;
19527struct CPUX86State { struct { int ZMM_L(int); } xmm_regs[8]; };
19528void gen_op_movl(void *s, int first, int second) { }
19529const char *strZ(const char *value) { return value; }
19530int body(void *s) {
19531    MAKE(int *);
19532    gen_op_movl(s, offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)),
19533                offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)));
19534    execvp(strZ(value), UNCONSTIFY(char **, args));
19535}
19536 #define DEV_CHECK_PRESENCE(TYPE, MEMBER, DEVTYPE, PROPERTY, VALUE) \
19537    if (!((TYPE)target)->MEMBER) { check(DEVTYPE, PROPERTY, VALUE); }
19538int recovered_deviation(struct Deviation *d, struct Target *target, void *ctx) {
19539    if (d->units) {
19540        switch (target->nodetype) {
19541        case 1:
19542        case 2:
19543            break;
19544        default:
19545            AMEND_WRONG_NODETYPE("deviation", "replace", "units");
19546        }
19547        DEV_CHECK_PRESENCE(struct Item *, units, "replacing", "units", d->units);
19548        lysdict_remove(ctx, ((struct Item *)target)->units);
19549        DUP_STRING_GOTO(ctx, d->units, ((struct Item *)target)->units, ret, cleanup);
19550    }
19551     return 0;
19552 }
19553STATIC EFI_STATUS Encode () { return 0; }
19554"#;
19555        let tree = parse_cpp(source);
19556        let top_macro_start = source.find("MAKE(int *);").expect("top macro");
19557        let top_macro = tree
19558            .root_node()
19559            .named_descendant_for_byte_range(top_macro_start, top_macro_start + 4)
19560            .expect("top macro node");
19561        let body_macro_start = source
19562            .match_indices("MAKE(int *);")
19563            .nth(1)
19564            .expect("body macro")
19565            .0;
19566        let body_macro = tree
19567            .root_node()
19568            .named_descendant_for_byte_range(body_macro_start, body_macro_start + 4)
19569            .expect("body macro node");
19570        let function_call_start = source
19571            .find("gen_op_movl(s, offsetof(CPUX86State")
19572            .expect("function call");
19573        let function_call = tree
19574            .root_node()
19575            .named_descendant_for_byte_range(function_call_start, function_call_start + 11)
19576            .expect("function call node");
19577        let strz_start = source.find("strZ(value)").expect("nested function call");
19578        let strz = tree
19579            .root_node()
19580            .named_descendant_for_byte_range(strz_start, strz_start + 4)
19581            .expect("nested function call node");
19582        let recovered_call_start = source.find("lysdict_remove(ctx").expect("recovered call");
19583        let recovered_call = tree
19584            .root_node()
19585            .named_descendant_for_byte_range(
19586                recovered_call_start,
19587                recovered_call_start + "lysdict_remove".len(),
19588            )
19589            .expect("recovered call node");
19590        let binder_start = source.find("Encode").expect("binder");
19591        let binder = tree
19592            .root_node()
19593            .named_descendant_for_byte_range(binder_start, binder_start + 6)
19594            .expect("binder node");
19595
19596        assert!(recovered_c_function_declarator_invocation(top_macro));
19597        assert!(recovered_c_function_declarator_invocation(body_macro));
19598        assert!(recovered_c_function_declarator_invocation(function_call));
19599        assert!(recovered_c_function_declarator_invocation(strz));
19600        assert!(recovered_c_function_declarator_invocation(recovered_call));
19601        assert!(!recovered_c_function_declarator_invocation(binder));
19602    }
19603
19604    #[test]
19605    #[hardwon("31982bd4d78a: C++ declaration recovery regressed for this structured syntax edge")]
19606    fn c_parenthesized_declarator_recovery_keeps_keyword_argument_and_rejects_siblings() {
19607        let source = r#"typedef int krb5_context;
19608int helper(int first, int second) { return first + second; }
19609static krb5_context ctx;
19610int main(int argc, char **argv) {
19611    int ccinitial;
19612    const char *collection_name, *typename;
19613    typename = helper(ctx, ccinitial);
19614    return 0;
19615}
19616"#;
19617        let tree = parse_cpp(source);
19618        let ctx = tree
19619            .root_node()
19620            .descendant_for_byte_range(
19621                source.find("ctx, ccinitial").expect("ctx argument"),
19622                source.find("ctx, ccinitial").expect("ctx argument") + 3,
19623            )
19624            .expect("ctx node");
19625        let ccinitial_start = source.find("ctx, ccinitial").expect("ctx argument") + 5;
19626        let ccinitial = tree
19627            .root_node()
19628            .descendant_for_byte_range(ccinitial_start, ccinitial_start + "ccinitial".len())
19629            .expect("sibling node");
19630        let typename = named_node_at(&tree, source, "typename = helper");
19631        let helper = named_node_at(&tree, source, "helper(ctx, ccinitial)");
19632
19633        assert_eq!(ctx.kind(), "identifier");
19634        assert!(recovered_c_parenthesized_declarator_reference(ctx));
19635        assert!(!recovered_c_parenthesized_declarator_reference(ccinitial));
19636        assert!(!recovered_c_parenthesized_declarator_reference(typename));
19637        assert!(!recovered_c_parenthesized_declarator_reference(helper));
19638    }
19639
19640    fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
19641        let mut parser = Parser::new();
19642        parser
19643            .set_language(&tree_sitter_cpp::LANGUAGE.into())
19644            .expect("C++ grammar");
19645        let tree = parser.parse(source, None).expect("C++ fixture tree");
19646        let mut stack = vec![tree.root_node()];
19647        while let Some(node) = stack.pop() {
19648            if node.kind() == "enum_specifier" {
19649                return flattened_macro_namespace_components(node, source);
19650            }
19651            let mut cursor = node.walk();
19652            let children = node.named_children(&mut cursor).collect::<Vec<_>>();
19653            stack.extend(children.into_iter().rev());
19654        }
19655        None
19656    }
19657
19658    #[test]
19659    #[hardwon("0e6df36dfa5d: C++ declaration recovery regressed for this structured syntax edge")]
19660    fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
19661        let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
19662namespace detail
19663{
19664enum class value_t { null };
19665}
19666NLOHMANN_JSON_NAMESPACE_END
19667NLOHMANN_JSON_NAMESPACE_BEGIN
19668namespace next
19669{
19670struct next_type {};
19671}
19672NLOHMANN_JSON_NAMESPACE_END
19673"#;
19674        assert_eq!(
19675            first_enum_flattened_namespace(complete),
19676            Some(vec!["detail".to_string()])
19677        );
19678
19679        let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
19680        assert_eq!(
19681            first_enum_flattened_namespace(&stale_end),
19682            Some(vec!["detail".to_string()]),
19683            "a stale end marker before the begin marker must not replace the intended namespace"
19684        );
19685
19686        let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
19687namespace detail
19688{
19689enum class value_t { null };
19690}
19691struct next_type {};
19692"#;
19693        assert_eq!(first_enum_flattened_namespace(incomplete), None);
19694    }
19695}
19696
19697/// Comparator laws for the total C++ lookup order introduced by #1876.
19698///
19699/// `sort_lookup_units` is the single tie-break the C++ resolver applies before
19700/// any "first wins" selection (template families in #1836, the visible
19701/// identifier index, the type-candidate lists). If its comparator is not a
19702/// total order over CodeUnit identity, some pair stays tied and the survivor
19703/// falls back to the order the units arrived in -- which is FxHash iteration
19704/// order over keys whose hash covers the absolute workspace root. That is the
19705/// exact mechanism behind #1836 and the #414 / #432 heisenbug, so the laws are
19706/// checked generatively rather than on one hand-picked list.
19707///
19708/// CodeUnit identity is `source`, `kind`, `fq`, `package_segment_count`,
19709/// `signature` and `synthetic` (see `impl PartialEq for CodeUnit`); a CodeUnit
19710/// carries no range, so declaration ranges are covered by the workspace-level
19711/// property in `tests/suite_analyzers/determinism_properties.rs` instead.
19712#[cfg(test)]
19713mod lookup_order_properties {
19714    use super::*;
19715    use proptest::prelude::*;
19716
19717    /// Segment spellings the C++ extractor and the shared renderer actually
19718    /// produce, including the `$`-joined nested spellings and non-ASCII
19719    /// identifiers that a byte-wise comparison has to keep apart.
19720    const ATOMS: [&str; 9] = ["a", "b", "A", "a$b", "a$", "$a", "ab", "naïve", "識別子"];
19721    const REL_PATHS: [&str; 3] = ["a.cpp", "b.cpp", "sub/a.cpp"];
19722    /// Two roots so the order is pinned across workspaces as well as inside
19723    /// one: the root path is precisely the byte string that used to leak into
19724    /// iteration order.
19725    const ROOT_NAMES: [&str; 2] = ["ws", "ws_much_longer_root_name"];
19726    const SIGNATURES: [Option<&str>; 3] = [None, Some("()"), Some("(int)")];
19727    const KINDS: [CodeUnitType; 6] = [
19728        CodeUnitType::Class,
19729        CodeUnitType::Function,
19730        CodeUnitType::Field,
19731        CodeUnitType::Module,
19732        CodeUnitType::Macro,
19733        CodeUnitType::FileScope,
19734    ];
19735
19736    /// Where one unit sits relative to another under the comparator that
19737    /// `sort_lookup_units` owns.
19738    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
19739    enum ProbedOrder {
19740        Before,
19741        Tied,
19742        After,
19743        /// Both directions reported "strictly first": the comparator is not
19744        /// dual, and no sort over it can be order-independent.
19745        Contradictory,
19746    }
19747
19748    impl ProbedOrder {
19749        fn mirror(self) -> Self {
19750            match self {
19751                ProbedOrder::Before => ProbedOrder::After,
19752                ProbedOrder::After => ProbedOrder::Before,
19753                other => other,
19754            }
19755        }
19756
19757        /// -1 / 0 / +1, so transitivity reads as the `<= 0` law.
19758        fn signum(self) -> i8 {
19759            match self {
19760                ProbedOrder::Before => -1,
19761                ProbedOrder::Tied => 0,
19762                ProbedOrder::After => 1,
19763                ProbedOrder::Contradictory => panic!("probed a non-dual comparator"),
19764            }
19765        }
19766    }
19767
19768    /// Read the comparator through its only caller.
19769    ///
19770    /// `sort_lookup_units` is a stable sort, so for a two-element slice the
19771    /// output says exactly whether the comparator put the second element
19772    /// strictly first. Sorting both arrangements of one pair therefore reports
19773    /// the comparator's verdict in both directions, including the contradictory
19774    /// case a single sort would hide.
19775    fn probe_order(left: &CodeUnit, right: &CodeUnit) -> ProbedOrder {
19776        if left == right {
19777            // A stable sort cannot distinguish two equal values, and `Equal` is
19778            // the only verdict a total order can give them.
19779            return ProbedOrder::Tied;
19780        }
19781        let mut forward = vec![left.clone(), right.clone()];
19782        sort_lookup_units(&mut forward);
19783        let mut backward = vec![right.clone(), left.clone()];
19784        sort_lookup_units(&mut backward);
19785        let left_first = backward[0] == *left;
19786        let right_first = forward[0] == *right;
19787        match (left_first, right_first) {
19788            (true, true) => ProbedOrder::Contradictory,
19789            (true, false) => ProbedOrder::Before,
19790            (false, true) => ProbedOrder::After,
19791            (false, false) => ProbedOrder::Tied,
19792        }
19793    }
19794
19795    fn code_unit_strategy() -> impl Strategy<Value = CodeUnit> {
19796        (
19797            0..ROOT_NAMES.len(),
19798            0..REL_PATHS.len(),
19799            0..KINDS.len(),
19800            prop::collection::vec((0..ATOMS.len(), 0..SegmentKind::ALL.len()), 1..=3),
19801            0..3usize,
19802            0..SIGNATURES.len(),
19803            any::<bool>(),
19804        )
19805            .prop_map(
19806                |(root, rel_path, kind, segments, package_prefix, signature, synthetic)| {
19807                    let source = ProjectFile::new(
19808                        std::env::temp_dir().join(ROOT_NAMES[root]),
19809                        REL_PATHS[rel_path],
19810                    );
19811                    let interner = segment_interner();
19812                    let mut fq = FqName::new();
19813                    for (atom, segment_kind) in &segments {
19814                        fq.push(interner.intern(ATOMS[*atom], SegmentKind::ALL[*segment_kind]));
19815                    }
19816                    // `from_fq` requires a non-empty declaration tail.
19817                    let package_segment_count = package_prefix % fq.len();
19818                    CodeUnit::from_fq(
19819                        source,
19820                        KINDS[kind],
19821                        fq,
19822                        package_segment_count,
19823                        SIGNATURES[signature].map(str::to_string),
19824                        synthetic,
19825                    )
19826                },
19827            )
19828    }
19829
19830    proptest! {
19831        #![proptest_config(ProptestConfig::with_cases(256))]
19832
19833        /// Reflexivity and duality: a unit ties with itself, and no pair is
19834        /// strictly first in both directions.
19835        #[test]
19836        #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
19837        fn lookup_order_is_reflexive_and_dual(
19838            left in code_unit_strategy(),
19839            right in code_unit_strategy(),
19840        ) {
19841            prop_assert_eq!(
19842                probe_order(&left, &left),
19843                ProbedOrder::Tied,
19844                "a unit must tie with itself: {:?}",
19845                left
19846            );
19847            let forward = probe_order(&left, &right);
19848            prop_assert_ne!(
19849                forward,
19850                ProbedOrder::Contradictory,
19851                "comparator put each of these strictly first: left={:?} right={:?}",
19852                left,
19853                right
19854            );
19855            prop_assert_eq!(
19856                probe_order(&right, &left),
19857                forward.mirror(),
19858                "compare(b, a) must reverse compare(a, b): left={:?} right={:?}",
19859                left,
19860                right
19861            );
19862        }
19863
19864        /// Transitivity: `a <= b` and `b <= c` imply `a <= c`.
19865        #[test]
19866        #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
19867        fn lookup_order_is_transitive(
19868            a in code_unit_strategy(),
19869            b in code_unit_strategy(),
19870            c in code_unit_strategy(),
19871        ) {
19872            let ab = probe_order(&a, &b);
19873            let bc = probe_order(&b, &c);
19874            let ac = probe_order(&a, &c);
19875            for (probed, pair) in [(ab, "a,b"), (bc, "b,c"), (ac, "a,c")] {
19876                prop_assert_ne!(
19877                    probed,
19878                    ProbedOrder::Contradictory,
19879                    "comparator is not dual over {}: a={:?} b={:?} c={:?}",
19880                    pair,
19881                    a,
19882                    b,
19883                    c
19884                );
19885            }
19886            if ab.signum() <= 0 && bc.signum() <= 0 {
19887                prop_assert!(
19888                    ac.signum() <= 0,
19889                    "transitivity broken: a<=b ({:?}) and b<=c ({:?}) but a?c is {:?}; \
19890                     a={:?} b={:?} c={:?}",
19891                    ab,
19892                    bc,
19893                    ac,
19894                    a,
19895                    b,
19896                    c
19897                );
19898            }
19899        }
19900
19901        /// The property #1876 exists for: only identical identities may tie.
19902        /// A tie between distinct units is the residual hash-order dependence.
19903        #[test]
19904        #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
19905        fn lookup_order_separates_distinct_identities(
19906            left in code_unit_strategy(),
19907            right in code_unit_strategy(),
19908        ) {
19909            if probe_order(&left, &right) == ProbedOrder::Tied {
19910                prop_assert_eq!(
19911                    &left,
19912                    &right,
19913                    "distinct identities tied, so their order is whatever order they \
19914                     arrived in: left_segments={:?} right_segments={:?}",
19915                    left.fq_segments_debug(),
19916                    right.fq_segments_debug()
19917                );
19918            }
19919        }
19920
19921        /// The consequence the resolver relies on: the sorted list is a
19922        /// function of the SET of units, not of the order they were pushed in.
19923        #[test]
19924        #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
19925        fn lookup_sort_is_permutation_invariant(
19926            units in prop::collection::vec(code_unit_strategy(), 1..=8),
19927        ) {
19928            let mut sorted = units.clone();
19929            sort_lookup_units(&mut sorted);
19930            for rotation in 0..units.len() {
19931                for reversed in [false, true] {
19932                    let mut permuted = units.clone();
19933                    permuted.rotate_left(rotation);
19934                    if reversed {
19935                        permuted.reverse();
19936                    }
19937                    sort_lookup_units(&mut permuted);
19938                    prop_assert_eq!(
19939                        &permuted,
19940                        &sorted,
19941                        "sorting a permutation gave a different list \
19942                         (rotation={}, reversed={}): input={:?}",
19943                        rotation,
19944                        reversed,
19945                        units
19946                    );
19947                }
19948            }
19949        }
19950    }
19951}