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 with a borrowed source and scope proof.
982/// Cache only `data()` across calls, and reattach the current source and token
983/// with `from_data`; cloning the analyzer would lose its query read cache.
984pub struct VisibilityIndex<'a> {
985    cpp: &'a dyn CppSource,
986    token: QueryToken<'a>,
987    data: Arc<VisibilityIndexData>,
988}
989
990impl std::ops::Deref for VisibilityIndex<'_> {
991    type Target = VisibilityIndexData;
992
993    fn deref(&self) -> &Self::Target {
994        &self.data
995    }
996}
997
998/// Owned visibility facts. They retain no source or request-scope borrow.
999/// The caller keeps these facts inside the query that built them.
1000pub struct VisibilityIndexData {
1001    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1002    visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
1003    global_field_internal_linkage: HashMap<CodeUnit, bool>,
1004    visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
1005    alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
1006    visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
1007    parser_alias_target_matches:
1008        RwLock<HashMap<(ProjectFile, String, LogicalSymbolKey), ParserAliasTargetMatchCell>>,
1009    ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
1010    project_using_index: OnceLock<ProjectUsingIndex>,
1011    callable_reference_specs:
1012        Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
1013    structured_include_fact_cells: Mutex<HashMap<ProjectFile, StructuredIncludeFactCell>>,
1014    include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
1015    compile_proven_guard_cells: Mutex<HashMap<ProjectFile, Arc<HashSet<PreprocessorGuard>>>>,
1016    include_path_admission_cells: Mutex<HashMap<ProjectFile, IncludePathAdmission>>,
1017    conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
1018    #[cfg(any(test, feature = "test-support"))]
1019    conditional_include_projection_index_build_count: AtomicUsize,
1020    #[cfg(any(test, feature = "test-support"))]
1021    conditional_include_projection_state_count: AtomicUsize,
1022    #[cfg(any(test, feature = "test-support"))]
1023    conditional_include_target_state_count: AtomicUsize,
1024    #[cfg(any(test, feature = "test-support"))]
1025    include_activation_build_count: AtomicUsize,
1026    #[cfg(any(test, feature = "test-support"))]
1027    using_donor_activation_count: AtomicUsize,
1028    #[cfg(any(test, feature = "test-support"))]
1029    using_namespace_lookup_count: AtomicUsize,
1030    #[cfg(any(test, feature = "test-support"))]
1031    using_name_candidate_inspection_count: AtomicUsize,
1032    #[cfg(any(test, feature = "test-support"))]
1033    callable_reference_spec_build_count: AtomicUsize,
1034    #[cfg(any(test, feature = "test-support"))]
1035    alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
1036    #[cfg(any(test, feature = "test-support"))]
1037    visible_parser_alias_name_set_build_count: AtomicUsize,
1038    parser_alias_fallback_calls: AtomicUsize,
1039    parser_alias_fallback_files: AtomicUsize,
1040    parser_alias_source_parses: AtomicUsize,
1041    parser_alias_fallback_elapsed_micros: AtomicUsize,
1042    field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
1043    structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
1044    callable_comparables: Mutex<HashMap<CodeUnit, Option<Arc<ExtractedComparable>>>>,
1045    /// Complete callable metadata is expensive for reconciled C++ members:
1046    /// each read can rebuild the declaration/definition evidence. Reuse it
1047    /// across the member, arity, identity, and navigation phases of this
1048    /// visibility query, with an explicit cap so the query never retains a
1049    /// workspace-sized unit table.
1050    callable_metadata: Mutex<HashMap<CodeUnit, Arc<Vec<SignatureMetadata>>>>,
1051    comparable_name_declarations: Mutex<HashMap<StructuredTypeName, Option<CodeUnit>>>,
1052    indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
1053    indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
1054    precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1055    c_tag_kind_cache: Mutex<HashMap<CodeUnit, Option<CppCTagKind>>>,
1056    c_tag_complete_definition_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1057    macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
1058    macro_event_name_sets: Mutex<HashMap<ProjectFile, Arc<HashSet<String>>>>,
1059    pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
1060    // The environment at selected event prefixes of each file, built once and read by every
1061    // worker. The authoritative differential shares this index across target workers whose
1062    // frontiers interleave arbitrarily and move backwards, so a forward cursor -- per worker or
1063    // not -- replayed a file's events once per backward request. Checkpoints answer any position
1064    // with a binary search and at most one stride of replay, and being immutable they need no
1065    // per-worker copy (#1496).
1066    macro_environment_checkpoints: Mutex<HashMap<ProjectFile, MacroEnvironmentCheckpointCell>>,
1067    macro_replacements: Mutex<MacroReplacementCache>,
1068    macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
1069    pub(crate) macro_lexical_templates: Mutex<MacroLexicalTemplateCache>,
1070    macro_lexical_indexes:
1071        Mutex<HashMap<ProjectFile, Arc<crate::graph::macro_lexical::MacroLexicalIndex>>>,
1072    macro_replacement_bodies: Mutex<MacroReplacementBodyCache>,
1073    macro_type_parameters: Mutex<MacroTypeParameterCache>,
1074    callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
1075    #[cfg(any(test, feature = "test-support"))]
1076    pub macro_replacement_parse_count: AtomicUsize,
1077    #[cfg(any(test, feature = "test-support"))]
1078    pub macro_event_application_count: AtomicUsize,
1079    /// How many files had their checkpoint vector built. One build per file
1080    /// per query even when workers race for the same file.
1081    #[cfg(any(test, feature = "test-support"))]
1082    pub macro_environment_checkpoint_build_count: AtomicUsize,
1083    /// How many requests landed off a checkpoint and so had to copy one and
1084    /// replay the events after it.
1085    #[cfg(any(test, feature = "test-support"))]
1086    pub macro_environment_copy_count: AtomicUsize,
1087    #[cfg(any(test, feature = "test-support"))]
1088    pub macro_environment_request_count: AtomicUsize,
1089    cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
1090    cpp_template_families: HashMap<String, Vec<CodeUnit>>,
1091    #[cfg(any(test, feature = "test-support"))]
1092    qualified_candidate_inspections: AtomicUsize,
1093    #[cfg(any(test, feature = "test-support"))]
1094    target_preserving_type_resolution_count: AtomicUsize,
1095    #[cfg(any(test, feature = "test-support"))]
1096    visibility_identifier_lookup_count: usize,
1097    #[cfg(any(test, feature = "test-support"))]
1098    visibility_identifier_batch_count: usize,
1099}
1100
1101impl Drop for VisibilityIndexData {
1102    fn drop(&mut self) {
1103        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_none() {
1104            return;
1105        }
1106        #[cfg(any(test, feature = "test-support"))]
1107        eprintln!(
1108            "BIFROST_CPP_MACRO_STATS requests={} copies={} checkpoint_builds={} applications={}",
1109            self.macro_environment_request_count.load(Ordering::Relaxed),
1110            self.macro_environment_copy_count.load(Ordering::Relaxed),
1111            self.macro_environment_checkpoint_build_count
1112                .load(Ordering::Relaxed),
1113            self.macro_event_application_count.load(Ordering::Relaxed),
1114        );
1115        let calls = self.parser_alias_fallback_calls.load(Ordering::Relaxed);
1116        if calls == 0 {
1117            return;
1118        }
1119        eprintln!(
1120            "BIFROST_CPP_ALIAS_FALLBACK_STATS calls={} files={} source_parses={} elapsed_ms={}",
1121            calls,
1122            self.parser_alias_fallback_files.load(Ordering::Relaxed),
1123            self.parser_alias_source_parses.load(Ordering::Relaxed),
1124            self.parser_alias_fallback_elapsed_micros
1125                .load(Ordering::Relaxed)
1126                / 1_000,
1127        );
1128    }
1129}
1130
1131#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1132pub enum PreprocessorGuard {
1133    Defined(String),
1134    Undefined(String),
1135    Boolean(BooleanGuardExpression),
1136    Expression(String),
1137    NegatedExpression(String),
1138    Constant(bool),
1139}
1140
1141#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1142pub enum BooleanGuardExpression {
1143    Defined(String),
1144    Undefined(String),
1145    Truthy(String),
1146    Falsy(String),
1147    Opaque(String),
1148    NegatedOpaque(String),
1149    All(Vec<BooleanGuardExpression>),
1150    Any(Vec<BooleanGuardExpression>),
1151    Constant(bool),
1152}
1153
1154impl BooleanGuardExpression {
1155    fn negated(&self) -> Self {
1156        match self {
1157            Self::Defined(name) => Self::Undefined(name.clone()),
1158            Self::Undefined(name) => Self::Defined(name.clone()),
1159            Self::Truthy(name) => Self::Falsy(name.clone()),
1160            Self::Falsy(name) => Self::Truthy(name.clone()),
1161            Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
1162            Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
1163            Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
1164            Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
1165            Self::Constant(value) => Self::Constant(!value),
1166        }
1167    }
1168
1169    fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
1170        Self::normalized(expressions, true)
1171    }
1172
1173    fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
1174        Self::normalized(expressions, false)
1175    }
1176
1177    fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
1178        let mut normalized = Vec::new();
1179        for expression in expressions {
1180            match expression {
1181                Self::All(nested) if conjunction => normalized.extend(nested),
1182                Self::Any(nested) if !conjunction => normalized.extend(nested),
1183                Self::Constant(value) if value == conjunction => {}
1184                Self::Constant(value) => return Self::Constant(value),
1185                expression => normalized.push(expression),
1186            }
1187        }
1188        normalized.sort_unstable();
1189        normalized.dedup();
1190        match normalized.len() {
1191            0 => Self::Constant(conjunction),
1192            1 => normalized.pop().expect("one Boolean guard expression"),
1193            _ if conjunction => Self::All(normalized),
1194            _ => Self::Any(normalized),
1195        }
1196    }
1197
1198    fn implies(&self, required: &Self) -> bool {
1199        if self == required
1200            || matches!(self, Self::Constant(false))
1201            || matches!(required, Self::Constant(true))
1202        {
1203            return true;
1204        }
1205        if matches!(
1206            (self, required),
1207            (Self::Truthy(active), Self::Defined(required))
1208                | (Self::Undefined(active), Self::Falsy(required))
1209                if active == required
1210        ) {
1211            return true;
1212        }
1213        match self {
1214            Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
1215            Self::All(active) => match required {
1216                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1217                _ => active.iter().any(|expression| expression.implies(required)),
1218            },
1219            _ => match required {
1220                Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
1221                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1222                _ => false,
1223            },
1224        }
1225    }
1226
1227    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1228        match self {
1229            Self::Defined(name)
1230            | Self::Undefined(name)
1231            | Self::Truthy(name)
1232            | Self::Falsy(name) => name == macro_name,
1233            // Opaque expressions have structured conditional ownership but no
1234            // structured macro operands, so any mutation may change them.
1235            Self::Opaque(_) | Self::NegatedOpaque(_) => true,
1236            Self::All(expressions) | Self::Any(expressions) => expressions
1237                .iter()
1238                .any(|expression| expression.may_depend_on_macro(macro_name)),
1239            Self::Constant(_) => false,
1240        }
1241    }
1242
1243    pub fn heap_size(&self) -> usize {
1244        match self {
1245            Self::Defined(value)
1246            | Self::Undefined(value)
1247            | Self::Truthy(value)
1248            | Self::Falsy(value)
1249            | Self::Opaque(value)
1250            | Self::NegatedOpaque(value) => value.len(),
1251            Self::All(expressions) | Self::Any(expressions) => {
1252                expressions
1253                    .iter()
1254                    .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
1255                        size.saturating_add(std::mem::size_of::<Self>())
1256                            .saturating_add(expression.heap_size())
1257                    })
1258            }
1259            Self::Constant(_) => 0,
1260        }
1261    }
1262}
1263
1264impl PreprocessorGuard {
1265    fn as_boolean_expression(&self) -> Option<BooleanGuardExpression> {
1266        match self {
1267            Self::Defined(name) => Some(BooleanGuardExpression::Defined(name.clone())),
1268            Self::Undefined(name) => Some(BooleanGuardExpression::Undefined(name.clone())),
1269            Self::Boolean(expression) => Some(expression.clone()),
1270            Self::Constant(value) => Some(BooleanGuardExpression::Constant(*value)),
1271            Self::Expression(_) | Self::NegatedExpression(_) => None,
1272        }
1273    }
1274
1275    fn negated(&self) -> Self {
1276        match self {
1277            Self::Defined(name) => Self::Undefined(name.clone()),
1278            Self::Undefined(name) => Self::Defined(name.clone()),
1279            Self::Boolean(expression) => Self::Boolean(expression.negated()),
1280            Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1281            Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1282            Self::Constant(value) => Self::Constant(!value),
1283        }
1284    }
1285
1286    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1287        match self {
1288            Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1289            Self::Boolean(expression) => expression.may_depend_on_macro(macro_name),
1290            // These expressions could not be lowered to a Boolean operand
1291            // tree, so their dependencies remain unknown.
1292            Self::Expression(_) | Self::NegatedExpression(_) => true,
1293            Self::Constant(_) => false,
1294        }
1295    }
1296}
1297
1298#[derive(Clone, PartialEq, Eq)]
1299pub enum MacroDefinition {
1300    Object {
1301        replacement: String,
1302    },
1303    Function {
1304        parameters: Vec<String>,
1305        replacement: String,
1306    },
1307    VariadicFunction {
1308        parameters: Vec<String>,
1309        replacement: String,
1310    },
1311    Unsupported,
1312}
1313
1314#[derive(Clone, Debug, PartialEq, Eq)]
1315pub enum MacroIncludeProtection {
1316    MacroGuard(String),
1317    PragmaOnce,
1318    None,
1319}
1320
1321enum ParsedMacroReplacement {
1322    Parsed { source: String, tree: Tree },
1323    Unsupported,
1324}
1325
1326/// The sentinel that gives a function-like macro replacement a parseable
1327/// statement context. The replacement text is copied in verbatim, so the only
1328/// bytes ahead of it are this prefix.
1329const MACRO_BODY_SENTINEL_PREFIX: &str = "void __bifrost_macro_body() { ";
1330
1331/// A function-like macro replacement parsed inside a sentinel function body.
1332///
1333/// Tree-sitter keeps a `#define NAME(a) ...` replacement as one opaque
1334/// `preproc_arg`. Wrapping that exact byte slice in a function body recovers
1335/// its statements, declarations, and member calls as ordinary C++ structure.
1336/// The slice is copied verbatim at [`Self::body_offset`], so a node range in
1337/// [`Self::tree`] maps back onto the defining `preproc_arg` by subtracting
1338/// that offset.
1339pub struct ParsedReplacementBody {
1340    pub source: String,
1341    pub tree: Tree,
1342    pub body_offset: usize,
1343    pub parameters: Vec<String>,
1344    /// For a normal parse this is an identity map. A constrained statement
1345    /// recovery may insert separators after a macro formal; each boundary in
1346    /// this map points back to the corresponding byte in the original
1347    /// replacement, keeping source-backed ranges exact after substitution.
1348    original_offsets: Box<[usize]>,
1349}
1350
1351impl ParsedReplacementBody {
1352    /// The sentinel function body holding the replacement's statements.
1353    pub fn statements(&self) -> Option<Node<'_>> {
1354        first_descendant_of_kind(self.tree.root_node(), "function_definition")?
1355            .child_by_field_name("body")
1356    }
1357
1358    /// The byte range `node` occupies in the file that defines the macro.
1359    ///
1360    /// `replacement_start` is the logical replacement span's start byte.
1361    /// The origin map accounts for continuation whitespace and any synthetic
1362    /// statement separators inserted while parsing the replacement.
1363    pub fn file_range(&self, node: Node<'_>, replacement_start: usize) -> std::ops::Range<usize> {
1364        assert!(
1365            node.start_byte() >= self.body_offset,
1366            "synthetic sentinel node cannot be mapped to a macro replacement"
1367        );
1368        assert!(
1369            node.end_byte() >= self.body_offset,
1370            "synthetic sentinel node cannot be mapped to a macro replacement"
1371        );
1372        let start_offset = node.start_byte() - self.body_offset;
1373        let end_offset = node.end_byte() - self.body_offset;
1374        assert!(start_offset <= end_offset);
1375        let start_origin = *self
1376            .original_offsets
1377            .get(start_offset)
1378            .expect("replacement node start must have a source mapping");
1379        let end_origin = *self
1380            .original_offsets
1381            .get(end_offset)
1382            .expect("replacement node end must have a source mapping");
1383        assert!(start_origin <= end_origin);
1384        let start = replacement_start + start_origin;
1385        let end = replacement_start + end_origin;
1386        start..end
1387    }
1388
1389    /// Whether the replacement names the variadic argument pack.
1390    ///
1391    /// `__VA_ARGS__` parses as an ordinary identifier, so the sentinel tree
1392    /// gives no error for it even though the expansion it stands for is
1393    /// unknown at the definition. Reject it from the parsed tree rather than
1394    /// by scanning the replacement text.
1395    fn expands_variadic_arguments(&self) -> bool {
1396        let mut stack = vec![self.tree.root_node()];
1397        while let Some(node) = stack.pop() {
1398            if matches!(
1399                node.kind(),
1400                "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
1401            ) && node_text(node, &self.source) == "__VA_ARGS__"
1402            {
1403                return true;
1404            }
1405            push_named_children_reversed(node, &mut stack);
1406        }
1407        false
1408    }
1409}
1410
1411/// Parse the value of a C++ integer-literal token when it fits in `i128`.
1412pub fn parse_cpp_integer_literal(text: &str) -> Option<i128> {
1413    let compact = text.chars().filter(|ch| *ch != '\'').collect::<String>();
1414    let (radix, digits_start, digit_matches): (u32, usize, fn(char) -> bool) =
1415        if compact.starts_with("0x") || compact.starts_with("0X") {
1416            (16, 2, |ch| ch.is_ascii_hexdigit())
1417        } else if compact.starts_with("0b") || compact.starts_with("0B") {
1418            (2, 2, |ch| matches!(ch, '0' | '1'))
1419        } else if compact.starts_with('0') && compact.len() > 1 {
1420            (8, 0, |ch| matches!(ch, '0'..='7'))
1421        } else {
1422            (10, 0, |ch| ch.is_ascii_digit())
1423        };
1424    let digit_len = compact[digits_start..]
1425        .chars()
1426        .take_while(|ch| digit_matches(*ch))
1427        .map(char::len_utf8)
1428        .sum::<usize>();
1429    if digit_len == 0 {
1430        return None;
1431    }
1432    let digits_end = digits_start + digit_len;
1433    if !compact[digits_end..]
1434        .chars()
1435        .all(|ch| matches!(ch, 'u' | 'U' | 'l' | 'L' | 'z' | 'Z'))
1436    {
1437        return None;
1438    }
1439    i128::from_str_radix(&compact[digits_start..digits_end], radix).ok()
1440}
1441
1442#[derive(Clone)]
1443enum MacroLocalBindingTypeTemplate {
1444    Parameter(usize),
1445    Fixed(String),
1446}
1447
1448#[derive(Clone)]
1449struct MacroLocalBindingTemplate {
1450    name: String,
1451    declared_type: MacroLocalBindingTypeTemplate,
1452    pointer_depth: i32,
1453}
1454
1455/// A local declaration contributed by one structurally known function-like macro.
1456///
1457/// `type_node` points into the invocation syntax when the replacement's type
1458/// is one of the macro parameters. Consumers can therefore use their normal
1459/// lexical type resolver without parsing replacement text themselves.
1460/// `proven_unit` carries a structured C-tag resolution when the grammar splits
1461/// an explicit `struct` or `union` argument across recovery nodes.
1462pub struct MacroLocalBinding<'tree> {
1463    pub name: String,
1464    pub type_name: String,
1465    pub type_node: Option<Node<'tree>>,
1466    pub pointer_depth: i32,
1467    pub proven_unit: Option<CodeUnit>,
1468}
1469
1470/// The source-backed declaration introduced by a function-like macro. The
1471/// declaration belongs to the macro definition, while the reference range
1472/// returned by [`VisibilityIndex::macro_lexical_references`] belongs to the
1473/// definition body, invocation argument, or caller scope where substitution
1474/// makes the binding visible.
1475#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1476pub enum MacroLexicalBindingKind {
1477    Parameter,
1478    Local,
1479}
1480
1481#[derive(Clone, Debug, PartialEq, Eq)]
1482pub struct MacroLexicalBinding {
1483    pub definition: ProjectFile,
1484    pub kind: MacroLexicalBindingKind,
1485    pub name: String,
1486    pub name_range: std::ops::Range<usize>,
1487    pub declaration_range: std::ops::Range<usize>,
1488}
1489
1490/// Lexical macro references are query-local facts. A cancelled or bounded
1491/// walk is kept distinct from a complete empty enumeration so authoritative
1492/// inverse callers can preserve their completeness status.
1493#[derive(Clone, Debug, Default, PartialEq, Eq)]
1494pub struct MacroLexicalReferences {
1495    pub references: Vec<(std::ops::Range<usize>, MacroLexicalBinding)>,
1496    pub truncated: bool,
1497    pub cancelled: bool,
1498}
1499
1500fn macro_replacement_type_parameters(
1501    body: &ParsedReplacementBody,
1502    parameters: &[String],
1503) -> Option<Vec<usize>> {
1504    let mut found = Vec::new();
1505    let mut stack = vec![body.tree.root_node()];
1506    while let Some(node) = stack.pop() {
1507        let type_position = node.kind() == "type_identifier"
1508            || (node.kind() == "identifier"
1509                && node.parent().is_some_and(|parent| {
1510                    parent.kind() == "type_descriptor"
1511                        && parent.child_by_field_name("type") == Some(node)
1512                }));
1513        let offsetof_type_position = node.kind() == "identifier"
1514            && node
1515                .parent()
1516                .filter(|parent| parent.kind() == "argument_list")
1517                .and_then(|arguments| arguments.parent())
1518                .is_some_and(|call| {
1519                    call.kind() == "call_expression"
1520                        && call
1521                            .child_by_field_name("function")
1522                            .is_some_and(|function| {
1523                                function.kind() == "identifier"
1524                                    && node_text(function, &body.source) == "offsetof"
1525                            })
1526                        && call
1527                            .child_by_field_name("arguments")
1528                            .is_some_and(|arguments| {
1529                                argument_children(arguments).next() == Some(node)
1530                            })
1531                });
1532        if (type_position || offsetof_type_position)
1533            && let Some(index) = parameters
1534                .iter()
1535                .position(|parameter| parameter == node_text(node, &body.source))
1536            && !found.contains(&index)
1537        {
1538            found.push(index);
1539        }
1540        push_named_children_reversed(node, &mut stack);
1541    }
1542    (!found.is_empty()).then_some(found)
1543}
1544
1545fn macro_replacement_type_parameter(
1546    body: &ParsedReplacementBody,
1547    parameters: &[String],
1548) -> Option<usize> {
1549    let mut parameters = macro_replacement_type_parameters(body, parameters)?;
1550    (parameters.len() == 1).then(|| parameters.pop().unwrap())
1551}
1552
1553pub(crate) fn macro_type_argument_node<'tree>(
1554    node: Node<'tree>,
1555    source: &str,
1556) -> Option<Node<'tree>> {
1557    match node.kind() {
1558        "type_descriptor" => {
1559            let type_child = node
1560                .child_by_field_name("type")
1561                .or_else(|| first_type_child(node))?;
1562            for index in (0..node.named_child_count()).rev() {
1563                let child = node.named_child(index)?;
1564                if child != type_child
1565                    && matches!(
1566                        child.kind(),
1567                        "identifier"
1568                            | "type_identifier"
1569                            | "qualified_identifier"
1570                            | "scoped_type_identifier"
1571                    )
1572                {
1573                    return Some(child);
1574                }
1575            }
1576            macro_type_argument_node(type_child, source)
1577        }
1578        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => {
1579            node.child_by_field_name("name")
1580        }
1581        "identifier" if matches!(node_text(node, source), "struct" | "union") => node
1582            .next_named_sibling()
1583            .filter(|sibling| sibling.is_error() && sibling.named_child_count() == 1)
1584            .and_then(|error| error.named_child(0))
1585            .filter(|name| matches!(name.kind(), "identifier" | "type_identifier")),
1586        _ => cpp_name_component_nodes(node).is_some().then_some(node),
1587    }
1588}
1589
1590fn c_function_macro_argument<'tree>(
1591    node: Node<'tree>,
1592) -> Option<(Node<'tree>, usize, Node<'tree>)> {
1593    let mut current = node;
1594    while let Some(parent) = current.parent() {
1595        if parent.kind() == "argument_list" {
1596            let call = parent.parent().filter(|call| {
1597                call.kind() == "call_expression"
1598                    && call.child_by_field_name("arguments") == Some(parent)
1599                    && call
1600                        .child_by_field_name("function")
1601                        .is_some_and(|function| {
1602                            function.kind() == "identifier"
1603                                && !node_range_contains(function, current)
1604                        })
1605            })?;
1606            let mut actuals = argument_children(parent).enumerate();
1607            let (index, argument) = actuals.find(|(_, argument)| {
1608                node_range_contains(*argument, node)
1609                    && (argument.start_byte() == current.start_byte()
1610                        || argument.end_byte() == current.end_byte())
1611            })?;
1612            return Some((call, index, argument));
1613        }
1614        current = parent;
1615    }
1616    None
1617}
1618
1619/// Recover GLib's `g_autoptr(T) name = value` declaration from the CST shape
1620/// produced by tree-sitter-cpp for C source. The grammar retains the macro
1621/// invocation as the assignment's left operand and the declared name as one
1622/// adjacent `ERROR(identifier)` node, so no macro text splitting is needed.
1623fn recognized_c_macro_declarator_binding<'tree>(
1624    statement: Node<'tree>,
1625    source: &str,
1626) -> Option<MacroLocalBinding<'tree>> {
1627    let assignment = match statement.kind() {
1628        "assignment_expression" => statement,
1629        "expression_statement" if statement.named_child_count() == 1 => statement.named_child(0)?,
1630        _ => return None,
1631    };
1632    if assignment.kind() != "assignment_expression" {
1633        return None;
1634    }
1635    let call = assignment.child_by_field_name("left")?;
1636    if call.kind() != "call_expression" {
1637        return None;
1638    }
1639    let function = call.child_by_field_name("function")?;
1640    if function.kind() != "identifier" || node_text(function, source) != "g_autoptr" {
1641        return None;
1642    }
1643    let arguments = call.child_by_field_name("arguments")?;
1644    let mut actuals = argument_children(arguments);
1645    let type_node = actuals.next()?;
1646    if actuals.next().is_some()
1647        || !matches!(
1648            type_node.kind(),
1649            "identifier"
1650                | "type_identifier"
1651                | "qualified_identifier"
1652                | "scoped_type_identifier"
1653                | "template_type"
1654        )
1655    {
1656        return None;
1657    }
1658    let name_node = (0..assignment.named_child_count())
1659        .filter_map(|index| assignment.named_child(index))
1660        .filter(|child| child.kind() == "ERROR")
1661        .filter_map(|error| {
1662            (error.named_child_count() == 1)
1663                .then(|| error.named_child(0))
1664                .flatten()
1665        })
1666        .find(|node| node.kind() == "identifier")?;
1667    let name = node_text(name_node, source).trim();
1668    let type_name = node_text(type_node, source).trim();
1669    if name.is_empty() || type_name.is_empty() {
1670        return None;
1671    }
1672    Some(MacroLocalBinding {
1673        name: name.to_string(),
1674        type_name: type_name.to_string(),
1675        type_node: Some(type_node),
1676        pointer_depth: 1,
1677        proven_unit: None,
1678    })
1679}
1680
1681#[derive(Clone, PartialEq, Eq)]
1682pub struct MacroBinding {
1683    source: ProjectFile,
1684    declaration_byte: usize,
1685    definition: MacroDefinition,
1686    exact: bool,
1687}
1688
1689impl MacroBinding {
1690    fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1691        Self {
1692            source: source.clone(),
1693            declaration_byte,
1694            definition: MacroDefinition::Unsupported,
1695            exact: false,
1696        }
1697    }
1698
1699    fn is_exact(&self) -> bool {
1700        self.exact
1701    }
1702
1703    fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1704        Self {
1705            source: source.clone(),
1706            declaration_byte,
1707            definition: current.definition.clone(),
1708            exact: false,
1709        }
1710    }
1711}
1712
1713/// The preprocessor conditionals whose truth decides whether one macro event
1714/// applies, by the start byte of each conditional node. Empty means the event
1715/// is unconditional.
1716///
1717/// [`VisibilityIndex::macro_event_condition_value`] needs exactly the
1718/// conditional ancestors that structurally contain the event, and deciding
1719/// containment means asking [`cpp_displaced_preprocessor_boundary`] for an
1720/// `#endif` tree-sitter displaced into error recovery -- a walk of the
1721/// conditional's whole subtree. That answer is a fact about the tree alone, so
1722/// it is settled once, when the events are collected, instead of on every
1723/// replay of the event (#1496).
1724type OwningPreprocessorConditionals = Box<[usize]>;
1725
1726#[derive(Clone)]
1727pub enum MacroEvent {
1728    Define {
1729        name: String,
1730        binding: MacroBinding,
1731        byte: usize,
1732        conditionals: OwningPreprocessorConditionals,
1733    },
1734    Undef {
1735        name: String,
1736        byte: usize,
1737        conditionals: OwningPreprocessorConditionals,
1738    },
1739    Include {
1740        targets: Vec<ProjectFile>,
1741        byte: usize,
1742        conditionals: OwningPreprocessorConditionals,
1743    },
1744    Invalidate {
1745        byte: usize,
1746    },
1747}
1748
1749impl MacroEvent {
1750    pub fn byte(&self) -> usize {
1751        match self {
1752            Self::Define { byte, .. }
1753            | Self::Undef { byte, .. }
1754            | Self::Include { byte, .. }
1755            | Self::Invalidate { byte } => *byte,
1756        }
1757    }
1758}
1759
1760#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1761pub enum CallArityEvidence {
1762    Exact(usize),
1763    Unknown,
1764}
1765
1766impl CallArityEvidence {
1767    pub fn exact(self) -> Option<usize> {
1768        match self {
1769            Self::Exact(arity) => Some(arity),
1770            Self::Unknown => None,
1771        }
1772    }
1773
1774    pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1775        self.exact().map(|arity| expected.accepts(arity))
1776    }
1777}
1778
1779struct CppAlias {
1780    name: String,
1781    target: String,
1782    namespace: Option<String>,
1783}
1784
1785type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1786
1787/// Why template-argument resolution failed. Definition diagnostics render
1788/// each mode differently; graph scans only care that the resolution is
1789/// unproven and match `Err(_)`.
1790#[derive(Debug, Clone, PartialEq, Eq)]
1791pub enum CppTemplateResolutionError {
1792    /// A template alias expansion revisited `alias`.
1793    AliasCycle { alias: CodeUnit },
1794    /// The explicit arguments do not bind to the declared template parameters.
1795    ArgumentBinding,
1796    /// Bound arguments do not substitute into the alias target's arguments.
1797    Substitution,
1798    /// No visible primary template declaration could be selected and
1799    /// reconciled for the specialization family.
1800    PrimarySelection,
1801    /// More than one applicable specialization remains and none is strictly
1802    /// more specialized than every other candidate.
1803    AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1804}
1805
1806/// The ambiguity candidates, deduplicated to one representative per visible
1807/// symbol so a diagnostic lists each contender once.
1808fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1809    let mut distinct: Vec<CodeUnit> = Vec::new();
1810    for unit in units {
1811        if !distinct
1812            .iter()
1813            .any(|existing| same_visible_symbol(existing, unit))
1814        {
1815            distinct.push(unit.clone());
1816        }
1817    }
1818    distinct
1819}
1820
1821/// Enumerate macro lexical references while constructing visibility only if
1822/// the bounded source walk finds a definition or identifier call candidate.
1823pub fn macro_lexical_references_with_visibility<'visibility, 'source: 'visibility, Factory>(
1824    visibility: Factory,
1825    file: &ProjectFile,
1826    root: Node<'_>,
1827    source: &str,
1828    max_references: usize,
1829    cancelled: impl FnMut() -> bool,
1830) -> MacroLexicalReferences
1831where
1832    Factory: FnOnce() -> &'visibility VisibilityIndex<'source>,
1833{
1834    crate::graph::macro_lexical::all_references(
1835        visibility,
1836        file,
1837        root,
1838        source,
1839        max_references,
1840        cancelled,
1841    )
1842}
1843
1844impl<'a> VisibilityIndex<'a> {
1845    pub fn data(&self) -> Arc<VisibilityIndexData> {
1846        Arc::clone(&self.data)
1847    }
1848
1849    pub fn from_data(
1850        cpp: &'a dyn CppSource,
1851        token: QueryToken<'a>,
1852        data: Arc<VisibilityIndexData>,
1853    ) -> Self {
1854        Self { cpp, token, data }
1855    }
1856
1857    pub fn cpp(&self) -> &'a dyn CppSource {
1858        self.cpp
1859    }
1860
1861    /// The request-scope proof this index was built with (issue #2414 step 3).
1862    pub fn token(&self) -> QueryToken<'a> {
1863        self.token
1864    }
1865
1866    /// Build the source-backed macro lexical index once per file in this
1867    /// visibility query. The entry cap keeps the query from retaining a
1868    /// workspace-sized collection when a batch visits many translation units.
1869    pub(crate) fn macro_lexical_index(
1870        &self,
1871        file: &ProjectFile,
1872        root: Node<'_>,
1873        source: &str,
1874    ) -> Arc<crate::graph::macro_lexical::MacroLexicalIndex> {
1875        if let Some(index) = self
1876            .macro_lexical_indexes
1877            .lock()
1878            .expect("macro lexical index cache poisoned")
1879            .get(file)
1880            .cloned()
1881        {
1882            debug_assert_eq!(
1883                index.source_len,
1884                source.len(),
1885                "macro lexical index for {file:?} was built from a different source"
1886            );
1887            return index;
1888        }
1889        let index = Arc::new(crate::graph::macro_lexical::build_index(
1890            self, file, root, source,
1891        ));
1892        let mut cache = self
1893            .macro_lexical_indexes
1894            .lock()
1895            .expect("macro lexical index cache poisoned");
1896        if let Some(existing) = cache.get(file).cloned() {
1897            return existing;
1898        }
1899        if cache.len() < MACRO_LEXICAL_INDEX_CACHE_CAP {
1900            cache.insert(file.clone(), Arc::clone(&index));
1901        }
1902        index
1903    }
1904
1905    /// The complete signature rows for one callable, shared by every
1906    /// candidate fact that the visibility query asks for. C++ reconciled
1907    /// members make this read substantially more expensive than an ordinary
1908    /// metadata lookup, so keep the answer on this per-query index and cap it
1909    /// explicitly.
1910    fn callable_metadata(&self, unit: &CodeUnit) -> Arc<Vec<SignatureMetadata>> {
1911        if let Some(metadata) = self
1912            .callable_metadata
1913            .lock()
1914            .expect("C++ callable metadata cache poisoned")
1915            .get(unit)
1916            .cloned()
1917        {
1918            return metadata;
1919        }
1920        let metadata = Arc::new(self.cpp.stored_signature_metadata(unit));
1921        let mut cache = self
1922            .callable_metadata
1923            .lock()
1924            .expect("C++ callable metadata cache poisoned");
1925        if cache.len() < CALLABLE_METADATA_CACHE_CAP {
1926            cache.insert(unit.clone(), Arc::clone(&metadata));
1927        }
1928        metadata
1929    }
1930
1931    /// Callable linkage backed by the query-local metadata memo.
1932    pub fn callable_linkage(&self, unit: &CodeUnit) -> Option<CallableLinkage> {
1933        crate::identity::cpp_callable_linkage_from_metadata(
1934            self.callable_metadata(unit).iter().cloned(),
1935        )
1936    }
1937
1938    /// Callable arity backed by the same query-local metadata memo.
1939    pub fn callable_arity(&self, unit: &CodeUnit) -> Option<CallableArity> {
1940        brokk_bifrost_core::analyzer::model::first_callable_arity(
1941            self.callable_metadata(unit).iter().cloned(),
1942        )
1943    }
1944
1945    /// Callable parameter types backed by the same stored-metadata memo.
1946    /// Parameter spellings are source-owned facts and do not depend on the
1947    /// modifier joins that full `signature_metadata` performs for an
1948    /// out-of-line member.
1949    pub fn callable_parameter_types(&self, unit: &CodeUnit) -> Option<Vec<String>> {
1950        brokk_bifrost_core::analyzer::model::first_callable_parameter_types(
1951            self.callable_metadata(unit).iter().cloned(),
1952        )
1953    }
1954
1955    /// Whether `file` has a structured unresolved include that is active before
1956    /// the reference at `before_byte`.
1957    ///
1958    /// Include facts are collected from the prepared tree once per visibility
1959    /// query. The reference position is still evaluated for each call because
1960    /// preprocessor visibility depends on the reference's own conditional
1961    /// context.
1962    pub fn has_unresolved_include_visible_before(
1963        &self,
1964        file: &ProjectFile,
1965        before_byte: usize,
1966    ) -> bool {
1967        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
1968            return false;
1969        };
1970        let facts = self.structured_include_facts(file, prepared.as_ref());
1971        has_unresolved_include_visible_before_in_prepared(
1972            file,
1973            prepared.as_ref(),
1974            self.cpp.include_target_index(),
1975            &facts,
1976            before_byte,
1977        )
1978    }
1979
1980    fn structured_include_facts(
1981        &self,
1982        file: &ProjectFile,
1983        prepared: &PreparedSyntaxTree,
1984    ) -> Arc<[StructuredIncludeFact]> {
1985        let cell = self
1986            .structured_include_fact_cells
1987            .lock()
1988            .expect("C++ structured include-fact cache poisoned")
1989            .entry(file.clone())
1990            .or_default()
1991            .clone();
1992        Arc::clone(cell.get_or_init(|| collect_structured_include_facts(prepared)))
1993    }
1994
1995    /// A [`VisibilityIndex`] over a caller-supplied visible-declaration map,
1996    /// bypassing the include-closure walk [`Self::build`] performs.
1997    ///
1998    /// The resolver's own unit tests drive the type-resolution paths against a
1999    /// hand-written visibility table; they live in `brokk-bifrost-analysis`
2000    /// because they need a real `CppAnalyzer`, so the struct literal they used
2001    /// to write inline is here instead of thirty-three public fields.
2002    #[cfg(any(test, feature = "test-support"))]
2003    pub fn from_visible_files_for_test(
2004        cpp: &'a dyn CppSource,
2005        token: QueryToken<'a>,
2006        visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
2007    ) -> Self {
2008        let visible_source_files_by_root = visible_by_file
2009            .iter()
2010            .map(|(file, visible)| {
2011                (
2012                    file.clone(),
2013                    visible
2014                        .iter()
2015                        .map(|unit| unit.source().clone())
2016                        .chain(std::iter::once(file.clone()))
2017                        .collect(),
2018                )
2019            })
2020            .collect();
2021        let mut global_field_internal_linkage = HashMap::default();
2022        Self {
2023            cpp,
2024            token,
2025            data: Arc::new(VisibilityIndexData {
2026                visible_by_identifier: build_visible_identifier_index(
2027                    &CppGraphSource::from_source(cpp, token),
2028                    &visible_by_file,
2029                    &visible_source_files_by_root,
2030                    &mut global_field_internal_linkage,
2031                ),
2032                global_field_internal_linkage,
2033                visible_by_file,
2034                visible_source_files_by_root,
2035                alias_cells: Mutex::new(HashMap::default()),
2036                visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2037                parser_alias_target_matches: RwLock::new(HashMap::default()),
2038                ordinary_type_import_cells: Mutex::new(HashMap::default()),
2039                project_using_index: OnceLock::new(),
2040                callable_reference_specs: Mutex::new(HashMap::default()),
2041                structured_include_fact_cells: Mutex::new(HashMap::default()),
2042                include_activation_cells: Mutex::new(HashMap::default()),
2043                compile_proven_guard_cells: Mutex::new(HashMap::default()),
2044                include_path_admission_cells: Mutex::new(HashMap::default()),
2045                conditional_include_projection_cells: Mutex::new(HashMap::default()),
2046                conditional_include_projection_index_build_count: AtomicUsize::new(0),
2047                conditional_include_projection_state_count: AtomicUsize::new(0),
2048                conditional_include_target_state_count: AtomicUsize::new(0),
2049                include_activation_build_count: AtomicUsize::new(0),
2050                using_donor_activation_count: AtomicUsize::new(0),
2051                using_namespace_lookup_count: AtomicUsize::new(0),
2052                using_name_candidate_inspection_count: AtomicUsize::new(0),
2053                callable_reference_spec_build_count: AtomicUsize::new(0),
2054                alias_source_parse_counts: Mutex::new(HashMap::default()),
2055                visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2056                parser_alias_fallback_calls: AtomicUsize::new(0),
2057                parser_alias_fallback_files: AtomicUsize::new(0),
2058                parser_alias_source_parses: AtomicUsize::new(0),
2059                parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2060                field_type_facts: Mutex::new(HashMap::default()),
2061                structured_alias_targets: Mutex::new(HashMap::default()),
2062                callable_comparables: Mutex::new(HashMap::default()),
2063                callable_metadata: Mutex::new(HashMap::default()),
2064                comparable_name_declarations: Mutex::new(HashMap::default()),
2065                indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2066                indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2067                precise_parent_cache: Mutex::new(HashMap::default()),
2068                c_tag_kind_cache: Mutex::new(HashMap::default()),
2069                c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2070                macro_event_cells: Mutex::new(HashMap::default()),
2071                macro_event_name_sets: Mutex::new(HashMap::default()),
2072                macro_include_protection_cells: Mutex::new(HashMap::default()),
2073                macro_environment_checkpoints: Mutex::new(HashMap::default()),
2074                macro_replacements: Mutex::new(HashMap::default()),
2075                macro_local_binding_templates: Mutex::new(HashMap::default()),
2076                macro_lexical_templates: Mutex::new(HashMap::default()),
2077                macro_lexical_indexes: Mutex::new(HashMap::default()),
2078                macro_replacement_bodies: Mutex::new(HashMap::default()),
2079                macro_type_parameters: Mutex::new(HashMap::default()),
2080                callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2081                macro_replacement_parse_count: AtomicUsize::new(0),
2082                macro_event_application_count: AtomicUsize::new(0),
2083                macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2084                macro_environment_copy_count: AtomicUsize::new(0),
2085                macro_environment_request_count: AtomicUsize::new(0),
2086                cpp_template_metadata: HashMap::default(),
2087                cpp_template_families: HashMap::default(),
2088                qualified_candidate_inspections: AtomicUsize::new(0),
2089                target_preserving_type_resolution_count: AtomicUsize::new(0),
2090                visibility_identifier_lookup_count: 0,
2091                visibility_identifier_batch_count: 0,
2092            }),
2093        }
2094    }
2095
2096    /// The index's own C++ source, in the dispatching-analyzer shape.
2097    ///
2098    /// Four resolution paths reach the workspace through the C++ analyzer they
2099    /// already hold rather than through the analyzer the query was issued
2100    /// against; before the move they passed `&CppAnalyzer` straight into a
2101    /// `&dyn IAnalyzer` parameter. See [`CppGraphSource::from_source`].
2102    fn cpp_source(&self) -> CppGraphSource<'a> {
2103        CppGraphSource::from_source(self.cpp, self.token)
2104    }
2105
2106    pub fn build(
2107        cpp: &'a dyn CppSource,
2108        token: QueryToken<'a>,
2109        analyzer: &CppGraphSource<'_>,
2110        roots: &HashSet<ProjectFile>,
2111    ) -> Self {
2112        Self::build_with_cancellation(cpp, token, analyzer, roots, None)
2113    }
2114
2115    pub fn build_with_cancellation(
2116        cpp: &'a dyn CppSource,
2117        token: QueryToken<'a>,
2118        analyzer: &CppGraphSource<'_>,
2119        roots: &HashSet<ProjectFile>,
2120        cancellation: Option<&CancellationToken>,
2121    ) -> Self {
2122        let visibility_started = Instant::now();
2123        let include_targets = cpp.include_target_index();
2124        let includes_started = Instant::now();
2125        let mut include_graph = IncludeGraph::default();
2126        for root in roots {
2127            include_graph.extend_with(root, cancellation, &mut |file| {
2128                cpp.canonical_include_paths(token, file)
2129                    .unwrap_or_default()
2130                    .into_iter()
2131                    .flat_map(|include| {
2132                        resolve_include_targets_with_index(file, &include, include_targets)
2133                    })
2134                    .collect()
2135            });
2136        }
2137        let include_elapsed = includes_started.elapsed();
2138        let include_file_count = include_graph.files().count();
2139        let visible_source_files_by_root = roots
2140            .iter()
2141            .map(|root| {
2142                (
2143                    root.clone(),
2144                    include_graph.reachable_files(root, cancellation),
2145                )
2146            })
2147            .collect::<HashMap<_, _>>();
2148        let mut visibility_stats = BoundedVisibilityStats::default();
2149        let mut visible_by_file = build_bounded_visible_declarations(
2150            cpp,
2151            token,
2152            analyzer,
2153            roots,
2154            &visible_source_files_by_root,
2155            cancellation,
2156            &mut visibility_stats,
2157        );
2158        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
2159            eprintln!(
2160                "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={}",
2161                visibility_started.elapsed().as_millis(),
2162                include_elapsed.as_millis(),
2163                include_file_count,
2164                visibility_stats.rounds,
2165                visibility_stats.root_names,
2166                visibility_stats.identifier_lookups,
2167                visibility_stats.identifier_batches,
2168                visibility_stats.candidate_units,
2169                visibility_stats.candidate_sources,
2170                visibility_stats.declaration_reads,
2171                visibility_stats.declaration_units,
2172                visibility_stats.selected_units,
2173                visibility_stats.dependency_source_names,
2174                visibility_stats.dependency_names,
2175                visibility_stats.lookup_elapsed.as_millis(),
2176                visibility_stats.declaration_elapsed.as_millis(),
2177                visibility_stats.dependency_source_elapsed.as_millis(),
2178            );
2179        }
2180        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
2181        let finalize_started = Instant::now();
2182        if report_stats {
2183            eprintln!(
2184                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=started roots={} visible_units={}",
2185                visible_by_file.len(),
2186                visible_by_file.values().map(HashSet::len).sum::<usize>(),
2187            );
2188        }
2189        let owner_started = Instant::now();
2190        if report_stats {
2191            eprintln!("BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=started");
2192        }
2193        let owner_stats = extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
2194        if report_stats {
2195            eprintln!(
2196                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=completed unseen_owners={} definition_lookups={} admitted={} elapsed_ms={}",
2197                owner_stats.unseen_owners,
2198                owner_stats.definition_lookups,
2199                owner_stats.admitted,
2200                owner_started.elapsed().as_millis(),
2201            );
2202        }
2203        let mut global_field_internal_linkage = HashMap::default();
2204        let identifier_started = Instant::now();
2205        if report_stats {
2206            eprintln!(
2207                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=started"
2208            );
2209        }
2210        let visible_by_identifier = build_visible_identifier_index(
2211            analyzer,
2212            &visible_by_file,
2213            &visible_source_files_by_root,
2214            &mut global_field_internal_linkage,
2215        );
2216        if report_stats {
2217            eprintln!(
2218                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=completed roots={} names={} candidates={} elapsed_ms={}",
2219                visible_by_identifier.len(),
2220                visible_by_identifier
2221                    .values()
2222                    .map(HashMap::len)
2223                    .sum::<usize>(),
2224                visible_by_identifier
2225                    .values()
2226                    .flat_map(HashMap::values)
2227                    .map(Vec::len)
2228                    .sum::<usize>(),
2229                identifier_started.elapsed().as_millis(),
2230            );
2231        }
2232        let mut cpp_template_metadata = HashMap::default();
2233        let metadata_started = Instant::now();
2234        let mut template_classes = 0usize;
2235        if report_stats {
2236            eprintln!(
2237                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=started"
2238            );
2239        }
2240        for unit in visible_by_file
2241            .values()
2242            .flatten()
2243            .filter(|unit| unit.is_class())
2244        {
2245            template_classes += 1;
2246            if cpp_template_metadata.contains_key(unit) {
2247                continue;
2248            }
2249            if let Some(metadata) = cpp.template_metadata(unit) {
2250                cpp_template_metadata.insert(unit.clone(), metadata);
2251            }
2252        }
2253        if report_stats {
2254            eprintln!(
2255                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=completed classes={} metadata={} elapsed_ms={}",
2256                template_classes,
2257                cpp_template_metadata.len(),
2258                metadata_started.elapsed().as_millis(),
2259            );
2260        }
2261        let families_started = Instant::now();
2262        if report_stats {
2263            eprintln!(
2264                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=started"
2265            );
2266        }
2267        let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
2268        for (unit, metadata) in &cpp_template_metadata {
2269            cpp_template_families
2270                .entry(metadata.primary_fq_name.clone())
2271                .or_default()
2272                .push(unit.clone());
2273        }
2274        // `cpp_template_metadata` is hash-keyed on `CodeUnit`, so the push
2275        // order above is a function of those hashes. Two mirrored headers can
2276        // declare one specialization; `select_template_specialization` treats
2277        // them as interchangeable and returns the family's first entry, so an
2278        // unsorted family made the reported declaration depend on the
2279        // workspace's absolute path and on unrelated files (#1836). Order the
2280        // family exactly as `build_visible_identifier_index` orders its
2281        // per-identifier candidate lists.
2282        for family in cpp_template_families.values_mut() {
2283            sort_lookup_units(family);
2284        }
2285        if report_stats {
2286            eprintln!(
2287                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=completed families={} members={} elapsed_ms={}",
2288                cpp_template_families.len(),
2289                cpp_template_families.values().map(Vec::len).sum::<usize>(),
2290                families_started.elapsed().as_millis(),
2291            );
2292            eprintln!(
2293                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=completed roots={} visible_units={} elapsed_ms={} total_ms={}",
2294                visible_by_file.len(),
2295                visible_by_file.values().map(HashSet::len).sum::<usize>(),
2296                finalize_started.elapsed().as_millis(),
2297                visibility_started.elapsed().as_millis(),
2298            );
2299        }
2300        Self {
2301            cpp,
2302            token,
2303            data: Arc::new(VisibilityIndexData {
2304                visible_by_file,
2305                visible_by_identifier,
2306                global_field_internal_linkage,
2307                visible_source_files_by_root,
2308                alias_cells: Mutex::new(HashMap::default()),
2309                visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2310                parser_alias_target_matches: RwLock::new(HashMap::default()),
2311                ordinary_type_import_cells: Mutex::new(HashMap::default()),
2312                project_using_index: OnceLock::new(),
2313                callable_reference_specs: Mutex::new(HashMap::default()),
2314                structured_include_fact_cells: Mutex::new(HashMap::default()),
2315                include_activation_cells: Mutex::new(HashMap::default()),
2316                compile_proven_guard_cells: Mutex::new(HashMap::default()),
2317                include_path_admission_cells: Mutex::new(HashMap::default()),
2318                conditional_include_projection_cells: Mutex::new(HashMap::default()),
2319                #[cfg(any(test, feature = "test-support"))]
2320                conditional_include_projection_index_build_count: AtomicUsize::new(0),
2321                #[cfg(any(test, feature = "test-support"))]
2322                conditional_include_projection_state_count: AtomicUsize::new(0),
2323                #[cfg(any(test, feature = "test-support"))]
2324                conditional_include_target_state_count: AtomicUsize::new(0),
2325                #[cfg(any(test, feature = "test-support"))]
2326                include_activation_build_count: AtomicUsize::new(0),
2327                #[cfg(any(test, feature = "test-support"))]
2328                using_donor_activation_count: AtomicUsize::new(0),
2329                #[cfg(any(test, feature = "test-support"))]
2330                using_namespace_lookup_count: AtomicUsize::new(0),
2331                #[cfg(any(test, feature = "test-support"))]
2332                using_name_candidate_inspection_count: AtomicUsize::new(0),
2333                #[cfg(any(test, feature = "test-support"))]
2334                callable_reference_spec_build_count: AtomicUsize::new(0),
2335                #[cfg(any(test, feature = "test-support"))]
2336                alias_source_parse_counts: Mutex::new(HashMap::default()),
2337                #[cfg(any(test, feature = "test-support"))]
2338                visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2339                parser_alias_fallback_calls: AtomicUsize::new(0),
2340                parser_alias_fallback_files: AtomicUsize::new(0),
2341                parser_alias_source_parses: AtomicUsize::new(0),
2342                parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2343                field_type_facts: Mutex::new(HashMap::default()),
2344                structured_alias_targets: Mutex::new(HashMap::default()),
2345                callable_comparables: Mutex::new(HashMap::default()),
2346                callable_metadata: Mutex::new(HashMap::default()),
2347                comparable_name_declarations: Mutex::new(HashMap::default()),
2348                indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2349                indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2350                precise_parent_cache: Mutex::new(HashMap::default()),
2351                c_tag_kind_cache: Mutex::new(HashMap::default()),
2352                c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2353                macro_event_cells: Mutex::new(HashMap::default()),
2354                macro_event_name_sets: Mutex::new(HashMap::default()),
2355                macro_include_protection_cells: Mutex::new(HashMap::default()),
2356                macro_environment_checkpoints: Mutex::new(HashMap::default()),
2357                macro_replacements: Mutex::new(HashMap::default()),
2358                macro_local_binding_templates: Mutex::new(HashMap::default()),
2359                macro_lexical_templates: Mutex::new(HashMap::default()),
2360                macro_lexical_indexes: Mutex::new(HashMap::default()),
2361                macro_replacement_bodies: Mutex::new(HashMap::default()),
2362                macro_type_parameters: Mutex::new(HashMap::default()),
2363                callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2364                #[cfg(any(test, feature = "test-support"))]
2365                macro_replacement_parse_count: AtomicUsize::new(0),
2366                #[cfg(any(test, feature = "test-support"))]
2367                macro_event_application_count: AtomicUsize::new(0),
2368                #[cfg(any(test, feature = "test-support"))]
2369                macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2370                #[cfg(any(test, feature = "test-support"))]
2371                macro_environment_copy_count: AtomicUsize::new(0),
2372                #[cfg(any(test, feature = "test-support"))]
2373                macro_environment_request_count: AtomicUsize::new(0),
2374                cpp_template_metadata,
2375                cpp_template_families,
2376                #[cfg(any(test, feature = "test-support"))]
2377                qualified_candidate_inspections: AtomicUsize::new(0),
2378                #[cfg(any(test, feature = "test-support"))]
2379                target_preserving_type_resolution_count: AtomicUsize::new(0),
2380                #[cfg(any(test, feature = "test-support"))]
2381                visibility_identifier_lookup_count: visibility_stats.identifier_lookups,
2382                #[cfg(any(test, feature = "test-support"))]
2383                visibility_identifier_batch_count: visibility_stats.identifier_batches,
2384            }),
2385        }
2386    }
2387
2388    pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2389        if file == target.source() {
2390            return true;
2391        }
2392        if self.global_field_has_internal_linkage(target) {
2393            return self
2394                .visible_source_files_by_root
2395                .get(file)
2396                .is_some_and(|sources| sources.contains(target.source()));
2397        }
2398        self.visible_by_file
2399            .get(file)
2400            .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
2401    }
2402
2403    fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
2404        self.global_field_internal_linkage
2405            .get(unit)
2406            .copied()
2407            .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
2408    }
2409
2410    pub fn call_arity_evidence(
2411        &self,
2412        file: &ProjectFile,
2413        call: Node<'_>,
2414        source: &str,
2415    ) -> CallArityEvidence {
2416        self.call_arity_evidence_at(file, call, source, call.start_byte())
2417    }
2418
2419    /// Argument-count evidence for a call whose macro environment is not the
2420    /// one at its own byte offset.
2421    ///
2422    /// A call recovered from a macro replacement lives in a sentinel parse of
2423    /// its own, so its node offsets say nothing about which macros are active.
2424    /// `environment_byte` names the position in `file` whose macro environment
2425    /// governs the call: the macro definition site for a replacement body.
2426    pub fn call_arity_evidence_at(
2427        &self,
2428        file: &ProjectFile,
2429        call: Node<'_>,
2430        source: &str,
2431        environment_byte: usize,
2432    ) -> CallArityEvidence {
2433        let Some(arguments) = call
2434            .child_by_field_name("arguments")
2435            .or_else(|| call.child_by_field_name("parameters"))
2436            .or_else(|| call.child_by_field_name("value"))
2437            .or_else(|| first_named_child_of_kind(call, "argument_list"))
2438            .or_else(|| first_named_child_of_kind(call, "initializer_list"))
2439        else {
2440            return CallArityEvidence::Exact(0);
2441        };
2442        let recovered_c_keyword_arguments =
2443            recovered_c_keyword_argument_count(file, call, arguments, source);
2444        let c_semantics = reference_uses_c_semantics(self.cpp, file);
2445        let arguments = argument_children(arguments)
2446            .flat_map(|argument| {
2447                recovered_c_new_expression_arguments(argument, c_semantics)
2448                    .map(Vec::from)
2449                    .unwrap_or_else(|| vec![argument])
2450            })
2451            .collect::<Vec<_>>();
2452        if arguments
2453            .iter()
2454            .all(|argument| !argument_shape_may_change_arity(*argument))
2455        {
2456            return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
2457        }
2458        let environment = self.macro_environment(file, environment_byte);
2459        let mut stack = Vec::new();
2460        let mut total = recovered_c_keyword_arguments;
2461        for argument in arguments {
2462            if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
2463                return CallArityEvidence::Unknown;
2464            }
2465            let CallArityEvidence::Exact(spread) =
2466                self.argument_arity_evidence(argument, source, &environment, &mut stack)
2467            else {
2468                return CallArityEvidence::Unknown;
2469            };
2470            total += spread;
2471        }
2472        CallArityEvidence::Exact(total)
2473    }
2474
2475    fn argument_arity_evidence(
2476        &self,
2477        argument: Node<'_>,
2478        source: &str,
2479        environment: &MacroEnvironment,
2480        stack: &mut Vec<(ProjectFile, usize)>,
2481    ) -> CallArityEvidence {
2482        let (name, invocation_arguments, function_like) = match argument.kind() {
2483            "identifier" => (node_text(argument, source), None, false),
2484            "call_expression" => {
2485                let Some(function) = argument.child_by_field_name("function") else {
2486                    return CallArityEvidence::Exact(1);
2487                };
2488                if function.kind() != "identifier" {
2489                    return CallArityEvidence::Exact(1);
2490                }
2491                let Some(arguments) = argument.child_by_field_name("arguments") else {
2492                    return CallArityEvidence::Exact(1);
2493                };
2494                (node_text(function, source), Some(arguments), true)
2495            }
2496            _ => return CallArityEvidence::Exact(1),
2497        };
2498        let Some(binding) = environment.binding(name) else {
2499            return if environment.unknown_names {
2500                CallArityEvidence::Unknown
2501            } else {
2502                CallArityEvidence::Exact(1)
2503            };
2504        };
2505        if !binding.is_exact() {
2506            return CallArityEvidence::Unknown;
2507        }
2508        match (&binding.definition, invocation_arguments, function_like) {
2509            (MacroDefinition::Object { replacement }, None, false) => self
2510                .replacement_arity_evidence(
2511                    replacement,
2512                    &[],
2513                    &[],
2514                    source,
2515                    environment,
2516                    stack,
2517                    binding,
2518                ),
2519            (
2520                MacroDefinition::Function {
2521                    parameters,
2522                    replacement,
2523                },
2524                Some(arguments),
2525                true,
2526            ) => {
2527                let actuals = argument_children(arguments).collect::<Vec<_>>();
2528                if actuals.len() != parameters.len() {
2529                    CallArityEvidence::Unknown
2530                } else {
2531                    self.replacement_arity_evidence(
2532                        replacement,
2533                        parameters,
2534                        &actuals,
2535                        source,
2536                        environment,
2537                        stack,
2538                        binding,
2539                    )
2540                }
2541            }
2542            (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
2543            _ => CallArityEvidence::Unknown,
2544        }
2545    }
2546
2547    #[allow(clippy::too_many_arguments)]
2548    fn replacement_arity_evidence(
2549        &self,
2550        replacement: &str,
2551        parameters: &[String],
2552        actuals: &[Node<'_>],
2553        actual_source: &str,
2554        environment: &MacroEnvironment,
2555        stack: &mut Vec<(ProjectFile, usize)>,
2556        binding: &MacroBinding,
2557    ) -> CallArityEvidence {
2558        let identity = (binding.source.clone(), binding.declaration_byte);
2559        if stack.contains(&identity) || replacement.trim().is_empty() {
2560            return CallArityEvidence::Unknown;
2561        }
2562        stack.push(identity);
2563        let parsed = self.parsed_macro_replacement(binding, replacement);
2564        let evidence = (|| {
2565            let ParsedMacroReplacement::Parsed {
2566                source: sentinel,
2567                tree,
2568            } = parsed.as_ref()
2569            else {
2570                return None;
2571            };
2572            let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
2573            let arguments = call.child_by_field_name("arguments")?;
2574            let mut total = 0usize;
2575            for argument in argument_children(arguments) {
2576                if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
2577                    return None;
2578                }
2579                if argument.kind() == "identifier"
2580                    && let Some(parameter_index) = parameters
2581                        .iter()
2582                        .position(|parameter| parameter == node_text(argument, sentinel))
2583                {
2584                    if !macro_expansion_shape_is_safe(
2585                        actuals[parameter_index],
2586                        actual_source,
2587                        &[],
2588                        environment,
2589                    ) {
2590                        return None;
2591                    }
2592                    let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
2593                        actuals[parameter_index],
2594                        actual_source,
2595                        environment,
2596                        stack,
2597                    ) else {
2598                        return None;
2599                    };
2600                    total += spread;
2601                    continue;
2602                }
2603                let CallArityEvidence::Exact(spread) =
2604                    self.argument_arity_evidence(argument, sentinel, environment, stack)
2605                else {
2606                    return None;
2607                };
2608                total += spread;
2609            }
2610            Some(CallArityEvidence::Exact(total))
2611        })()
2612        .unwrap_or(CallArityEvidence::Unknown);
2613        stack.pop();
2614        evidence
2615    }
2616
2617    fn parsed_macro_replacement(
2618        &self,
2619        binding: &MacroBinding,
2620        replacement: &str,
2621    ) -> Arc<ParsedMacroReplacement> {
2622        let key = (binding.source.clone(), binding.declaration_byte);
2623        let mut cache = self
2624            .macro_replacements
2625            .lock()
2626            .expect("C++ macro replacement cache poisoned");
2627        if let Some(parsed) = cache.get(&key) {
2628            return Arc::clone(parsed);
2629        }
2630        #[cfg(any(test, feature = "test-support"))]
2631        self.macro_replacement_parse_count
2632            .fetch_add(1, Ordering::Relaxed);
2633        let source =
2634            format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
2635        let mut parser = Parser::new();
2636        let parsed = parser
2637            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2638            .ok()
2639            .and_then(|()| parser.parse(&source, None))
2640            .filter(|tree| !tree.root_node().has_error())
2641            .map_or(ParsedMacroReplacement::Unsupported, |tree| {
2642                ParsedMacroReplacement::Parsed { source, tree }
2643            });
2644        let parsed = Arc::new(parsed);
2645        cache.insert(key, Arc::clone(&parsed));
2646        parsed
2647    }
2648
2649    /// Recover a typed local declared by an active C function-like macro.
2650    ///
2651    /// This is intentionally narrower than macro expansion. The replacement
2652    /// must parse as one declaration, and the invocation must bind every
2653    /// formal parameter to one structured argument. That is sufficient for
2654    /// declaration macros such as `THIS(StorageAzure)`. An unavailable include
2655    /// can make the binding provisional without erasing its last known
2656    /// definition; an explicit conflicting definition still replaces it with
2657    /// Unsupported. Malformed and statement-producing macros also fail closed.
2658    pub fn function_macro_local_binding<'tree>(
2659        &self,
2660        file: &ProjectFile,
2661        statement: Node<'tree>,
2662        source: &str,
2663    ) -> Option<MacroLocalBinding<'tree>> {
2664        if !is_c_source_file(file) {
2665            return None;
2666        }
2667        if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
2668            return Some(binding);
2669        }
2670        let call = match statement.kind() {
2671            "call_expression" => statement,
2672            "expression_statement" if statement.named_child_count() == 1 => {
2673                statement.named_child(0)?
2674            }
2675            _ => return None,
2676        };
2677        if call.kind() != "call_expression" {
2678            return None;
2679        }
2680        let function = call.child_by_field_name("function")?;
2681        if function.kind() != "identifier" {
2682            return None;
2683        }
2684        let arguments = call.child_by_field_name("arguments")?;
2685        let actuals = argument_children(arguments).collect::<Vec<_>>();
2686        let environment = self.macro_environment(file, call.start_byte());
2687        let function_name = node_text(function, source);
2688        let binding = environment.binding(function_name)?;
2689        let MacroDefinition::Function {
2690            parameters,
2691            replacement,
2692        } = &binding.definition
2693        else {
2694            return None;
2695        };
2696        if actuals.len() != parameters.len() {
2697            return None;
2698        }
2699        let template = self.macro_local_binding_template(binding, parameters, replacement)?;
2700        let (type_name, type_node) = match &template.declared_type {
2701            MacroLocalBindingTypeTemplate::Parameter(index) => {
2702                let actual = *actuals.get(*index)?;
2703                if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
2704                    return None;
2705                }
2706                (node_text(actual, source).trim().to_string(), Some(actual))
2707            }
2708            MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
2709        };
2710        if type_name.is_empty() {
2711            return None;
2712        }
2713        Some(MacroLocalBinding {
2714            name: template.name.clone(),
2715            type_name,
2716            type_node,
2717            pointer_depth: template.pointer_depth,
2718            proven_unit: None,
2719        })
2720    }
2721
2722    /// Recover the typed receiver established by a C container macro
2723    /// assignment, such as `value = container_of(ptr, struct item, link)`.
2724    ///
2725    /// The macro definition's parsed replacement identifies the one formal
2726    /// parameter used in type position.  The invocation supplies the actual
2727    /// type node, which is then resolved through the ordinary visibility
2728    /// index.  Calls with no unique type-position parameter, an unresolved
2729    /// type, or an uncertain macro environment remain unproven.
2730    pub fn function_macro_container_binding<'tree>(
2731        &self,
2732        analyzer: &CppGraphSource<'_>,
2733        file: &ProjectFile,
2734        assignment: Node<'tree>,
2735        source: &str,
2736    ) -> Option<MacroLocalBinding<'tree>> {
2737        if !is_c_source_file(file) || assignment.kind() != "assignment_expression" {
2738            return None;
2739        }
2740        let name_node = assignment.child_by_field_name("left")?;
2741        if name_node.kind() != "identifier" {
2742            return None;
2743        }
2744        let call = assignment.child_by_field_name("right")?;
2745        if call.kind() != "call_expression" {
2746            return None;
2747        }
2748        let function = call.child_by_field_name("function")?;
2749        if function.kind() != "identifier" {
2750            return None;
2751        }
2752        let arguments = call.child_by_field_name("arguments")?;
2753        let actuals = argument_children(arguments).collect::<Vec<_>>();
2754        let function_name = node_text(function, source);
2755        let environment = self.macro_environment(file, call.start_byte());
2756        let binding = environment.binding(function_name)?;
2757        if !binding.is_exact() {
2758            return None;
2759        }
2760        let MacroDefinition::Function {
2761            parameters,
2762            replacement,
2763        } = &binding.definition
2764        else {
2765            return None;
2766        };
2767        if actuals.len() != parameters.len() {
2768            return None;
2769        }
2770        let body = self.parsed_macro_replacement_body(
2771            &(binding.source.clone(), binding.declaration_byte),
2772            parameters,
2773            replacement,
2774        )?;
2775        let type_parameter = macro_replacement_type_parameter(&body, parameters)?;
2776        let type_argument = *actuals.get(type_parameter)?;
2777        let type_node = macro_type_argument_node(type_argument, source)?;
2778        let type_name = node_text(type_node, source).trim().to_string();
2779        if type_name.is_empty() {
2780            return None;
2781        }
2782        let explicit_tag = match node_text(type_argument, source) {
2783            "struct" => Some(CppCTagKind::Struct),
2784            "union" => Some(CppCTagKind::Union),
2785            _ => None,
2786        };
2787        let resolved_type = if let Some(tag) = explicit_tag {
2788            let candidates = self
2789                .visible_identifier_candidates(file, &type_name)
2790                .filter(|candidate| self.cached_c_tag_kind(analyzer, candidate) == Some(tag))
2791                .collect::<Vec<_>>();
2792            self.resolve_type_candidates(
2793                analyzer,
2794                file,
2795                &candidates,
2796                TypeCandidateResolution::Canonical,
2797            )
2798            .ok()
2799        } else {
2800            self.resolve_type_node_result(file, type_node, source)
2801                .ok()
2802                .flatten()
2803        };
2804        let proven_unit = resolved_type?;
2805        Some(MacroLocalBinding {
2806            name: node_text(name_node, source).to_string(),
2807            type_name,
2808            type_node: Some(type_node),
2809            pointer_depth: 1,
2810            proven_unit: Some(proven_unit),
2811        })
2812    }
2813
2814    /// Recover the invocation-specific type of a replacement local visible
2815    /// at the selected source token.
2816    pub fn macro_local_binding_at<'tree>(
2817        &self,
2818        file: &ProjectFile,
2819        root: Node<'tree>,
2820        source: &str,
2821        start_byte: usize,
2822        end_byte: usize,
2823    ) -> Option<MacroLocalBinding<'tree>> {
2824        crate::graph::macro_lexical::typed_binding(self, file, root, source, start_byte, end_byte)
2825    }
2826
2827    /// Resolve the source-backed lexical macro declaration for one source
2828    /// range. The range may be a point range (`start..start + 1`) inside an
2829    /// identifier, which is the form used by goto-definition queries.
2830    pub fn macro_lexical_binding(
2831        &self,
2832        file: &ProjectFile,
2833        root: Node<'_>,
2834        source: &str,
2835        start_byte: usize,
2836        end_byte: usize,
2837    ) -> Option<MacroLexicalBinding> {
2838        crate::graph::macro_lexical::binding(self, file, root, source, start_byte, end_byte)
2839    }
2840
2841    /// Enumerate source ranges whose lexical meaning comes from a
2842    /// function-like macro formal or replacement local. Definition spellings
2843    /// themselves are omitted; callers needing goto-definition on a spelling
2844    /// use [`Self::macro_lexical_binding`].
2845    pub fn macro_lexical_references(
2846        &self,
2847        file: &ProjectFile,
2848        root: Node<'_>,
2849        source: &str,
2850        max_references: usize,
2851        cancelled: impl FnMut() -> bool,
2852    ) -> MacroLexicalReferences {
2853        crate::graph::macro_lexical::all_references(
2854            || self,
2855            file,
2856            root,
2857            source,
2858            max_references,
2859            cancelled,
2860        )
2861    }
2862
2863    /// Return the source identity of the exact active function-like macro
2864    /// binding. The definition AST is hydrated by the lexical helper from
2865    /// this `(ProjectFile, declaration byte)` pair, so no parser node escapes
2866    /// the prepared source borrow.
2867    pub(crate) fn function_macro_binding_at(
2868        &self,
2869        file: &ProjectFile,
2870        name: &str,
2871        before_byte: usize,
2872    ) -> Option<(ProjectFile, usize)> {
2873        let environment = self.macro_environment(file, before_byte);
2874        let binding = environment.binding(name)?;
2875        if binding.is_exact()
2876            && matches!(
2877                binding.definition,
2878                MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2879            )
2880        {
2881            return Some((binding.source.clone(), binding.declaration_byte));
2882        }
2883        // A definition under an unknown condition is still certain at a use
2884        // within that same branch. The checkpoint retains the event that made
2885        // the binding uncertain. Require that exact defining event, so an
2886        // intervening undef, include, or conditional redefinition cannot be
2887        // bypassed by a lexical search for an older macro.
2888        if binding.source != *file {
2889            return None;
2890        }
2891        let prepared = self.cpp.prepared_syntax(self.token, file)?;
2892        let root = prepared.tree().root_node();
2893        let source = prepared.source();
2894        let reference = root.descendant_for_byte_range(
2895            before_byte,
2896            before_byte.saturating_add(1).min(source.len()),
2897        )?;
2898        let reference_conditions = owning_preprocessor_conditionals(root, reference, source);
2899        let cell = self.macro_event_cell(file);
2900        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2901        let event = events
2902            .iter()
2903            .find(|event| event.byte() == binding.declaration_byte)?;
2904        let MacroEvent::Define {
2905            name: defined_name,
2906            binding: definition,
2907            conditionals,
2908            ..
2909        } = event
2910        else {
2911            return None;
2912        };
2913        if defined_name != name
2914            || conditionals.is_empty()
2915            || !conditionals
2916                .iter()
2917                .all(|condition| reference_conditions.contains(condition))
2918            || !matches!(
2919                definition.definition,
2920                MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2921            )
2922        {
2923            return None;
2924        }
2925        Some((definition.source.clone(), definition.declaration_byte))
2926    }
2927
2928    /// Return the type argument at a C function-like macro invocation.
2929    ///
2930    /// Exact local macro definitions identify type formals from their parsed
2931    /// replacement. An unavailable definition is deliberately not inferred:
2932    /// C has no AST distinction between an external macro call and an
2933    /// ordinary call to an unindexed function.
2934    pub fn function_macro_type_argument<'tree>(
2935        &self,
2936        file: &ProjectFile,
2937        node: Node<'tree>,
2938        source: &str,
2939    ) -> Option<Node<'tree>> {
2940        if !is_c_source_file(file) {
2941            return None;
2942        }
2943        let (call, argument_index, argument) = c_function_macro_argument(node)?;
2944        let function = call.child_by_field_name("function")?;
2945        let function_name = node_text(function, source);
2946        if function_name.is_empty() {
2947            return None;
2948        }
2949        if !self.file_defines_macro_name(file, function_name)
2950            && !self
2951                .visible_identifier_candidates(file, function_name)
2952                .any(|candidate| candidate.is_macro())
2953        {
2954            return None;
2955        }
2956        let environment = self.macro_environment(file, call.start_byte());
2957        let binding = environment.binding(function_name)?;
2958        if !binding.is_exact() {
2959            return None;
2960        }
2961        let (parameters, replacement, variadic) = match &binding.definition {
2962            MacroDefinition::Function {
2963                parameters,
2964                replacement,
2965            } => (parameters, replacement, false),
2966            MacroDefinition::VariadicFunction {
2967                parameters,
2968                replacement,
2969            } => (parameters, replacement, true),
2970            MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
2971        };
2972        let actuals = call
2973            .child_by_field_name("arguments")
2974            .map(argument_children)
2975            .into_iter()
2976            .flatten()
2977            .collect::<Vec<_>>();
2978        let arity_matches = if variadic {
2979            actuals.len() >= parameters.len()
2980        } else {
2981            actuals.len() == parameters.len()
2982        };
2983        let type_parameters = self.macro_type_parameter_indices(
2984            &(binding.source.clone(), binding.declaration_byte),
2985            parameters,
2986            replacement,
2987        )?;
2988        if !arity_matches || !type_parameters.contains(&argument_index) {
2989            return None;
2990        }
2991        let type_node = macro_type_argument_node(argument, source)?;
2992        if self.names_a_macro_at(file, node_text(type_node, source), type_node.start_byte()) {
2993            return None;
2994        }
2995        Some(type_node)
2996    }
2997
2998    fn macro_local_binding_template(
2999        &self,
3000        binding: &MacroBinding,
3001        parameters: &[String],
3002        replacement: &str,
3003    ) -> Option<Arc<MacroLocalBindingTemplate>> {
3004        let key = (binding.source.clone(), binding.declaration_byte);
3005        if let Some(template) = self
3006            .macro_local_binding_templates
3007            .lock()
3008            .expect("C++ macro local-binding cache poisoned")
3009            .get(&key)
3010        {
3011            return template.clone();
3012        }
3013        let template = (|| {
3014            let body = self.parsed_macro_replacement_body(&key, parameters, replacement)?;
3015            let sentinel = body.source.as_str();
3016            let statements = body.statements()?;
3017            if statements.named_child_count() != 1 {
3018                return None;
3019            }
3020            let declaration = statements.named_child(0)?;
3021            if declaration.kind() != "declaration" {
3022                return None;
3023            }
3024            let type_node = declaration
3025                .child_by_field_name("type")
3026                .or_else(|| first_type_child(declaration))?;
3027            let declarator = declaration.child_by_field_name("declarator").or_else(|| {
3028                let mut cursor = declaration.walk();
3029                declaration.named_children(&mut cursor).find_map(|child| {
3030                    if child.kind() == "init_declarator" {
3031                        child.child_by_field_name("declarator")
3032                    } else {
3033                        is_declarator_node(child).then_some(child)
3034                    }
3035                })
3036            })?;
3037            let name = extract_variable_name(declarator, sentinel)?;
3038            let pointer_depth =
3039                declared_name_binding(declaration, type_node, &name, sentinel)?.pointer_depth;
3040            let type_text = node_text(type_node, sentinel).trim();
3041            let declared_type = parameters
3042                .iter()
3043                .position(|parameter| parameter == type_text)
3044                .map(MacroLocalBindingTypeTemplate::Parameter)
3045                .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
3046            Some(Arc::new(MacroLocalBindingTemplate {
3047                name,
3048                declared_type,
3049                pointer_depth,
3050            }))
3051        })();
3052        self.macro_local_binding_templates
3053            .lock()
3054            .expect("C++ macro local-binding cache poisoned")
3055            .insert(key, template.clone());
3056        template
3057    }
3058
3059    /// The parsed replacement body of the function-like macro `definition`
3060    /// defines, or `None` when the replacement cannot be recovered exactly.
3061    ///
3062    /// `definition` is the defining `preproc_function_def` node in `file`, so
3063    /// the result describes that definition rather than whichever same-named
3064    /// macro a later reference resolves to.
3065    pub fn function_macro_replacement_body(
3066        &self,
3067        file: &ProjectFile,
3068        definition: Node<'_>,
3069        source: &str,
3070    ) -> Option<Arc<ParsedReplacementBody>> {
3071        debug_assert_eq!(definition.kind(), "preproc_function_def");
3072        let (parameters, replacement) = match Self::decode_macro_definition(definition, source) {
3073            MacroDefinition::Function {
3074                parameters,
3075                replacement,
3076            }
3077            | MacroDefinition::VariadicFunction {
3078                parameters,
3079                replacement,
3080            } => (parameters, replacement),
3081            MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
3082        };
3083        self.parsed_macro_replacement_body(
3084            &(file.clone(), definition.start_byte()),
3085            &parameters,
3086            &replacement,
3087        )
3088    }
3089
3090    /// Parse one function-like macro replacement inside the shared sentinel.
3091    ///
3092    /// The parse fails closed, and the failure is cached, whenever the
3093    /// sentinel tree carries an error or the replacement uses preprocessor
3094    /// syntax that has no C++ meaning. Token pasting and stringizing produce
3095    /// `ERROR` nodes; `__VA_ARGS__` parses as an ordinary identifier and is
3096    /// therefore rejected from the parsed tree instead of the source text.
3097    fn parsed_macro_replacement_body(
3098        &self,
3099        key: &(ProjectFile, usize),
3100        parameters: &[String],
3101        replacement: &str,
3102    ) -> Option<Arc<ParsedReplacementBody>> {
3103        if let Some(body) = self
3104            .macro_replacement_bodies
3105            .lock()
3106            .expect("C++ macro replacement body cache poisoned")
3107            .get(key)
3108        {
3109            return body.clone();
3110        }
3111        let body = (|| {
3112            if replacement.trim().is_empty() {
3113                return None;
3114            }
3115            let (source, tree, original_offsets) =
3116                Self::parse_macro_replacement_body(replacement, parameters)?;
3117            let body = ParsedReplacementBody {
3118                source,
3119                tree,
3120                body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3121                parameters: parameters.to_vec(),
3122                original_offsets,
3123            };
3124            body.statements()?;
3125            if body.expands_variadic_arguments() {
3126                return None;
3127            }
3128            Some(Arc::new(body))
3129        })();
3130        self.macro_replacement_bodies
3131            .lock()
3132            .expect("C++ macro replacement body cache poisoned")
3133            .insert(key.clone(), body.clone());
3134        body
3135    }
3136
3137    /// Parse a replacement while preserving a map from the parse buffer back
3138    /// to the original logical-line bytes. A block formal can occur where C
3139    /// requires a statement terminator (`if (condition) block`); the caller's
3140    /// actual block supplies that terminator only after macro substitution.
3141    /// Tree-sitter exposes the formal as an identifier inside recovery, so a
3142    /// semicolon is inserted at that AST-derived boundary for the fallback
3143    /// parse. No identifier text is scanned to find the insertion point.
3144    fn parse_macro_replacement_body(
3145        replacement: &str,
3146        parameters: &[String],
3147    ) -> Option<(String, Tree, Box<[usize]>)> {
3148        let normalized = normalize_macro_continuations(replacement);
3149        let parse = |replacement: &str| {
3150            let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
3151            let mut parser = Parser::new();
3152            parser
3153                .set_language(&tree_sitter_cpp::LANGUAGE.into())
3154                .ok()?;
3155            let tree = parser.parse(&source, None)?;
3156            Some((source, tree))
3157        };
3158        let (source, tree) = parse(&normalized)?;
3159        if !tree.root_node().has_error() {
3160            let mut original_offsets = (0..=normalized.len()).collect::<Vec<_>>();
3161            Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
3162            return Some((source, tree, original_offsets.into_boxed_slice()));
3163        }
3164
3165        let body_offset = MACRO_BODY_SENTINEL_PREFIX.len();
3166        let mut insertion_points = Vec::new();
3167        let mut stack = vec![tree.root_node()];
3168        while let Some(node) = stack.pop() {
3169            if matches!(
3170                node.kind(),
3171                "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
3172            ) && parameters
3173                .iter()
3174                .any(|parameter| parameter.as_str() == node_text(node, &source))
3175                && Self::macro_formal_needs_statement_separator(node)
3176            {
3177                let point = node.end_byte().saturating_sub(body_offset);
3178                if point <= normalized.len() && !insertion_points.contains(&point) {
3179                    insertion_points.push(point);
3180                }
3181            }
3182            push_named_children_reversed(node, &mut stack);
3183        }
3184        if insertion_points.is_empty() {
3185            return None;
3186        }
3187        insertion_points.sort_unstable();
3188        let mut recovered = Vec::with_capacity(normalized.len() + insertion_points.len());
3189        let mut original_offsets =
3190            Vec::with_capacity(normalized.len() + insertion_points.len() + 1);
3191        let mut next_insertion = 0;
3192        for (index, byte) in normalized.bytes().enumerate() {
3193            recovered.push(byte);
3194            original_offsets.push(index);
3195            while insertion_points.get(next_insertion).copied() == Some(index + 1) {
3196                recovered.push(b';');
3197                original_offsets.push(index + 1);
3198                next_insertion += 1;
3199            }
3200        }
3201        original_offsets.push(normalized.len());
3202        Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
3203        let recovered = String::from_utf8(recovered).expect("source text remains UTF-8");
3204        let (source, tree) = parse(&recovered)?;
3205        if tree.root_node().has_error() {
3206            return None;
3207        }
3208        Some((source, tree, original_offsets.into_boxed_slice()))
3209    }
3210
3211    /// Extend a replacement-origin map over the `; }` suffix appended by the
3212    /// sentinel parser. Nodes such as a declaration whose source omits its
3213    /// terminator include that synthetic semicolon; every suffix boundary
3214    /// still maps to the replacement's real end.
3215    fn append_sentinel_offsets(offsets: &mut Vec<usize>, replacement_end: usize) {
3216        offsets.extend([replacement_end; 3]);
3217    }
3218
3219    /// A formal is statement-shaped when the recovered tree places it directly
3220    /// at a statement boundary. This is the grammar recovery produced for
3221    /// replacement forms such as `if (condition) block`; an identifier inside
3222    /// an expression or call argument must not receive an artificial `;`.
3223    fn macro_formal_needs_statement_separator(node: Node<'_>) -> bool {
3224        let mut current = node;
3225        let mut crossed_recovery = false;
3226        while let Some(parent) = current.parent() {
3227            if parent.is_error() {
3228                crossed_recovery = true;
3229                current = parent;
3230                continue;
3231            }
3232            if matches!(
3233                parent.kind(),
3234                "call_expression"
3235                    | "argument_list"
3236                    | "field_expression"
3237                    | "binary_expression"
3238                    | "unary_expression"
3239                    | "assignment_expression"
3240                    | "conditional_expression"
3241                    | "parenthesized_expression"
3242                    | "subscript_expression"
3243            ) {
3244                return false;
3245            }
3246            if parent.kind() == "expression_statement" {
3247                return parent.named_child_count() == 1;
3248            }
3249            if parent.kind() == "compound_statement" && current == node {
3250                return true;
3251            }
3252            if matches!(
3253                parent.kind(),
3254                "compound_statement" | "if_statement" | "while_statement" | "do_statement"
3255            ) {
3256                return crossed_recovery
3257                    || parent.child_by_field_name("consequence") == Some(current);
3258            }
3259            if matches!(parent.kind(), "declaration" | "init_declarator") {
3260                return false;
3261            }
3262            current = parent;
3263        }
3264        false
3265    }
3266
3267    /// Identify fixed macro formals that retain an unambiguous type role even
3268    /// when an unrelated part of the replacement needs parser recovery.
3269    ///
3270    /// Variadic production macros commonly contain token pasting elsewhere in
3271    /// the body. The strict shared replacement parser must keep rejecting that
3272    /// body for expansion modeling, but a type declaration such as `TY col`
3273    /// remains usable when the node itself retains a structured type role.
3274    fn macro_type_parameter_indices(
3275        &self,
3276        key: &(ProjectFile, usize),
3277        parameters: &[String],
3278        replacement: &str,
3279    ) -> Option<Arc<[usize]>> {
3280        if let Some(indices) = self
3281            .macro_type_parameters
3282            .lock()
3283            .expect("C++ macro type-parameter cache poisoned")
3284            .get(key)
3285        {
3286            return indices.clone();
3287        }
3288        #[cfg(any(test, feature = "test-support"))]
3289        self.macro_replacement_parse_count
3290            .fetch_add(1, Ordering::Relaxed);
3291        let indices = (|| {
3292            if replacement.trim().is_empty() {
3293                return None;
3294            }
3295            // Keep this parser permissive. Type-role extraction predates the
3296            // replacement-body model and deliberately survives unrelated
3297            // token-pasting or recovery elsewhere in a production macro.
3298            let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
3299            let mut parser = Parser::new();
3300            parser
3301                .set_language(&tree_sitter_cpp::LANGUAGE.into())
3302                .ok()?;
3303            let tree = parser.parse(&source, None)?;
3304            let mut original_offsets = (0..=replacement.len()).collect::<Vec<_>>();
3305            Self::append_sentinel_offsets(&mut original_offsets, replacement.len());
3306            let body = ParsedReplacementBody {
3307                source,
3308                tree,
3309                body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3310                parameters: parameters.to_vec(),
3311                original_offsets: original_offsets.into_boxed_slice(),
3312            };
3313            macro_replacement_type_parameters(&body, parameters).map(Arc::from)
3314        })();
3315        self.macro_type_parameters
3316            .lock()
3317            .expect("C++ macro type-parameter cache poisoned")
3318            .insert(key.clone(), indices.clone());
3319        indices
3320    }
3321
3322    fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
3323        let replacement = if node.kind() == "preproc_function_def" {
3324            function_macro_replacement_span(node, source)
3325                .and_then(|span| source.get(span))
3326                .map(str::to_owned)
3327                .or_else(|| {
3328                    node.child_by_field_name("value")
3329                        .map(|value| node_text(value, source).to_string())
3330                })
3331                .unwrap_or_default()
3332        } else {
3333            node.child_by_field_name("value")
3334                .map(|value| node_text(value, source).to_string())
3335                .unwrap_or_default()
3336        };
3337        if node.kind() == "preproc_def" {
3338            return MacroDefinition::Object { replacement };
3339        }
3340        let Some(parameters) = node.child_by_field_name("parameters") else {
3341            return MacroDefinition::Unsupported;
3342        };
3343        let variadic = (0..parameters.child_count()).any(|index| {
3344            parameters
3345                .child(index)
3346                .is_some_and(|child| child.kind() == "...")
3347        });
3348        let parameters = (0..parameters.named_child_count())
3349            .filter_map(|index| parameters.named_child(index))
3350            .map(|parameter| node_text(parameter, source).to_string())
3351            .collect::<Vec<_>>();
3352        if variadic {
3353            MacroDefinition::VariadicFunction {
3354                parameters,
3355                replacement,
3356            }
3357        } else {
3358            MacroDefinition::Function {
3359                parameters,
3360                replacement,
3361            }
3362        }
3363    }
3364
3365    pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
3366        clone_or_default_file_cell(
3367            &self.macro_event_cells,
3368            file,
3369            "C++ macro event cache poisoned",
3370        )
3371    }
3372
3373    fn file_defines_macro_name(&self, file: &ProjectFile, name: &str) -> bool {
3374        if let Some(names) = self
3375            .macro_event_name_sets
3376            .lock()
3377            .expect("C++ macro event-name cache poisoned")
3378            .get(file)
3379            .cloned()
3380        {
3381            return names.contains(name);
3382        }
3383        let cell = self.macro_event_cell(file);
3384        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3385        let names = Arc::new(
3386            events
3387                .iter()
3388                .filter_map(|event| match event {
3389                    MacroEvent::Define { name, .. } => Some(name.clone()),
3390                    MacroEvent::Undef { .. }
3391                    | MacroEvent::Include { .. }
3392                    | MacroEvent::Invalidate { .. } => None,
3393                })
3394                .collect(),
3395        );
3396        self.macro_event_name_sets
3397            .lock()
3398            .expect("C++ macro event-name cache poisoned")
3399            .insert(file.clone(), Arc::clone(&names));
3400        names.contains(name)
3401    }
3402
3403    fn macro_environment_checkpoint_cell(
3404        &self,
3405        file: &ProjectFile,
3406    ) -> MacroEnvironmentCheckpointCell {
3407        clone_or_default_file_cell(
3408            &self.macro_environment_checkpoints,
3409            file,
3410            "C++ macro environment checkpoint cache poisoned",
3411        )
3412    }
3413
3414    /// The environment of `file`'s macro events applied up to `before_byte`.
3415    pub fn macro_environment(
3416        &self,
3417        file: &ProjectFile,
3418        before_byte: usize,
3419    ) -> Arc<MacroEnvironment> {
3420        #[cfg(any(test, feature = "test-support"))]
3421        self.macro_environment_request_count
3422            .fetch_add(1, Ordering::Relaxed);
3423        let cell = self.macro_event_cell(file);
3424        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3425        let frontier = events.partition_point(|event| event.byte() < before_byte);
3426        let checkpoint_cell = self.macro_environment_checkpoint_cell(file);
3427        let checkpoints =
3428            checkpoint_cell.get_or_init(|| self.build_macro_environment_checkpoints(file, events));
3429        let checkpoint = checkpoints.at_or_before(frontier);
3430        if checkpoint.frontier == frontier {
3431            return Arc::clone(&checkpoint.environment);
3432        }
3433        #[cfg(any(test, feature = "test-support"))]
3434        self.macro_environment_copy_count
3435            .fetch_add(1, Ordering::Relaxed);
3436        let mut environment = checkpoint.environment.as_ref().clone();
3437        let mut include_stack = HashSet::from_iter([file.clone()]);
3438        for event in &events[checkpoint.frontier..frontier] {
3439            self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3440        }
3441        Arc::new(environment)
3442    }
3443
3444    /// Apply `file`'s events once, keeping the environment at the prefixes
3445    /// [`MacroEnvironmentCheckpoints`] describes.
3446    fn build_macro_environment_checkpoints(
3447        &self,
3448        file: &ProjectFile,
3449        events: &[MacroEvent],
3450    ) -> MacroEnvironmentCheckpoints {
3451        #[cfg(any(test, feature = "test-support"))]
3452        self.macro_environment_checkpoint_build_count
3453            .fetch_add(1, Ordering::Relaxed);
3454        // The TU's build-proven defines hold from the first byte (#2011): they
3455        // are facts of the whole compile, so they seed the frontier-zero
3456        // checkpoint. A later explicit #undef event still overrides them
3457        // through `known_undefined_names`.
3458        let mut environment = MacroEnvironment {
3459            build_proven_defines: self
3460                .compile_proven_guards(file)
3461                .iter()
3462                .filter_map(|guard| match guard {
3463                    PreprocessorGuard::Defined(name) => Some(name.clone()),
3464                    _ => None,
3465                })
3466                .collect(),
3467            ..MacroEnvironment::default()
3468        };
3469        let mut checkpoints = vec![MacroEnvironmentCheckpoint {
3470            frontier: 0,
3471            environment: Arc::new(environment.clone()),
3472        }];
3473        // Keep roughly one fixed stride's worth of checkpoints, while taking
3474        // a checkpoint at every event for event sets no larger than the fixed
3475        // stride. Generated C tables commonly have thousands of uses between
3476        // one #define and a trailing #undef; with a fixed stride those
3477        // identical frontier requests would all copy and replay the same
3478        // prefix.
3479        let checkpoint_stride = events
3480            .len()
3481            .div_ceil(MACRO_ENVIRONMENT_CHECKPOINT_STRIDE)
3482            .clamp(1, MACRO_ENVIRONMENT_CHECKPOINT_STRIDE);
3483        let mut include_stack = HashSet::from_iter([file.clone()]);
3484        for (index, event) in events.iter().enumerate() {
3485            self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3486            let frontier = index + 1;
3487            if frontier % checkpoint_stride == 0 || matches!(event, MacroEvent::Include { .. }) {
3488                checkpoints.push(MacroEnvironmentCheckpoint {
3489                    frontier,
3490                    environment: Arc::new(environment.clone()),
3491                });
3492            }
3493        }
3494        MacroEnvironmentCheckpoints { checkpoints }
3495    }
3496
3497    /// Whether `name` is bound as a macro at `before_byte` in `file`,
3498    /// including a binding this environment cannot pin to one replacement
3499    /// (a conditional `#define`, or a function-like macro).
3500    ///
3501    /// [`Self::object_macro_replacement_at`] collapses every such binding to
3502    /// `None`, which is indistinguishable from "not a macro at all". A caller
3503    /// that must not read a macro token as an ordinary type name needs the two
3504    /// apart: an unexpandable macro is an unknown, a plain identifier is not.
3505    pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
3506        self.macro_environment(file, before_byte)
3507            .binding(name)
3508            .is_some()
3509    }
3510
3511    pub fn macro_name_may_be_bound_at(
3512        &self,
3513        file: &ProjectFile,
3514        name: &str,
3515        before_byte: usize,
3516    ) -> bool {
3517        self.macro_environment(file, before_byte).may_bind(name)
3518    }
3519
3520    /// Whether the active macro binding at this reference is the requested
3521    /// indexed definition. Name equality alone is not enough because two
3522    /// headers can define the same macro for different translation units.
3523    pub fn macro_binding_matches_target_at(
3524        &self,
3525        analyzer: &CppGraphSource<'_>,
3526        file: &ProjectFile,
3527        name: &str,
3528        before_byte: usize,
3529        target: &CodeUnit,
3530    ) -> bool {
3531        let ranges = analyzer.ranges(target);
3532        let declaration_bytes = self.macro_declaration_bytes(target, &ranges);
3533        self.macro_binding_matches_target_declaration_at(
3534            file,
3535            name,
3536            before_byte,
3537            target.source(),
3538            &declaration_bytes,
3539        )
3540    }
3541
3542    /// Resolve declaration ranges once for inverse scans that test many call
3543    /// sites against one macro target. Walking a large generated syntax tree
3544    /// back to the same `#define` for every call site is otherwise quadratic
3545    /// in the number of top-level declarations.
3546    pub(crate) fn macro_declaration_bytes(
3547        &self,
3548        target: &CodeUnit,
3549        ranges: &[Range],
3550    ) -> Vec<usize> {
3551        let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
3552            return Vec::new();
3553        };
3554        ranges
3555            .iter()
3556            .filter_map(|range| {
3557                let mut node = node_for_exact_range(prepared.tree().root_node(), range)?;
3558                while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
3559                    node = node.parent()?;
3560                }
3561                Some(node.start_byte())
3562            })
3563            .collect()
3564    }
3565
3566    pub(crate) fn macro_binding_matches_target_declaration_at(
3567        &self,
3568        file: &ProjectFile,
3569        name: &str,
3570        before_byte: usize,
3571        target_source: &ProjectFile,
3572        target_declaration_bytes: &[usize],
3573    ) -> bool {
3574        let environment = self.macro_environment(file, before_byte);
3575        let Some(binding) = environment.binding(name) else {
3576            return false;
3577        };
3578        if binding.definition == MacroDefinition::Unsupported {
3579            return false;
3580        }
3581        // A normal header guard makes the replacement text conditional, but
3582        // it does not erase the definition site's source and byte identity.
3583        // Keep that identity even when expansion details are not exact.
3584        if binding.source != *target_source {
3585            return false;
3586        }
3587        target_declaration_bytes.contains(&binding.declaration_byte)
3588    }
3589
3590    /// Resolve an ordinary expression-position macro token at its exact byte.
3591    ///
3592    /// Calls and preprocessor-condition tokens have separate resolution
3593    /// surfaces. Declaration names, macro parameters, and labels are not
3594    /// references. Keeping that role policy here makes forward and both
3595    /// inverse graph builders consume the same activation verdict (#2093).
3596    pub fn resolve_ordinary_macro_reference(
3597        &self,
3598        analyzer: &CppGraphSource<'_>,
3599        file: &ProjectFile,
3600        node: Node<'_>,
3601        source: &str,
3602    ) -> OrdinaryMacroReferenceResolution {
3603        if !is_ordinary_macro_reference_node(node) {
3604            return OrdinaryMacroReferenceResolution::Missing;
3605        }
3606        let name = node_text(node, source);
3607        if name.is_empty() {
3608            return OrdinaryMacroReferenceResolution::Missing;
3609        }
3610        let visible = self
3611            .visible_identifier_candidates(file, name)
3612            .filter(|candidate| candidate.is_macro())
3613            .cloned()
3614            .collect::<Vec<_>>();
3615        let mut exact = Vec::new();
3616        for candidate in &visible {
3617            if self.macro_binding_matches_target_at(
3618                analyzer,
3619                file,
3620                name,
3621                node.start_byte(),
3622                candidate,
3623            ) && !exact
3624                .iter()
3625                .any(|existing| same_visible_symbol(existing, candidate))
3626            {
3627                exact.push(candidate.clone());
3628            }
3629        }
3630        match exact.len() {
3631            1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
3632            2.. => OrdinaryMacroReferenceResolution::Ambiguous,
3633            0 if !visible.is_empty()
3634                && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
3635            {
3636                OrdinaryMacroReferenceResolution::Ambiguous
3637            }
3638            0 => OrdinaryMacroReferenceResolution::Missing,
3639        }
3640    }
3641
3642    /// Collect reference-capable C tokens beneath tree-sitter recovery nodes.
3643    ///
3644    /// The ordinary census deliberately skips every `ERROR` subtree. This
3645    /// separate, precision-only frontier admits only roles that retain enough
3646    /// structure for the C usage graph to interpret independently (#2089).
3647    /// Macro evidence comes from this visibility index at the exact byte; no
3648    /// source-text parsing or terminal-name fallback is used.
3649    pub fn recovered_c_reference_ranges(
3650        &self,
3651        file: &ProjectFile,
3652        root: Node<'_>,
3653        source: &str,
3654        limit: usize,
3655    ) -> RecoveredCReferenceRanges {
3656        if !is_c_source_file(file) {
3657            return RecoveredCReferenceRanges::Complete(Vec::new());
3658        }
3659        let mut ranges = Vec::new();
3660        let mut seen = HashSet::default();
3661        let mut stack = vec![(root, root.is_error())];
3662        while let Some((node, inside_error)) = stack.pop() {
3663            let inside_error = inside_error || node.is_error();
3664            if node.kind() == "preproc_arg" {
3665                // Tree-sitter keeps an object-like replacement opaque. The
3666                // inverse extractor uses the same parsed replacement helper;
3667                // retain its exact macro/type leaves for precision membership
3668                // even when the preprocessor node is outside ERROR recovery.
3669                let macro_value_kind = node.parent().and_then(|parent| {
3670                    (parent.child_by_field_name("value") == Some(node)).then_some(parent.kind())
3671                });
3672                if matches!(
3673                    macro_value_kind,
3674                    Some("preproc_def" | "preproc_function_def")
3675                ) {
3676                    let name = node_text(node, source);
3677                    if !name.is_empty()
3678                        && self.macro_name_may_be_bound_at(file, name, node.start_byte())
3679                        && !push_recovered_c_range(
3680                            &mut ranges,
3681                            &mut seen,
3682                            node.start_byte(),
3683                            node.end_byte(),
3684                            node,
3685                            limit,
3686                        )
3687                    {
3688                        return RecoveredCReferenceRanges::LimitExceeded;
3689                    }
3690                }
3691                if macro_value_kind == Some("preproc_def") {
3692                    for reference in object_macro_replacement_type_references(node, source) {
3693                        for range in reference.component_ranges {
3694                            let visible = self
3695                                .visible_identifier_candidates(file, &source[range.clone()])
3696                                .any(|candidate| candidate.is_class() || candidate.is_module());
3697                            if visible
3698                                && !push_recovered_c_range(
3699                                    &mut ranges,
3700                                    &mut seen,
3701                                    range.start,
3702                                    range.end,
3703                                    node,
3704                                    limit,
3705                                )
3706                            {
3707                                return RecoveredCReferenceRanges::LimitExceeded;
3708                            }
3709                        }
3710                    }
3711                }
3712            }
3713            if inside_error
3714                && recovered_c_reference_node(self, file, node, source)
3715                && !push_recovered_c_range(
3716                    &mut ranges,
3717                    &mut seen,
3718                    node.start_byte(),
3719                    node.end_byte(),
3720                    node,
3721                    limit,
3722                )
3723            {
3724                return RecoveredCReferenceRanges::LimitExceeded;
3725            }
3726            let mut cursor = node.walk();
3727            for child in node.named_children(&mut cursor) {
3728                stack.push((child, inside_error));
3729            }
3730        }
3731        ranges.sort_unstable();
3732        RecoveredCReferenceRanges::Complete(ranges)
3733    }
3734
3735    /// Whether this target is an indexed macro visible from this file.
3736    ///
3737    /// An unresolved conditional can make more than one same-name macro a
3738    /// possible active binding. Each possible target can keep the site as an
3739    /// unproven hit. A macro in an unrelated translation unit stays excluded.
3740    pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
3741        self.visible_identifier_candidates(file, target.identifier())
3742            .filter(|candidate| candidate.is_macro())
3743            .any(|candidate| {
3744                candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
3745            })
3746    }
3747
3748    pub fn object_macro_replacement_at(
3749        &self,
3750        file: &ProjectFile,
3751        name: &str,
3752        before_byte: usize,
3753    ) -> Option<String> {
3754        let environment = self.macro_environment(file, before_byte);
3755        let binding = environment.binding(name)?;
3756        if !binding.exact {
3757            return None;
3758        }
3759        match &binding.definition {
3760            MacroDefinition::Object { replacement } => Some(replacement.clone()),
3761            MacroDefinition::Function { .. }
3762            | MacroDefinition::VariadicFunction { .. }
3763            | MacroDefinition::Unsupported => None,
3764        }
3765    }
3766
3767    fn apply_macro_events(
3768        &self,
3769        file: &ProjectFile,
3770        before_byte: Option<usize>,
3771        environment: &mut MacroEnvironment,
3772        include_stack: &mut HashSet<ProjectFile>,
3773    ) {
3774        if !include_stack.insert(file.clone()) {
3775            return;
3776        }
3777        if self.cpp.prepared_syntax(self.token, file).is_none() {
3778            environment.mark_unknown_names(file, before_byte.unwrap_or_default());
3779            include_stack.remove(file);
3780            return;
3781        }
3782        match self.macro_include_protection(file) {
3783            MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
3784                Some(binding) if binding.is_exact() => {
3785                    include_stack.remove(file);
3786                    return;
3787                }
3788                Some(_) | None if environment.unknown_names => {
3789                    let mut ambiguous_seen = HashSet::default();
3790                    self.mark_macro_events_ambiguous(
3791                        file,
3792                        environment,
3793                        &mut ambiguous_seen,
3794                        file,
3795                        before_byte.unwrap_or_default(),
3796                    );
3797                    include_stack.remove(file);
3798                    return;
3799                }
3800                Some(_) => {
3801                    let mut ambiguous_seen = HashSet::default();
3802                    self.mark_macro_events_ambiguous(
3803                        file,
3804                        environment,
3805                        &mut ambiguous_seen,
3806                        file,
3807                        before_byte.unwrap_or_default(),
3808                    );
3809                    include_stack.remove(file);
3810                    return;
3811                }
3812                None => {}
3813            },
3814            MacroIncludeProtection::PragmaOnce => {
3815                if !environment.applied_pragma_once_files.insert(file.clone()) {
3816                    include_stack.remove(file);
3817                    return;
3818                }
3819                if environment.maybe_applied_pragma_once_files.remove(file) {
3820                    // A prior conditional include may already have consumed the pragma-once
3821                    // header. This unconditional include guarantees it is consumed now, but
3822                    // cannot prove whether its events occur before or after intervening local
3823                    // macro changes, so preserve the union as ambiguous.
3824                    let mut ambiguous_seen = HashSet::default();
3825                    environment.applied_pragma_once_files.remove(file);
3826                    self.mark_macro_events_ambiguous(
3827                        file,
3828                        environment,
3829                        &mut ambiguous_seen,
3830                        file,
3831                        before_byte.unwrap_or_default(),
3832                    );
3833                    environment.maybe_applied_pragma_once_files.remove(file);
3834                    environment.applied_pragma_once_files.insert(file.clone());
3835                    include_stack.remove(file);
3836                    return;
3837                }
3838            }
3839            MacroIncludeProtection::None => {}
3840        }
3841        let cell = self.macro_event_cell(file);
3842        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3843        for event in events {
3844            if before_byte.is_some_and(|limit| event.byte() >= limit) {
3845                break;
3846            }
3847            self.apply_macro_event(file, event, environment, include_stack);
3848        }
3849        include_stack.remove(file);
3850    }
3851
3852    fn apply_macro_event(
3853        &self,
3854        file: &ProjectFile,
3855        event: &MacroEvent,
3856        environment: &mut MacroEnvironment,
3857        include_stack: &mut HashSet<ProjectFile>,
3858    ) {
3859        #[cfg(any(test, feature = "test-support"))]
3860        self.macro_event_application_count
3861            .fetch_add(1, Ordering::Relaxed);
3862        match event {
3863            MacroEvent::Define {
3864                name,
3865                binding,
3866                conditionals,
3867                byte,
3868            } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3869                Some(true) => environment.insert(name.clone(), binding.clone()),
3870                Some(false) => {}
3871                None => Self::merge_conditional_macro_definition(
3872                    environment,
3873                    name,
3874                    binding,
3875                    file,
3876                    *byte,
3877                ),
3878            },
3879            MacroEvent::Undef {
3880                name,
3881                conditionals,
3882                byte,
3883            } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3884                Some(true) => environment.remove(name),
3885                Some(false) => {}
3886                None => {
3887                    if environment.binding(name).is_some() {
3888                        environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
3889                    }
3890                }
3891            },
3892            MacroEvent::Include {
3893                targets,
3894                conditionals,
3895                byte,
3896            } => {
3897                let condition =
3898                    self.macro_event_condition_value(file, *byte, environment, conditionals);
3899                if condition == Some(false) {
3900                    return;
3901                }
3902                if targets.is_empty() {
3903                    environment.mark_unknown_names(file, *byte);
3904                    return;
3905                }
3906                if condition.is_none() || targets.len() > 1 {
3907                    let mut ambiguous_seen = HashSet::default();
3908                    for target in targets {
3909                        self.mark_macro_events_ambiguous(
3910                            target,
3911                            environment,
3912                            &mut ambiguous_seen,
3913                            file,
3914                            *byte,
3915                        );
3916                    }
3917                } else if let Some(target) = targets.first() {
3918                    self.apply_macro_events(target, None, environment, include_stack);
3919                }
3920            }
3921            MacroEvent::Invalidate { byte } => {
3922                for binding in environment.bindings.values_mut() {
3923                    *binding = MacroBinding::uncertain_from(binding, file, *byte);
3924                }
3925            }
3926        }
3927    }
3928
3929    /// Evaluate the structured conditional path that owns one macro event.
3930    ///
3931    /// `Some(true)` and `Some(false)` are proofs from exact macro bindings at
3932    /// this source byte. `None` preserves the old conditional merge when a
3933    /// build/configuration input or an unsupported expression is involved.
3934    ///
3935    /// `conditionals` is the event's own [`OwningPreprocessorConditionals`]:
3936    /// which ancestors structurally own the event was decided when the events
3937    /// were collected, so all this walk does is find those nodes again and ask
3938    /// the environment what their conditions are worth here.
3939    fn macro_event_condition_value(
3940        &self,
3941        file: &ProjectFile,
3942        event_byte: usize,
3943        environment: &MacroEnvironment,
3944        conditionals: &OwningPreprocessorConditionals,
3945    ) -> Option<bool> {
3946        if conditionals.is_empty() {
3947            return Some(true);
3948        }
3949        let prepared = self.cpp.prepared_syntax(self.token, file)?;
3950        let source = prepared.source();
3951        let root = prepared.tree().root_node();
3952        let descendant = root.descendant_for_byte_range(
3953            event_byte,
3954            event_byte.saturating_add(1).min(source.len()),
3955        )?;
3956        let mut unknown = false;
3957        let mut current = descendant.parent();
3958        while let Some(conditional) = current {
3959            if matches!(
3960                conditional.kind(),
3961                "preproc_if" | "preproc_ifdef" | "preproc_elif"
3962            ) && conditionals.contains(&conditional.start_byte())
3963            {
3964                let mut value = match conditional.kind() {
3965                    "preproc_ifdef" => {
3966                        let name = conditional.child_by_field_name("name")?;
3967                        let defined =
3968                            self.macro_name_defined_value(environment, node_text(name, source));
3969                        match conditional.child(0)?.kind() {
3970                            "#ifdef" => defined,
3971                            "#ifndef" => defined.map(|defined| !defined),
3972                            _ => None,
3973                        }
3974                    }
3975                    "preproc_if" | "preproc_elif" => conditional
3976                        .child_by_field_name("condition")
3977                        .and_then(|condition| {
3978                            self.preprocessor_integer_value(
3979                                condition,
3980                                source,
3981                                environment,
3982                                &mut Vec::new(),
3983                                0,
3984                            )
3985                        })
3986                        .map(|value| value != 0),
3987                    _ => unreachable!(),
3988                };
3989                if conditional
3990                    .child_by_field_name("alternative")
3991                    .is_some_and(|alternative| {
3992                        alternative.start_byte() <= descendant.start_byte()
3993                            && descendant.end_byte() <= alternative.end_byte()
3994                    })
3995                {
3996                    value = value.map(|value| !value);
3997                }
3998                match value {
3999                    Some(true) => {}
4000                    Some(false) => return Some(false),
4001                    None => unknown = true,
4002                }
4003            }
4004            current = conditional.parent();
4005        }
4006        (!unknown).then_some(true)
4007    }
4008
4009    fn macro_name_defined_value(&self, environment: &MacroEnvironment, name: &str) -> Option<bool> {
4010        if environment.known_undefined_names.contains(name) {
4011            return Some(false);
4012        }
4013        if let Some(binding) = environment.binding(name) {
4014            return binding.is_exact().then_some(true);
4015        }
4016        environment
4017            .build_proven_defines
4018            .contains(name)
4019            .then_some(true)
4020    }
4021
4022    fn preprocessor_integer_value(
4023        &self,
4024        expression: Node<'_>,
4025        source: &str,
4026        environment: &MacroEnvironment,
4027        expansion_stack: &mut Vec<(ProjectFile, usize)>,
4028        depth: usize,
4029    ) -> Option<i128> {
4030        // Macro replacement graphs can cycle. This explicit bound makes the
4031        // otherwise recursive AST evaluation stack-safe for hostile input.
4032        if depth >= 64 {
4033            return None;
4034        }
4035        match expression.kind() {
4036            "number_literal" => parse_cpp_integer_literal(node_text(expression, source)),
4037            "identifier" | "type_identifier" => {
4038                let binding = environment.binding(node_text(expression, source))?;
4039                if !binding.is_exact() {
4040                    return None;
4041                }
4042                let MacroDefinition::Object { replacement } = &binding.definition else {
4043                    return None;
4044                };
4045                let identity = (binding.source.clone(), binding.declaration_byte);
4046                if expansion_stack.contains(&identity) {
4047                    return None;
4048                }
4049                expansion_stack.push(identity);
4050                let parsed = self.parsed_macro_replacement(binding, replacement);
4051                let value = match parsed.as_ref() {
4052                    ParsedMacroReplacement::Parsed {
4053                        source: replacement_source,
4054                        tree,
4055                    } => first_descendant_of_kind(tree.root_node(), "call_expression")
4056                        .and_then(|call| call.child_by_field_name("arguments"))
4057                        .and_then(|arguments| argument_children(arguments).next())
4058                        .and_then(|argument| {
4059                            self.preprocessor_integer_value(
4060                                argument,
4061                                replacement_source,
4062                                environment,
4063                                expansion_stack,
4064                                depth + 1,
4065                            )
4066                        }),
4067                    ParsedMacroReplacement::Unsupported => None,
4068                };
4069                expansion_stack.pop();
4070                value
4071            }
4072            "preproc_defined" => {
4073                let mut cursor = expression.walk();
4074                let name = expression
4075                    .named_children(&mut cursor)
4076                    .find(|child| child.kind() == "identifier")?;
4077                self.macro_name_defined_value(environment, node_text(name, source))
4078                    .map(i128::from)
4079            }
4080            "parenthesized_expression" => expression.named_child(0).and_then(|child| {
4081                self.preprocessor_integer_value(
4082                    child,
4083                    source,
4084                    environment,
4085                    expansion_stack,
4086                    depth + 1,
4087                )
4088            }),
4089            "unary_expression" => {
4090                let operator = expression.child_by_field_name("operator")?.kind();
4091                let argument = expression.child_by_field_name("argument")?;
4092                let value = self.preprocessor_integer_value(
4093                    argument,
4094                    source,
4095                    environment,
4096                    expansion_stack,
4097                    depth + 1,
4098                )?;
4099                match operator {
4100                    "+" => Some(value),
4101                    "-" => value.checked_neg(),
4102                    "!" => Some(i128::from(value == 0)),
4103                    "~" => Some(!value),
4104                    _ => None,
4105                }
4106            }
4107            "binary_expression" => {
4108                let left = self.preprocessor_integer_value(
4109                    expression.child_by_field_name("left")?,
4110                    source,
4111                    environment,
4112                    expansion_stack,
4113                    depth + 1,
4114                )?;
4115                let right = self.preprocessor_integer_value(
4116                    expression.child_by_field_name("right")?,
4117                    source,
4118                    environment,
4119                    expansion_stack,
4120                    depth + 1,
4121                )?;
4122                match expression.child_by_field_name("operator")?.kind() {
4123                    "+" => left.checked_add(right),
4124                    "-" => left.checked_sub(right),
4125                    "*" => left.checked_mul(right),
4126                    "/" => left.checked_div(right),
4127                    "%" => left.checked_rem(right),
4128                    "<<" => u32::try_from(right)
4129                        .ok()
4130                        .and_then(|shift| left.checked_shl(shift)),
4131                    ">>" => u32::try_from(right)
4132                        .ok()
4133                        .and_then(|shift| left.checked_shr(shift)),
4134                    "<" => Some(i128::from(left < right)),
4135                    "<=" => Some(i128::from(left <= right)),
4136                    ">" => Some(i128::from(left > right)),
4137                    ">=" => Some(i128::from(left >= right)),
4138                    "==" => Some(i128::from(left == right)),
4139                    "!=" => Some(i128::from(left != right)),
4140                    "&" => Some(left & right),
4141                    "|" => Some(left | right),
4142                    "^" => Some(left ^ right),
4143                    "&&" => Some(i128::from(left != 0 && right != 0)),
4144                    "||" => Some(i128::from(left != 0 || right != 0)),
4145                    _ => None,
4146                }
4147            }
4148            _ => None,
4149        }
4150    }
4151
4152    fn mark_macro_events_ambiguous(
4153        &self,
4154        file: &ProjectFile,
4155        environment: &mut MacroEnvironment,
4156        include_stack: &mut HashSet<ProjectFile>,
4157        conditional_file: &ProjectFile,
4158        conditional_byte: usize,
4159    ) {
4160        if !include_stack.insert(file.clone()) {
4161            return;
4162        }
4163        if self.cpp.prepared_syntax(self.token, file).is_none() {
4164            environment.mark_unknown_names(conditional_file, conditional_byte);
4165            return;
4166        }
4167        match self.macro_include_protection(file) {
4168            MacroIncludeProtection::MacroGuard(guard) => {
4169                if environment
4170                    .binding(&guard)
4171                    .is_some_and(MacroBinding::is_exact)
4172                {
4173                    return;
4174                }
4175            }
4176            MacroIncludeProtection::PragmaOnce => {
4177                if environment.applied_pragma_once_files.contains(file) {
4178                    return;
4179                }
4180                environment
4181                    .maybe_applied_pragma_once_files
4182                    .insert(file.clone());
4183            }
4184            MacroIncludeProtection::None => {}
4185        }
4186        let cell = self.macro_event_cell(file);
4187        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
4188        for event in events {
4189            #[cfg(any(test, feature = "test-support"))]
4190            self.macro_event_application_count
4191                .fetch_add(1, Ordering::Relaxed);
4192            match event {
4193                MacroEvent::Define { name, binding, .. } => {
4194                    Self::merge_conditional_macro_definition(
4195                        environment,
4196                        name,
4197                        binding,
4198                        conditional_file,
4199                        conditional_byte,
4200                    );
4201                }
4202                MacroEvent::Undef { name, .. } => {
4203                    if environment.binding(name).is_some() {
4204                        environment.insert(
4205                            name.clone(),
4206                            MacroBinding::ambiguous(conditional_file, conditional_byte),
4207                        );
4208                    } else {
4209                        environment.remove_known_undefined(name);
4210                    }
4211                }
4212                MacroEvent::Include { targets, .. } => {
4213                    if targets.is_empty() {
4214                        environment.mark_unknown_names(conditional_file, conditional_byte);
4215                        continue;
4216                    }
4217                    for target in targets {
4218                        self.mark_macro_events_ambiguous(
4219                            target,
4220                            environment,
4221                            include_stack,
4222                            conditional_file,
4223                            conditional_byte,
4224                        );
4225                    }
4226                }
4227                MacroEvent::Invalidate { .. } => {
4228                    for binding in environment.bindings.values_mut() {
4229                        *binding = MacroBinding::uncertain_from(
4230                            binding,
4231                            conditional_file,
4232                            conditional_byte,
4233                        );
4234                    }
4235                }
4236            }
4237        }
4238    }
4239
4240    fn merge_conditional_macro_definition(
4241        environment: &mut MacroEnvironment,
4242        name: &str,
4243        possible_binding: &MacroBinding,
4244        conditional_file: &ProjectFile,
4245        conditional_byte: usize,
4246    ) {
4247        // A conditional include can revisit an already-active guarded header.
4248        // If the possible branch defines the exact same macro, both outcomes
4249        // leave the binding unchanged; degrading it to Unknown would discard
4250        // proof because of an unrelated unresolved macro name (#2092).
4251        if environment.binding(name).is_some_and(|current| {
4252            current.definition != MacroDefinition::Unsupported
4253                && current.definition == possible_binding.definition
4254        }) {
4255            return;
4256        }
4257        environment.insert(
4258            name.to_string(),
4259            MacroBinding::ambiguous(conditional_file, conditional_byte),
4260        );
4261    }
4262
4263    pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
4264        let cell = self
4265            .macro_include_protection_cells
4266            .lock()
4267            .expect("C++ include protection cache poisoned")
4268            .entry(file.clone())
4269            .or_default()
4270            .clone();
4271        cell.get_or_init(|| {
4272            self.cpp.prepared_syntax(self.token, file).map_or(
4273                MacroIncludeProtection::None,
4274                |prepared| {
4275                    top_level_macro_include_protection(
4276                        prepared.tree().root_node(),
4277                        prepared.source(),
4278                    )
4279                },
4280            )
4281        })
4282        .clone()
4283    }
4284
4285    fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
4286        collect_macro_events(self.cpp, self.token, file)
4287    }
4288
4289    pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
4290        self.ordinary_type_import_cells
4291            .lock()
4292            .expect("C++ ordinary type import cache poisoned")
4293            .entry(file.clone())
4294            .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
4295            .clone()
4296    }
4297
4298    pub fn project_using_index(
4299        &self,
4300        build: impl FnOnce() -> ProjectUsingIndex,
4301    ) -> &ProjectUsingIndex {
4302        self.project_using_index.get_or_init(build)
4303    }
4304
4305    pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
4306        let mut files = self
4307            .visible_source_files_by_root
4308            .values()
4309            .flatten()
4310            .cloned()
4311            .collect::<HashSet<_>>()
4312            .into_iter()
4313            .collect::<Vec<_>>();
4314        files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
4315        files
4316    }
4317
4318    pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
4319        self.visible_source_files_by_root
4320            .get(root)
4321            .is_some_and(|files| files.contains(source))
4322    }
4323
4324    fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
4325        let cached = self
4326            .visible_parser_alias_name_sets
4327            .read()
4328            .expect("visible parser alias-name cache poisoned")
4329            .get(file)
4330            .cloned();
4331        let cell = if let Some(cached) = cached {
4332            cached
4333        } else {
4334            let mut cells = self
4335                .visible_parser_alias_name_sets
4336                .write()
4337                .expect("visible parser alias-name cache poisoned");
4338            Arc::clone(
4339                cells
4340                    .entry(file.clone())
4341                    .or_insert_with(|| Arc::new(OnceLock::new())),
4342            )
4343        };
4344        if let Some(names) = cell.get() {
4345            return names.contains(name);
4346        }
4347        let mut names = HashSet::default();
4348        let visible_files = self
4349            .visible_source_files_by_root
4350            .get(file)
4351            .cloned()
4352            .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
4353        for visible_file in visible_files {
4354            let Some(aliases) = self.canonical_alias_cell(self.cpp, &visible_file) else {
4355                return false;
4356            };
4357            for alias in aliases.get().expect("published canonical aliases").iter() {
4358                names.insert(alias.name.clone());
4359            }
4360        }
4361        #[cfg(any(test, feature = "test-support"))]
4362        self.visible_parser_alias_name_set_build_count
4363            .fetch_add(1, Ordering::Relaxed);
4364        cell.get_or_init(|| names).contains(name)
4365    }
4366
4367    pub fn parser_alias_name_may_resolve_to_target(
4368        &self,
4369        file: &ProjectFile,
4370        alias_name: &str,
4371        target: &CodeUnit,
4372    ) -> bool {
4373        let started = std::time::Instant::now();
4374        self.parser_alias_fallback_calls
4375            .fetch_add(1, Ordering::Relaxed);
4376        let key = (
4377            file.clone(),
4378            alias_name.to_string(),
4379            logical_symbol_key(target),
4380        );
4381        let cached = self
4382            .parser_alias_target_matches
4383            .read()
4384            .expect("parser alias target-match cache poisoned")
4385            .get(&key)
4386            .cloned();
4387        let cell = if let Some(cached) = cached {
4388            cached
4389        } else {
4390            let mut cells = self
4391                .parser_alias_target_matches
4392                .write()
4393                .expect("parser alias target-match cache poisoned");
4394            Arc::clone(
4395                cells
4396                    .entry(key)
4397                    .or_insert_with(|| Arc::new(OnceLock::new())),
4398            )
4399        };
4400        let matched = *cell.get_or_init(|| {
4401            any_visible_source_file_matches(
4402                &self.visible_source_files_by_root,
4403                file,
4404                |visible_file| {
4405                    self.parser_alias_fallback_files
4406                        .fetch_add(1, Ordering::Relaxed);
4407                    self.file_alias_matches(self.cpp, visible_file, alias_name, target)
4408                },
4409            )
4410        });
4411        self.parser_alias_fallback_elapsed_micros.fetch_add(
4412            started.elapsed().as_micros().min(usize::MAX as u128) as usize,
4413            Ordering::Relaxed,
4414        );
4415        matched
4416    }
4417
4418    fn callable_arities_for_target(
4419        &self,
4420        analyzer: &CppGraphSource<'_>,
4421        cpp: &dyn CppSource,
4422        file: &ProjectFile,
4423        prepared: &PreparedSyntaxTree,
4424        spec: &TargetSpec,
4425    ) -> Vec<ActivatedCallableArity> {
4426        let Some(signature) = spec.target.signature() else {
4427            return Vec::new();
4428        };
4429        let Some(candidates) = self
4430            .visible_by_identifier
4431            .get(file)
4432            .and_then(|by_name| by_name.get(&spec.member_name))
4433        else {
4434            return Vec::new();
4435        };
4436        let differing_candidates = candidates
4437            .iter()
4438            .filter(|candidate| {
4439                candidate.is_function()
4440                    && candidate.fq_name() == spec.target.fq_name()
4441                    && candidate.signature() == Some(signature)
4442            })
4443            .filter_map(|candidate| {
4444                analyzer
4445                    .signature_metadata(candidate)
4446                    .into_iter()
4447                    .find_map(|metadata| metadata.callable_arity())
4448                    .filter(|arity| Some(*arity) != spec.callable_arity)
4449                    .map(|arity| (candidate, arity))
4450            })
4451            .collect::<Vec<_>>();
4452        if differing_candidates.is_empty() {
4453            return Vec::new();
4454        }
4455        let mut arities = Vec::with_capacity(differing_candidates.len());
4456        // The activation ranges here describe the whole file rather than one
4457        // reference, so there is no reference guard environment to consult.
4458        let reference = CallableReferenceContext {
4459            file,
4460            position: None,
4461        };
4462        for (candidate, candidate_arity) in differing_candidates {
4463            let declaration_activation =
4464                callable_declaration_activation_in_file(analyzer, candidate, &reference);
4465            let Some(declaration_activation) = declaration_activation else {
4466                continue;
4467            };
4468            let activation_byte = if candidate.source() == file {
4469                Some(declaration_activation)
4470            } else {
4471                self.include_activation_for_source(cpp, file, prepared, candidate.source())
4472            };
4473            if let Some(activation_byte) = activation_byte {
4474                arities.push(ActivatedCallableArity {
4475                    activation_byte,
4476                    arity: candidate_arity,
4477                });
4478            }
4479        }
4480        arities
4481    }
4482
4483    fn callable_parameter_macro_arity(
4484        &self,
4485        target: &CodeUnit,
4486        parameter_types: Option<&[String]>,
4487    ) -> Option<CallableArity> {
4488        let parameter_types = parameter_types?;
4489        let [macro_name] = parameter_types else {
4490            return None;
4491        };
4492        if macro_name.is_empty()
4493            || !macro_name
4494                .chars()
4495                .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
4496        {
4497            return None;
4498        }
4499        let cache_key = (target.source().clone(), macro_name.clone());
4500        if let Some(cached) = self
4501            .callable_parameter_macro_arities
4502            .lock()
4503            .expect("C++ callable parameter-macro arity cache poisoned")
4504            .get(&cache_key)
4505            .copied()
4506        {
4507            return cached;
4508        }
4509        let mut visible_files = HashSet::default();
4510        collect_include_closure(
4511            &self.cpp_source(),
4512            self.cpp.include_target_index(),
4513            target.source(),
4514            &mut visible_files,
4515            None,
4516        );
4517        let mut arities = Vec::new();
4518        for visible_file in visible_files {
4519            let cell = self.macro_event_cell(&visible_file);
4520            for event in
4521                cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
4522            {
4523                let MacroEvent::Define { name, binding, .. } = event else {
4524                    continue;
4525                };
4526                if name != macro_name {
4527                    continue;
4528                }
4529                let MacroDefinition::Object { replacement } = &binding.definition else {
4530                    continue;
4531                };
4532                let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
4533                    continue;
4534                };
4535                if !arities.contains(&arity) {
4536                    arities.push(arity);
4537                }
4538            }
4539        }
4540        let resolved = (|| {
4541            let required = arities
4542                .iter()
4543                .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
4544                .min()?;
4545            let total = arities.iter().map(|arity| arity.total()).max()?;
4546            let repeated = arities
4547                .iter()
4548                .any(|arity| arity.accepts(arity.total().saturating_add(1)));
4549            // Preprocessor conditions can leave more than one object-like parameter
4550            // bundle active in the target header's include closure. Preserve their
4551            // conservative callable envelope instead of choosing whichever definition
4552            // happened to be visited first.
4553            Some(CallableArity::new(required, total, repeated))
4554        })();
4555        self.callable_parameter_macro_arities
4556            .lock()
4557            .expect("C++ callable parameter-macro arity cache poisoned")
4558            .insert(cache_key, resolved);
4559        resolved
4560    }
4561
4562    pub fn include_activation_for_source(
4563        &self,
4564        cpp: &dyn CppSource,
4565        file: &ProjectFile,
4566        prepared: &PreparedSyntaxTree,
4567        donor_source: &ProjectFile,
4568    ) -> Option<usize> {
4569        let key = (file.clone(), donor_source.clone());
4570        if let Some(cached) = self
4571            .include_activation_cells
4572            .lock()
4573            .expect("C++ include activation cache poisoned")
4574            .get(&key)
4575            .copied()
4576        {
4577            return cached;
4578        }
4579        #[cfg(any(test, feature = "test-support"))]
4580        self.include_activation_build_count
4581            .fetch_add(1, Ordering::Relaxed);
4582        let language_guard_stable = OnceCell::new();
4583        let activation =
4584            find_include_activation(cpp, self.token, file, prepared, donor_source, &|| {
4585                *language_guard_stable.get_or_init(|| self.include_language_guard_is_stable(file))
4586            });
4587        let mut cells = self
4588            .include_activation_cells
4589            .lock()
4590            .expect("C++ include activation cache poisoned");
4591        *cells.entry(key).or_insert(activation)
4592    }
4593
4594    pub fn conditional_include_projections_for_source(
4595        &self,
4596        file: &ProjectFile,
4597        prepared: &PreparedSyntaxTree,
4598        donor_source: &ProjectFile,
4599    ) -> Arc<[ConditionalIncludeProjection]> {
4600        static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
4601        let cell = self
4602            .conditional_include_projection_cells
4603            .lock()
4604            .expect("C++ conditional include projection cache poisoned")
4605            .entry(file.clone())
4606            .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
4607            .clone();
4608        let index = cell.get_or_build_pool_independent(|| {
4609            #[cfg(any(test, feature = "test-support"))]
4610            self.conditional_include_projection_index_build_count
4611                .fetch_add(1, Ordering::Relaxed);
4612            let language_guard_stable = OnceCell::new();
4613            find_conditional_include_projection_index(
4614                self.cpp,
4615                self.token,
4616                file,
4617                prepared,
4618                &|| {
4619                    *language_guard_stable
4620                        .get_or_init(|| self.include_language_guard_is_stable(file))
4621                },
4622                &|| {
4623                    #[cfg(any(test, feature = "test-support"))]
4624                    self.conditional_include_projection_state_count
4625                        .fetch_add(1, Ordering::Relaxed);
4626                },
4627            )
4628        });
4629        index
4630            .get(donor_source)
4631            .cloned()
4632            .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
4633    }
4634
4635    fn include_language_guard_is_stable(&self, file: &ProjectFile) -> bool {
4636        let mut macro_events = |file: &ProjectFile| {
4637            let cell = self.macro_event_cell(file);
4638            cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice())
4639                .clone()
4640        };
4641        include_language_guard_is_stable_with(
4642            self.cpp,
4643            self.token,
4644            file,
4645            &mut macro_events,
4646            &mut |file: &ProjectFile, prepared: &PreparedSyntaxTree| {
4647                self.structured_include_facts(file, prepared)
4648            },
4649        )
4650    }
4651
4652    #[cfg(any(test, feature = "test-support"))]
4653    pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
4654        (
4655            self.conditional_include_projection_index_build_count
4656                .load(Ordering::Relaxed),
4657            self.conditional_include_projection_state_count
4658                .load(Ordering::Relaxed),
4659        )
4660    }
4661
4662    #[cfg(any(test, feature = "test-support"))]
4663    pub fn conditional_include_target_state_count_for_test(&self) -> usize {
4664        self.conditional_include_target_state_count
4665            .load(Ordering::Relaxed)
4666    }
4667
4668    #[cfg(any(test, feature = "test-support"))]
4669    pub fn include_activation_build_count_for_test(&self) -> usize {
4670        self.include_activation_build_count.load(Ordering::Relaxed)
4671    }
4672
4673    #[cfg(any(test, feature = "test-support"))]
4674    pub fn note_using_donor_activation_for_test(&self) {
4675        self.using_donor_activation_count
4676            .fetch_add(1, Ordering::Relaxed);
4677    }
4678
4679    #[cfg(not(any(test, feature = "test-support")))]
4680    pub fn note_using_donor_activation_for_test(&self) {}
4681
4682    #[cfg(any(test, feature = "test-support"))]
4683    pub fn note_using_namespace_lookup_for_test(&self) {
4684        self.using_namespace_lookup_count
4685            .fetch_add(1, Ordering::Relaxed);
4686    }
4687
4688    #[cfg(not(any(test, feature = "test-support")))]
4689    pub fn note_using_namespace_lookup_for_test(&self) {}
4690
4691    #[cfg(any(test, feature = "test-support"))]
4692    pub fn note_using_name_candidate_inspection_for_test(&self) {
4693        self.using_name_candidate_inspection_count
4694            .fetch_add(1, Ordering::Relaxed);
4695    }
4696
4697    #[cfg(not(any(test, feature = "test-support")))]
4698    pub fn note_using_name_candidate_inspection_for_test(&self) {}
4699
4700    #[cfg(any(test, feature = "test-support"))]
4701    pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
4702        (
4703            self.using_donor_activation_count.load(Ordering::Relaxed),
4704            self.using_namespace_lookup_count.load(Ordering::Relaxed),
4705            self.callable_reference_spec_build_count
4706                .load(Ordering::Relaxed),
4707            self.using_name_candidate_inspection_count
4708                .load(Ordering::Relaxed),
4709        )
4710    }
4711
4712    pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
4713        file == target.source()
4714            || self
4715                .visible_by_file
4716                .get(file)
4717                .is_some_and(|visible| visible.contains(target))
4718    }
4719
4720    /// Whether some declaration of `declaration`'s logical symbol is visible at
4721    /// `reference_byte` in `file`.
4722    ///
4723    /// The question is asked of the *logical* symbol, not of the physical unit:
4724    /// an out-of-line body in a `.cpp` nobody includes is never itself visible,
4725    /// and it does not have to be - what makes the call legal is the header
4726    /// declaration that the reference file does include. Reading that relation
4727    /// through `same_logical_callable` rather than through signature strings is
4728    /// the same #2010 correction the gates make, and it matters here because
4729    /// the body and the declaration are exactly the pair that spells one
4730    /// parameter type two ways.
4731    pub fn declaration_visible_at(
4732        &self,
4733        analyzer: &CppGraphSource<'_>,
4734        file: &ProjectFile,
4735        declaration: &CodeUnit,
4736        reference_byte: usize,
4737    ) -> bool {
4738        let reference_guards = OnceCell::new();
4739        self.visible_identifier_candidates(file, declaration.identifier())
4740            .filter(|candidate| {
4741                self.same_logical_callable(analyzer, candidate, declaration)
4742                    || flattened_macro_namespace_declaration_matches(
4743                        analyzer,
4744                        self.cpp,
4745                        file,
4746                        candidate,
4747                        declaration,
4748                        reference_byte,
4749                    )
4750            })
4751            .any(|candidate| {
4752                self.physical_declaration_visible_at(
4753                    analyzer,
4754                    file,
4755                    candidate,
4756                    reference_byte,
4757                    &reference_guards,
4758                )
4759            })
4760    }
4761
4762    /// Whether a physical declaration is visible at an exact structured
4763    /// reference node. Inverse C field references need the reference's own
4764    /// preprocessor environment before using the callable activation path:
4765    /// callable activation can establish that a field declaration is
4766    /// nameable, but it must not admit the opposite branch of that field's
4767    /// conditional family.
4768    pub fn declaration_visible_at_reference(
4769        &self,
4770        analyzer: &CppGraphSource<'_>,
4771        file: &ProjectFile,
4772        declaration: &CodeUnit,
4773        reference: Node<'_>,
4774    ) -> bool {
4775        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4776            return false;
4777        };
4778        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4779        let declaration_guards = declaration_guard_requirements(analyzer, self.cpp, declaration);
4780        if !declaration_guards.iter().any(|(_, required)| {
4781            guards_compatible_at_reference(required, reference_guards.as_ref())
4782        }) {
4783            return false;
4784        }
4785        if declaration.source() == file
4786            && !analyzer.reference_uses_c_semantics(file)
4787            && (declaration.is_field() || declaration.is_callable())
4788            && type_owner_of(analyzer, declaration).is_some_and(|owner| {
4789                self.indexed_enclosing_owner_scope(analyzer, file, reference)
4790                    .is_some_and(|scope| scope == canonical_cpp_scope_components(&owner))
4791            })
4792        {
4793            // C++ complete-class context makes members visible throughout the
4794            // class. The guard check above still excludes a declaration from
4795            // an incompatible preprocessor branch.
4796            return true;
4797        }
4798        if declaration.is_field() && declaration.source() == file {
4799            let reference_byte = reference.start_byte();
4800            let reference_function =
4801                real_function_definition_ancestor(reference, prepared.source());
4802            let guards = OnceCell::new();
4803            let field_reference = CallableReferenceContext {
4804                file,
4805                position: Some(CallableReferencePosition {
4806                    prepared: prepared.as_ref(),
4807                    byte: reference_byte,
4808                    guards: &guards,
4809                }),
4810            };
4811            let mut has_local_declaration = false;
4812            let mut local_declaration_visible = false;
4813            for declaration in callable_declaration_nodes(analyzer, prepared.as_ref(), declaration)
4814            {
4815                let Some(declaration_function) =
4816                    real_function_definition_ancestor(declaration, prepared.source())
4817                else {
4818                    continue;
4819                };
4820                has_local_declaration = true;
4821                if reference_function.is_some_and(|reference_function| {
4822                    reference_function.start_byte() == declaration_function.start_byte()
4823                        && reference_function.end_byte() == declaration_function.end_byte()
4824                }) && callable_preprocessor_context_is_visible_for_reference(
4825                    declaration,
4826                    prepared.source(),
4827                    &field_reference,
4828                ) && declaration.end_byte() < reference_byte
4829                {
4830                    local_declaration_visible = true;
4831                    break;
4832                }
4833            }
4834            if has_local_declaration {
4835                return local_declaration_visible;
4836            }
4837        }
4838        let guards = OnceCell::new();
4839        self.physical_declaration_visible_at(
4840            analyzer,
4841            file,
4842            declaration,
4843            reference.start_byte(),
4844            &guards,
4845        )
4846    }
4847
4848    /// C forward navigation may bind a call to a later same-file definition.
4849    /// There is no earlier source declaration to activate in that legacy C
4850    /// shape, but the call's preprocessor environment must still imply the
4851    /// definition's requirements. Ordinary C++ and inverse visibility retain
4852    /// the declaration-order rule in [`Self::declaration_visible_at`].
4853    pub fn declaration_visible_for_c_forward_call(
4854        &self,
4855        analyzer: &CppGraphSource<'_>,
4856        file: &ProjectFile,
4857        declaration: &CodeUnit,
4858        reference_byte: usize,
4859    ) -> bool {
4860        if self.declaration_visible_at(analyzer, file, declaration, reference_byte) {
4861            return true;
4862        }
4863        if declaration.source() != file {
4864            return false;
4865        }
4866        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4867            return false;
4868        };
4869        let reference_guards = prepared
4870            .tree()
4871            .root_node()
4872            .descendant_for_byte_range(reference_byte, reference_byte)
4873            .and_then(|node| preprocessor_guard_environment(node, prepared.source()));
4874        declaration_guard_requirements(analyzer, self.cpp, declaration)
4875            .into_iter()
4876            .any(|(_, required)| {
4877                guard_requirements_hold_at_reference(&required, reference_guards.as_ref())
4878            })
4879    }
4880
4881    pub fn callable_arity_at_reference(
4882        &self,
4883        analyzer: &CppGraphSource<'_>,
4884        file: &ProjectFile,
4885        candidate: &CodeUnit,
4886        reference_byte: usize,
4887    ) -> Option<CallableArity> {
4888        let key = (file.clone(), logical_symbol_key(candidate));
4889        let cell = self
4890            .callable_reference_specs
4891            .lock()
4892            .expect("C++ callable reference-spec cache poisoned")
4893            .entry(key)
4894            .or_default()
4895            .clone();
4896        let spec = cell.get_or_init(|| {
4897            let prepared = self.cpp.prepared_syntax(self.token, file)?;
4898            let spec = TargetSpec::from_target(analyzer, candidate)?;
4899            let spec = spec
4900                .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
4901                .into_owned();
4902            #[cfg(any(test, feature = "test-support"))]
4903            self.callable_reference_spec_build_count
4904                .fetch_add(1, Ordering::Relaxed);
4905            Some(spec)
4906        });
4907        spec.as_ref()?.callable_arity_at(reference_byte)
4908    }
4909
4910    fn physical_declaration_visible_at(
4911        &self,
4912        analyzer: &CppGraphSource<'_>,
4913        file: &ProjectFile,
4914        declaration: &CodeUnit,
4915        reference_byte: usize,
4916        reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
4917    ) -> bool {
4918        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4919            return false;
4920        };
4921        let reference = CallableReferenceContext {
4922            file,
4923            position: Some(CallableReferencePosition {
4924                prepared: prepared.as_ref(),
4925                byte: reference_byte,
4926                guards: reference_guards,
4927            }),
4928        };
4929        if declaration.source() == file {
4930            return callable_declaration_activation_in_file(analyzer, declaration, &reference)
4931                .or_else(|| {
4932                    self.exhaustive_guard_family_activation(analyzer, declaration, &reference)
4933                })
4934                .is_some_and(|activation| activation < reference_byte);
4935        }
4936        if self
4937            .foreign_callable_declaration_activation(analyzer, declaration, &reference)
4938            .or_else(|| self.exhaustive_guard_family_activation(analyzer, declaration, &reference))
4939            .is_none()
4940        {
4941            return false;
4942        }
4943        declaration_guard_requirements(analyzer, self.cpp, declaration)
4944            .into_iter()
4945            .any(|(_, declaration_guards)| {
4946                self.foreign_declaration_reachable_at_reference(
4947                    file,
4948                    prepared.as_ref(),
4949                    declaration.source(),
4950                    &declaration_guards,
4951                    reference.guards(),
4952                    reference_byte,
4953                )
4954            })
4955    }
4956
4957    /// The byte at which `declaration` activates inside the foreign file that
4958    /// its canonical source occurrence describes.
4959    ///
4960    /// Which guard rule applies depends on what decides the reference's
4961    /// configuration. When `compile_commands.json` covers the reference's
4962    /// translation unit, the build is the proof source and the declaration's
4963    /// guards must follow from the reference's active guards plus the proven
4964    /// defines; a platform macro the build does not prove leaves the
4965    /// declaration invisible (#2011). With no compile coverage nothing decides
4966    /// a platform macro, so the cross-file rule stands: the donor resolves its
4967    /// own conditionals and only has to stay free of contradiction with the
4968    /// reference. Otherwise a libuv-shaped `#if defined(__linux__)` prototype
4969    /// in a shared header is hidden from every unguarded caller in another
4970    /// translation unit (#2988).
4971    fn foreign_callable_declaration_activation(
4972        &self,
4973        analyzer: &CppGraphSource<'_>,
4974        declaration: &CodeUnit,
4975        reference: &CallableReferenceContext<'_>,
4976    ) -> Option<usize> {
4977        let build_decides = !self.compile_context_is_absent(reference.file);
4978        let proven = self.compile_proven_guards(reference.file);
4979        let augmented;
4980        let active = match reference.guards() {
4981            Some(active) if !proven.is_empty() => {
4982                augmented = active.union(&proven).cloned().collect();
4983                Some(&augmented)
4984            }
4985            other => other,
4986        };
4987        analyzer
4988            .declaration_source_occurrences(declaration)?
4989            .into_iter()
4990            .filter_map(|occurrence| {
4991                let activation = occurrence.fact.callable_activation?;
4992                let mut required =
4993                    cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
4994                if required.iter().any(|guard| match guard {
4995                    PreprocessorGuard::Defined(name) if name == "__cplusplus" => reference.is_c(),
4996                    PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
4997                        !reference.is_c()
4998                    }
4999                    _ => false,
5000                }) {
5001                    return None;
5002                }
5003                required.retain(|guard| {
5004                    !matches!(guard,
5005                    PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
5006                        if name == "__cplusplus")
5007                });
5008                let visible = required.is_empty()
5009                    || if build_decides {
5010                        guard_requirements_hold_at_reference(&required, active)
5011                    } else {
5012                        guards_compatible_at_reference(&required, reference.guards())
5013                    };
5014                visible.then_some(activation)
5015            })
5016            .min()
5017    }
5018
5019    pub fn external_type_candidate_visible_at(
5020        &self,
5021        file: &ProjectFile,
5022        candidate: &CodeUnit,
5023        reference_byte: usize,
5024    ) -> bool {
5025        if candidate.source() == file {
5026            return true;
5027        }
5028        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5029            return false;
5030        };
5031        self.visible_identifier_candidates(file, candidate.identifier())
5032            .filter(|peer| same_logical_symbol(candidate, peer))
5033            .any(|peer| {
5034                peer.source() == file
5035                    || self
5036                        .include_activation_for_source(
5037                            self.cpp,
5038                            file,
5039                            prepared.as_ref(),
5040                            peer.source(),
5041                        )
5042                        .is_some_and(|activation| activation <= reference_byte)
5043            })
5044    }
5045
5046    pub fn external_type_declaration_visible_at(
5047        &self,
5048        file: &ProjectFile,
5049        candidate: &CodeUnit,
5050        reference_byte: usize,
5051    ) -> bool {
5052        if candidate.source() == file {
5053            return true;
5054        }
5055        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5056            return false;
5057        };
5058        self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
5059            .is_some_and(|activation| activation <= reference_byte)
5060    }
5061
5062    /// The preprocessor facts the build proves for a reference sited in
5063    /// `file` (#2011).
5064    ///
5065    /// Every `-D` that survives its command's `-D`/`-U` ordering is a positive
5066    /// `Defined` fact, and a fact holds only when every compile configuration
5067    /// that governs the file agrees on it (intersection). The facts are
5068    /// strictly additive to the reference's active guard set: they can prove a
5069    /// required guard, but the guard check itself is never weakened and no
5070    /// implication is ever inferred from source text.
5071    ///
5072    /// A file with its own database entry answers from that entry alone
5073    /// (phase 1). A header takes its context from the translation units whose
5074    /// include closure reaches it, intersected across all of them (phase 2):
5075    /// the header is compiled once per including TU, so a fact holds for a
5076    /// header-sited reference only when every one of those compilations
5077    /// proves it. A reaching TU the database does not cover proves nothing,
5078    /// which empties the intersection. A file nothing covers or reaches has
5079    /// no facts and every check runs on source structure alone.
5080    pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
5081        if let Some(cached) = self
5082            .compile_proven_guard_cells
5083            .lock()
5084            .expect("C++ compile-proven guard cache poisoned")
5085            .get(file)
5086        {
5087            return Arc::clone(cached);
5088        }
5089        let names = match context_fact_names(self.cpp.compile_contexts_for(file)) {
5090            Some(names) => names,
5091            None => {
5092                let mut translation_units = self.cpp.reaching_translation_units(file).into_iter();
5093                let seed = translation_units.next().and_then(|translation_unit| {
5094                    context_fact_names(self.cpp.compile_contexts_for(&translation_unit))
5095                });
5096                match seed {
5097                    None => HashSet::default(),
5098                    Some(mut names) => {
5099                        for translation_unit in translation_units {
5100                            let Some(reached) = context_fact_names(
5101                                self.cpp.compile_contexts_for(&translation_unit),
5102                            ) else {
5103                                names.clear();
5104                                break;
5105                            };
5106                            names.retain(|name| reached.contains(name));
5107                            if names.is_empty() {
5108                                break;
5109                            }
5110                        }
5111                        names
5112                    }
5113                }
5114            }
5115        };
5116        let proven = Arc::new(
5117            names
5118                .into_iter()
5119                .map(PreprocessorGuard::Defined)
5120                .collect::<HashSet<_>>(),
5121        );
5122        self.compile_proven_guard_cells
5123            .lock()
5124            .expect("C++ compile-proven guard cache poisoned")
5125            .insert(file.clone(), Arc::clone(&proven));
5126        proven
5127    }
5128
5129    /// The rule a conditional `#include` path's guards face on the way to a
5130    /// reference in `file`.
5131    ///
5132    /// This is the include-edge analogue of the choice
5133    /// [`Self::foreign_callable_declaration_activation`] makes for a foreign
5134    /// declaration's own guards (#2988), and it is decided by the same thing:
5135    /// what settles the reference's configuration. Memoized per file because
5136    /// the answer depends on nothing else, and the callers ask it once per
5137    /// candidate.
5138    fn include_path_admission(&self, file: &ProjectFile) -> IncludePathAdmission {
5139        if let Some(cached) = self
5140            .include_path_admission_cells
5141            .lock()
5142            .expect("C++ include-path admission cache poisoned")
5143            .get(file)
5144            .copied()
5145        {
5146            return cached;
5147        }
5148        let admission = if self.compile_context_is_absent(file) {
5149            IncludePathAdmission::Compatible
5150        } else {
5151            IncludePathAdmission::Proven
5152        };
5153        self.include_path_admission_cells
5154            .lock()
5155            .expect("C++ include-path admission cache poisoned")
5156            .insert(file.clone(), admission);
5157        admission
5158    }
5159
5160    /// Whether no compile data covers the compilations of `file`: it has no
5161    /// database entry of its own, and either nothing reaches it or some
5162    /// translation unit that reaches it has no entry. This is the state a
5163    /// regenerated `compile_commands.json` could decide; data that is present
5164    /// for every governing compilation but does not prove a guard is a
5165    /// decided conservative miss, not this state.
5166    fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
5167        if !self.cpp.compile_contexts_for(file).is_empty() {
5168            return false;
5169        }
5170        let translation_units = self.cpp.reaching_translation_units(file);
5171        translation_units.is_empty()
5172            || translation_units
5173                .iter()
5174                .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
5175    }
5176
5177    /// Whether a lookup miss for `identifier` in `file` is explainable by
5178    /// missing compile context (#2011): some same-name declaration is
5179    /// reachable through a conditional include whose required guards neither
5180    /// contradict the reference's active guards nor follow from them, and the
5181    /// translation unit has no compile-commands entry that could decide the
5182    /// question. Callers surface this as an explicit "requires compile
5183    /// context" incompleteness instead of an indistinguishable miss.
5184    ///
5185    /// A structurally disproven declaration (contradicting guards) and a TU
5186    /// whose compile context exists but does not prove the guard both answer
5187    /// `false`: those misses are decided, not incomplete.
5188    pub fn miss_requires_compile_context(
5189        &self,
5190        file: &ProjectFile,
5191        identifier: &str,
5192        reference: Node<'_>,
5193    ) -> bool {
5194        if !self.compile_context_is_absent(file) {
5195            return false;
5196        }
5197        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5198            return false;
5199        };
5200        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5201        let reference_byte = reference.start_byte();
5202        let mut sources = self
5203            .visible_identifier_candidates(file, identifier)
5204            .map(CodeUnit::source)
5205            .filter(|source| *source != file)
5206            .collect::<Vec<_>>();
5207        sources.sort();
5208        sources.dedup();
5209        sources.into_iter().any(|declaration_source| {
5210            self.conditional_include_projections_for_source(
5211                file,
5212                prepared.as_ref(),
5213                declaration_source,
5214            )
5215            .iter()
5216            .any(|projection| {
5217                projection.activation_byte <= reference_byte
5218                    && !guard_requirements_hold_at_reference(
5219                        &projection.required_guards,
5220                        reference_guards.as_ref(),
5221                    )
5222                    && guards_compatible_at_reference(
5223                        &projection.required_guards,
5224                        reference_guards.as_ref(),
5225                    )
5226            })
5227        })
5228    }
5229
5230    /// Decide whether a declaration that lives in another file reaches a
5231    /// reference in `file`.
5232    ///
5233    /// An external header selects its declaration branch before the reference
5234    /// file is parsed. Require compatible reference guards, but do not test
5235    /// the header's guard expression for stability in the reference file: a
5236    /// `.c` translation unit can never satisfy the `#ifdef __cplusplus` that
5237    /// wraps every declaration of a portable C header, and demanding it would
5238    /// hide the whole header. Guards that the reference file imposes on its
5239    /// own `#include` still have to be stable, and must satisfy the rule
5240    /// [`IncludePathAdmission`] names for this reference's translation unit.
5241    fn foreign_declaration_reachable_at_reference(
5242        &self,
5243        file: &ProjectFile,
5244        prepared: &PreparedSyntaxTree,
5245        declaration_source: &ProjectFile,
5246        declaration_guards: &HashSet<PreprocessorGuard>,
5247        reference_guards: Option<&HashSet<PreprocessorGuard>>,
5248        reference_byte: usize,
5249    ) -> bool {
5250        // The translation unit's build-proven defines join the reference's
5251        // active guard set (#2011): a conditional include like the nng
5252        // `NNG_PLATFORM_POSIX` chain is provable only by the compile command.
5253        // A reference whose own environment is unknown stays unknown -- the
5254        // facts extend an environment, they never invent one.
5255        let proven = self.compile_proven_guards(file);
5256        let augmented;
5257        let reference_guards = match reference_guards {
5258            Some(active) if !proven.is_empty() => {
5259                augmented = active.union(&proven).cloned().collect();
5260                Some(&augmented)
5261            }
5262            other => other,
5263        };
5264        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5265            eprintln!(
5266                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=foreign_guard_compatibility declaration_source={} declaration_guards={declaration_guards:?} reference_guards={reference_guards:?}",
5267                declaration_source.rel_path().display(),
5268            );
5269        }
5270        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5271            return false;
5272        }
5273        if self
5274            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5275            .is_some_and(|activation| activation <= reference_byte)
5276        {
5277            return true;
5278        }
5279        let projections =
5280            self.conditional_include_projections_for_source(file, prepared, declaration_source);
5281        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5282            eprintln!(
5283                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=filtered_projection source={} declaration_guards={} proven_guards={} projections={}",
5284                declaration_source.rel_path().display(),
5285                declaration_guards.len(),
5286                proven.len(),
5287                projections.len(),
5288            );
5289        }
5290        let admission = self.include_path_admission(file);
5291        projections.iter().any(|projection| {
5292            projection.activation_byte <= reference_byte
5293                && admission.admits(
5294                    &projection.required_guards,
5295                    &projection.partial_guards,
5296                    reference_guards,
5297                )
5298                && self.preprocessor_guards_stable_between(
5299                    file,
5300                    projection.activation_byte,
5301                    reference_byte,
5302                    &projection.required_guards,
5303                )
5304        })
5305    }
5306
5307    fn foreign_declaration_may_be_reachable_from_raw_guards(
5308        &self,
5309        file: &ProjectFile,
5310        prepared: &PreparedSyntaxTree,
5311        declaration_source: &ProjectFile,
5312        declaration_guards: &HashSet<PreprocessorGuard>,
5313        reference_guards: Option<&HashSet<PreprocessorGuard>>,
5314        reference_byte: usize,
5315    ) -> bool {
5316        let proven = self.compile_proven_guards(file);
5317        let augmented;
5318        let reference_guards = match reference_guards {
5319            Some(active) if !proven.is_empty() => {
5320                augmented = active.union(&proven).cloned().collect();
5321                Some(&augmented)
5322            }
5323            other => other,
5324        };
5325        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5326            return false;
5327        }
5328        if self
5329            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5330            .is_some_and(|activation| activation <= reference_byte)
5331        {
5332            return true;
5333        }
5334        let language_guard_stable = OnceCell::new();
5335        let reachable = find_conditional_include_projection_for_source(
5336            self.cpp,
5337            self.token,
5338            file,
5339            prepared,
5340            declaration_source,
5341            self.include_path_admission(file),
5342            reference_guards,
5343            reference_byte,
5344            &|| *language_guard_stable.get_or_init(|| self.include_language_guard_is_stable(file)),
5345            &|| {
5346                #[cfg(any(test, feature = "test-support"))]
5347                self.conditional_include_target_state_count
5348                    .fetch_add(1, Ordering::Relaxed);
5349            },
5350        );
5351        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5352            eprintln!(
5353                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_projection source={} declaration_guards={} proven_guards={} raw_guards={} reachable={reachable}",
5354                declaration_source.rel_path().display(),
5355                declaration_guards.len(),
5356                proven.len(),
5357                reference_guards.map_or(0, HashSet::len),
5358            );
5359        }
5360        reachable
5361    }
5362
5363    fn foreign_declaration_reachable_from_compile_proven_guards(
5364        &self,
5365        file: &ProjectFile,
5366        prepared: &PreparedSyntaxTree,
5367        declaration_source: &ProjectFile,
5368        declaration_guards: &HashSet<PreprocessorGuard>,
5369        reference_byte: usize,
5370    ) -> bool {
5371        let proven = self.compile_proven_guards(file);
5372        if proven.is_empty()
5373            || !guards_compatible_at_reference(declaration_guards, Some(proven.as_ref()))
5374        {
5375            return false;
5376        }
5377        let projections =
5378            self.conditional_include_projections_for_source(file, prepared, declaration_source);
5379        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5380            eprintln!(
5381                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=compile_proven_projection source={} declaration_guards={} proven_guards={} projections={}",
5382                declaration_source.rel_path().display(),
5383                declaration_guards.len(),
5384                proven.len(),
5385                projections.len(),
5386            );
5387        }
5388        projections.iter().any(|projection| {
5389            projection.activation_byte <= reference_byte
5390                    && guard_requirements_hold_at_reference(
5391                        &projection.required_guards,
5392                        Some(proven.as_ref()),
5393                    )
5394                    // Build facts hold at translation-unit entry. A source
5395                    // `#undef` or an earlier include may invalidate one before
5396                    // this conditional include is reached; mutations after the
5397                    // include cannot revoke declarations it already supplied.
5398                    && self.preprocessor_guards_stable_between(
5399                        file,
5400                        0,
5401                        projection.activation_byte,
5402                        &projection.required_guards,
5403                    )
5404        })
5405    }
5406
5407    pub fn external_type_candidate_visible_in_context(
5408        &self,
5409        analyzer: &CppGraphSource<'_>,
5410        file: &ProjectFile,
5411        candidate: &CodeUnit,
5412        reference: Node<'_>,
5413    ) -> bool {
5414        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
5415        if report_stats {
5416            eprintln!(
5417                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=started fqn={} candidate_source={} reference_file={} reference_byte={}",
5418                candidate.fq_name(),
5419                candidate.source().rel_path().display(),
5420                file.rel_path().display(),
5421                reference.start_byte(),
5422            );
5423        }
5424        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5425            return false;
5426        };
5427        let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
5428        let reference_guards = OnceCell::new();
5429        let reference_guards_at_site = || {
5430            reference_guards.get_or_init(|| {
5431                let started = Instant::now();
5432                if report_stats {
5433                    eprintln!(
5434                        "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=started file={} reference_byte={} raw_guards={}",
5435                        file.rel_path().display(),
5436                        reference.start_byte(),
5437                        raw_reference_guards.as_ref().map_or(0, HashSet::len),
5438                    );
5439                }
5440                let macro_environment = self.macro_environment(file, reference.start_byte());
5441                let filtered = raw_reference_guards
5442                    .clone()
5443                    .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
5444                if report_stats {
5445                    eprintln!(
5446                        "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=completed retained={} elapsed_ms={}",
5447                        filtered.is_some(),
5448                        started.elapsed().as_millis(),
5449                    );
5450                }
5451                filtered
5452            })
5453        };
5454
5455        let peers = self
5456            .visible_identifier_candidates(file, candidate.identifier())
5457            .filter(|peer| same_logical_symbol(candidate, peer))
5458            .collect::<Vec<_>>();
5459        if report_stats {
5460            let peer_sources = peers
5461                .iter()
5462                .map(|peer| peer.source().rel_path().display().to_string())
5463                .collect::<Vec<_>>();
5464            eprintln!(
5465                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=peers fqn={} sources={peer_sources:?}",
5466                candidate.fq_name(),
5467            );
5468        }
5469        let directly_visible_without_reference_environment = peers.iter().any(|peer| {
5470            declaration_guard_requirements(analyzer, self.cpp, peer)
5471                .into_iter()
5472                .any(|(declaration_byte, declaration_guards)| {
5473                    if peer.source() == file {
5474                        let visible = declaration_byte < reference.start_byte()
5475                            && declaration_guards.is_empty();
5476                        if report_stats {
5477                            eprintln!(
5478                                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=true visible={visible}",
5479                                peer.source().rel_path().display(),
5480                                declaration_guards.len(),
5481                            );
5482                        }
5483                        return visible;
5484                    }
5485                    let direct = declaration_guards.is_empty()
5486                        && self
5487                            .include_activation_for_source(
5488                                self.cpp,
5489                                file,
5490                                prepared.as_ref(),
5491                                peer.source(),
5492                            )
5493                            .is_some_and(|activation| activation <= reference.start_byte());
5494                    let compile_proven = !direct
5495                        && self.foreign_declaration_reachable_from_compile_proven_guards(
5496                            file,
5497                            prepared.as_ref(),
5498                            peer.source(),
5499                            &declaration_guards,
5500                            reference.start_byte(),
5501                        );
5502                    if report_stats {
5503                        eprintln!(
5504                            "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=false direct={direct} compile_proven={compile_proven}",
5505                            peer.source().rel_path().display(),
5506                            declaration_guards.len(),
5507                        );
5508                    }
5509                    direct || compile_proven
5510                })
5511        });
5512        if directly_visible_without_reference_environment {
5513            if report_stats {
5514                eprintln!(
5515                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=direct_or_compile_proven fqn={}",
5516                    candidate.fq_name(),
5517                );
5518            }
5519            return true;
5520        }
5521        let directly_visible = peers.iter().any(|peer| {
5522            declaration_guard_sites(analyzer, self.cpp, peer, Some(reference.start_byte()))
5523                .into_iter()
5524                .any(|site| {
5525                    let declaration_byte = site.byte;
5526                    let declaration_guards = site.guards;
5527                    if peer.source() == file {
5528                        if declaration_byte >= reference.start_byte() {
5529                            return false;
5530                        }
5531                        // The reference's own conditional context must either
5532                        // prove the declaration's requirements, or follow a
5533                        // completed family that encloses the declaration
5534                        // (#3297); either way it must not contradict them.
5535                        let active = |guards: Option<&HashSet<PreprocessorGuard>>| {
5536                            guard_requirements_hold_at_reference(&declaration_guards, guards)
5537                                || (site.completed_family_before_reference
5538                                    && guards_compatible_at_reference(&declaration_guards, guards))
5539                        };
5540                        if !active(raw_reference_guards.as_ref()) {
5541                            return false;
5542                        }
5543                        return active(reference_guards_at_site().as_ref())
5544                            && self.preprocessor_guards_stable_between(
5545                                file,
5546                                declaration_byte,
5547                                reference.start_byte(),
5548                                &declaration_guards,
5549                            );
5550                    }
5551                    let raw_feasible = self.foreign_declaration_may_be_reachable_from_raw_guards(
5552                        file,
5553                        prepared.as_ref(),
5554                        peer.source(),
5555                        &declaration_guards,
5556                        raw_reference_guards.as_ref(),
5557                        reference.start_byte(),
5558                    );
5559                    if report_stats {
5560                        eprintln!(
5561                            "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_feasibility source={} declaration_guards={} feasible={raw_feasible}",
5562                            peer.source().rel_path().display(),
5563                            declaration_guards.len(),
5564                        );
5565                    }
5566                    if !raw_feasible {
5567                        return false;
5568                    }
5569                    self.foreign_declaration_reachable_at_reference(
5570                        file,
5571                        prepared.as_ref(),
5572                        peer.source(),
5573                        &declaration_guards,
5574                        reference_guards_at_site().as_ref(),
5575                        reference.start_byte(),
5576                    )
5577                })
5578        });
5579        if directly_visible {
5580            if report_stats {
5581                eprintln!(
5582                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=filtered_reference fqn={}",
5583                    candidate.fq_name(),
5584                );
5585            }
5586            return true;
5587        }
5588        let complementary = self
5589            .visible_identifier_candidates(file, candidate.identifier())
5590            .filter(|peer| {
5591                peer.kind() == candidate.kind()
5592                    && peer.fq_name() == candidate.fq_name()
5593                    && peer.source() == candidate.source()
5594            })
5595            .collect::<Vec<_>>();
5596        // A completed #if/#else family declares the shared source-level name
5597        // before this reference. A later macro mutation cannot revoke that
5598        // declaration. The family gate below rejects declarations split across
5599        // separate conditional blocks, where mutation can change coverage.
5600        let complementary_family =
5601            self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate);
5602        let raw_candidate_branch_compatible = complementary_family
5603            && raw_reference_guards.as_ref().is_some_and(|active| {
5604                declaration_guard_requirements(analyzer, self.cpp, candidate)
5605                    .iter()
5606                    .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5607            });
5608        if report_stats {
5609            eprintln!(
5610                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=complementary fqn={} candidates={} family={} raw_compatible={}",
5611                candidate.fq_name(),
5612                complementary.len(),
5613                complementary_family,
5614                raw_candidate_branch_compatible,
5615            );
5616        }
5617        let candidate_branch_compatible = raw_candidate_branch_compatible
5618            && reference_guards_at_site().as_ref().is_some_and(|active| {
5619                declaration_guard_requirements(analyzer, self.cpp, candidate)
5620                    .iter()
5621                    .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5622            });
5623        let complementary_visible = candidate_branch_compatible
5624            && if candidate.source() == file {
5625                declaration_guard_requirements(analyzer, self.cpp, candidate)
5626                    .iter()
5627                    .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
5628            } else {
5629                self.include_activation_for_source(
5630                    self.cpp,
5631                    file,
5632                    prepared.as_ref(),
5633                    candidate.source(),
5634                )
5635                .is_some_and(|activation| activation <= reference.start_byte())
5636            };
5637        if report_stats {
5638            eprintln!(
5639                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome={} fqn={}",
5640                if complementary_visible {
5641                    "complementary"
5642                } else {
5643                    "missing"
5644                },
5645                candidate.fq_name(),
5646            );
5647        }
5648        complementary_visible
5649    }
5650
5651    pub fn is_exhaustive_same_fqn_type_declaration_family(
5652        &self,
5653        analyzer: &CppGraphSource<'_>,
5654        file: &ProjectFile,
5655        candidate: &CodeUnit,
5656    ) -> bool {
5657        let candidates = self
5658            .visible_identifier_candidates(file, candidate.identifier())
5659            .filter(|peer| {
5660                peer.kind() == candidate.kind()
5661                    && peer.fq_name() == candidate.fq_name()
5662                    && peer.source() == candidate.source()
5663            })
5664            .collect::<Vec<_>>();
5665        self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
5666    }
5667
5668    /// Prove a nested type alias used as a dependent member-pointer owner when
5669    /// its owning class has mutually-exclusive declarations.  A common C++11
5670    /// compatibility shape provides the owning class in one preprocessor
5671    /// branch and aliases it to a standard-library type in the other branch;
5672    /// the nested fallback alias is therefore not itself active in every
5673    /// branch even though the qualified owner API is.
5674    ///
5675    /// This is deliberately narrower than ordinary type visibility.  The
5676    /// caller has already recovered a member-pointer owner path from the CST;
5677    /// this helper additionally requires the target's structured parent to
5678    /// match that path, physical source visibility, and exact preprocessor
5679    /// guard agreement with the parent declaration.  Only then may the
5680    /// parent's direct/complementary same-FQN visibility stand in for the
5681    /// nested terminal's active-branch check.
5682    pub fn dependent_member_pointer_alias_visible_in_context(
5683        &self,
5684        analyzer: &CppGraphSource<'_>,
5685        file: &ProjectFile,
5686        candidate: &CodeUnit,
5687        owner_components: &[String],
5688        reference: Node<'_>,
5689    ) -> bool {
5690        if !analyzer
5691            .type_alias_provider()
5692            .is_some_and(|provider| provider.is_type_alias(candidate))
5693        {
5694            return false;
5695        }
5696        let Some((terminal, owner_prefix)) = owner_components.split_last() else {
5697            return false;
5698        };
5699        if terminal != candidate.identifier()
5700            || canonical_cpp_scope_components(candidate) != owner_components
5701        {
5702            return false;
5703        }
5704        let Some(expected_parent_fq_name) =
5705            brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
5706        else {
5707            return false;
5708        };
5709        let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
5710            return false;
5711        };
5712        if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
5713            || parent_anchor.source() != candidate.source()
5714            || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
5715        {
5716            return false;
5717        }
5718
5719        // The ordinary path already handles unguarded aliases (and preserves
5720        // same-file declaration ordering).  This fallback is only for a
5721        // physically visible declaration whose guard is the owning branch's
5722        // guard, so reject a same-file declaration that appears after the
5723        // reference before considering guard compatibility.
5724        if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
5725            || candidate.source() == file
5726                && !analyzer
5727                    .ranges(candidate)
5728                    .iter()
5729                    .any(|range| range.start_byte < reference.start_byte())
5730        {
5731            return false;
5732        }
5733
5734        let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
5735        if candidate_guards.is_empty() {
5736            return false;
5737        }
5738        let same_guard_sets =
5739            |left: &[(usize, HashSet<PreprocessorGuard>)],
5740             right: &[(usize, HashSet<PreprocessorGuard>)]| {
5741                left.iter().all(|(_, left_guards)| {
5742                    right
5743                        .iter()
5744                        .any(|(_, right_guards)| left_guards == right_guards)
5745                })
5746            };
5747        let parent_candidates = self
5748            .visible_identifier_candidates(file, parent_anchor.identifier())
5749            .filter(|peer| {
5750                peer.kind() == parent_anchor.kind()
5751                    && peer.fq_name() == expected_parent_fq_name.as_str()
5752                    && peer.source() == parent_anchor.source()
5753                    && canonical_cpp_scope_components(peer) == owner_prefix
5754            })
5755            .filter_map(|peer| {
5756                let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
5757                (candidate_guards.len() == parent_guards.len()
5758                    && same_guard_sets(&candidate_guards, &parent_guards)
5759                    && same_guard_sets(&parent_guards, &candidate_guards))
5760                .then(|| (peer.clone(), parent_guards))
5761            })
5762            .collect::<Vec<_>>();
5763        let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
5764            return false;
5765        };
5766
5767        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5768            return false;
5769        };
5770        let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
5771        else {
5772            return false;
5773        };
5774        // An external header selects its declaration branch before the
5775        // reference file is parsed. Require compatible reference guards, but
5776        // do not test the header's guard expression for stability in the
5777        // reference file. Same-file aliases still require that stability.
5778        if !candidate_guards.iter().any(|(_, target_guards)| {
5779            guards_compatible_at_reference(target_guards, Some(&reference_guards))
5780                && (candidate.source() != file
5781                    || self.preprocessor_guards_stable_between(
5782                        file,
5783                        0,
5784                        reference.start_byte(),
5785                        target_guards,
5786                    ))
5787        }) {
5788            return false;
5789        }
5790
5791        self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
5792    }
5793
5794    /// Check a type candidate's preprocessor/import context without imposing
5795    /// ordinary declaration-before-reference ordering for same-file peers.
5796    ///
5797    /// C++ class scope makes member names visible throughout the complete
5798    /// class, including a trailing return type that appears before the member
5799    /// alias declaration in source order. Callers must first prove that the
5800    /// reference is inside the candidate's indexed class owner; this helper
5801    /// only relaxes the byte-order predicate while retaining guard and include
5802    /// activation checks.
5803    pub fn external_type_candidate_guard_compatible_in_context(
5804        &self,
5805        analyzer: &CppGraphSource<'_>,
5806        file: &ProjectFile,
5807        candidate: &CodeUnit,
5808        reference: Node<'_>,
5809    ) -> bool {
5810        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5811            return false;
5812        };
5813        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5814
5815        self.visible_identifier_candidates(file, candidate.identifier())
5816            .filter(|peer| same_logical_symbol(candidate, peer))
5817            .any(|peer| {
5818                declaration_guard_requirements(analyzer, self.cpp, peer)
5819                    .into_iter()
5820                    .any(|(declaration_byte, declaration_guards)| {
5821                        if peer.source() == file {
5822                            let (start, end) = if declaration_byte <= reference.start_byte() {
5823                                (declaration_byte, reference.start_byte())
5824                            } else {
5825                                (reference.start_byte(), declaration_byte)
5826                            };
5827                            return guard_requirements_hold_at_reference(
5828                                &declaration_guards,
5829                                reference_guards.as_ref(),
5830                            ) && self.preprocessor_guards_stable_between(
5831                                file,
5832                                start,
5833                                end,
5834                                &declaration_guards,
5835                            );
5836                        }
5837                        self.foreign_declaration_reachable_at_reference(
5838                            file,
5839                            prepared.as_ref(),
5840                            peer.source(),
5841                            &declaration_guards,
5842                            reference_guards.as_ref(),
5843                            reference.start_byte(),
5844                        )
5845                    })
5846            })
5847    }
5848
5849    /// Whether a same-file callable declaration is nameable from `reference`
5850    /// after deliberately relaxing declaration-before-reference ordering.
5851    ///
5852    /// Ordinary lookup still requires an earlier declaration. Definition
5853    /// navigation for incomplete C translation units may recover a later
5854    /// definition, but only when it is at file scope and its preprocessor
5855    /// requirements hold at the call (#2404).
5856    pub fn same_file_callable_guard_compatible_ignoring_order(
5857        &self,
5858        analyzer: &CppGraphSource<'_>,
5859        file: &ProjectFile,
5860        candidate: &CodeUnit,
5861        reference: Node<'_>,
5862    ) -> bool {
5863        if candidate.source() != file || !candidate.is_callable() {
5864            return false;
5865        }
5866        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5867            return false;
5868        };
5869        let guards = OnceCell::new();
5870        let context = CallableReferenceContext {
5871            file,
5872            position: Some(CallableReferencePosition {
5873                prepared: prepared.as_ref(),
5874                byte: reference.start_byte(),
5875                guards: &guards,
5876            }),
5877        };
5878        analyzer
5879            .declaration_source_occurrences(candidate)
5880            .into_iter()
5881            .flatten()
5882            .filter_map(|occurrence| {
5883                let guards = cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
5884                Some(callable_guard_requirements_are_visible(
5885                    &guards,
5886                    occurrence.fact.callable_guard_completion_byte,
5887                    &context,
5888                ))
5889            })
5890            .any(|visible| visible)
5891    }
5892
5893    pub fn type_candidate_may_be_visible_before_reference(
5894        &self,
5895        analyzer: &CppGraphSource<'_>,
5896        file: &ProjectFile,
5897        candidate: &CodeUnit,
5898        reference_byte: usize,
5899    ) -> bool {
5900        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5901            return false;
5902        };
5903        let root = prepared.tree().root_node();
5904        let end_byte = reference_byte
5905            .saturating_add(1)
5906            .min(prepared.source().len());
5907        let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
5908            return false;
5909        };
5910        self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
5911    }
5912
5913    pub fn preprocessor_guards_stable_between(
5914        &self,
5915        file: &ProjectFile,
5916        start_byte: usize,
5917        end_byte: usize,
5918        guards: &HashSet<PreprocessorGuard>,
5919    ) -> bool {
5920        if guards.is_empty() || start_byte >= end_byte {
5921            return true;
5922        }
5923        let cell = self.macro_event_cell(file);
5924        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5925        let mut visited = HashSet::from_iter([file.clone()]);
5926        !events.iter().any(|event| {
5927            event.byte() >= start_byte
5928                && event.byte() < end_byte
5929                && self.macro_event_may_mutate_guards(event, guards, &mut visited)
5930        })
5931    }
5932
5933    fn macro_event_may_mutate_guards(
5934        &self,
5935        event: &MacroEvent,
5936        guards: &HashSet<PreprocessorGuard>,
5937        visited: &mut HashSet<ProjectFile>,
5938    ) -> bool {
5939        match event {
5940            MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
5941                guards.iter().any(|guard| guard.may_depend_on_macro(name))
5942            }
5943            MacroEvent::Include { targets, .. } => {
5944                targets.is_empty()
5945                    || targets
5946                        .iter()
5947                        .any(|target| self.source_may_mutate_guards(target, guards, visited))
5948            }
5949            MacroEvent::Invalidate { .. } => true,
5950        }
5951    }
5952
5953    fn source_may_mutate_guards(
5954        &self,
5955        file: &ProjectFile,
5956        guards: &HashSet<PreprocessorGuard>,
5957        visited: &mut HashSet<ProjectFile>,
5958    ) -> bool {
5959        if !visited.insert(file.clone()) {
5960            return false;
5961        }
5962        let cell = self.macro_event_cell(file);
5963        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5964        events
5965            .iter()
5966            .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
5967    }
5968
5969    pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
5970        let normalized = normalize_reference_name(raw_name)?;
5971        self.type_candidates(file, &normalized)
5972            .into_iter()
5973            .next()
5974            .cloned()
5975    }
5976
5977    /// Mirror forward navigation's visible-name fallback for a bare parameter
5978    /// type after lexical owner and inheritance lookup is exhausted.
5979    ///
5980    /// Generated or otherwise unindexed base classes can hide the alias that
5981    /// makes a parameter type valid C++. Accept the fallback only when every
5982    /// include-visible class or alias with that spelling canonicalizes to one
5983    /// logical type. A shadowing local type resolves lexically before this
5984    /// path, while distinct visible types keep the result ambiguous.
5985    pub fn unique_visible_parameter_type_fallback(
5986        &self,
5987        analyzer: &CppGraphSource<'_>,
5988        file: &ProjectFile,
5989        node: Node<'_>,
5990        source: &str,
5991    ) -> Option<CodeUnit> {
5992        if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
5993            return None;
5994        }
5995        let name = node_text(node, source);
5996        let candidates = self
5997            .visible_identifier_candidates(file, name)
5998            .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
5999            .filter(|candidate| {
6000                self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
6001            })
6002            .collect::<Vec<_>>();
6003        self.unique_canonical_type_candidate(analyzer, file, &candidates)
6004    }
6005
6006    pub fn resolve_type_node_result(
6007        &self,
6008        file: &ProjectFile,
6009        node: Node<'_>,
6010        source: &str,
6011    ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
6012        let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
6013            return Ok(None);
6014        };
6015        let Some(arguments) = cpp_template_reference_arguments(node, source) else {
6016            return Ok(Some(primary));
6017        };
6018        self.resolve_template_arguments(file, primary, &arguments)
6019            .map(Some)
6020    }
6021
6022    pub fn resolve_type_node_primary(
6023        &self,
6024        file: &ProjectFile,
6025        node: Node<'_>,
6026        source: &str,
6027    ) -> Option<CodeUnit> {
6028        let components = cpp_type_name_components(node, source)?;
6029        self.resolve_type(file, &components.join("::"))
6030    }
6031
6032    pub fn resolve_template_arguments(
6033        &self,
6034        file: &ProjectFile,
6035        primary: CodeUnit,
6036        arguments: &[CppTemplateExpression],
6037    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6038        self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
6039    }
6040
6041    fn resolve_template_arguments_inner(
6042        &self,
6043        file: &ProjectFile,
6044        primary: CodeUnit,
6045        arguments: &[CppTemplateExpression],
6046        seen_aliases: &mut HashSet<CodeUnit>,
6047    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6048        if let Some(metadata) = self.cpp_template_metadata.get(&primary)
6049            && let Some(alias_target) = &metadata.alias_target
6050        {
6051            if !seen_aliases.insert(primary.clone()) {
6052                return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
6053            }
6054            let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
6055                .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6056            let target_name = alias_target.components.join("::");
6057            let target_primary = if alias_target.global {
6058                unique_logical_type_candidate(self.type_candidates(file, &target_name))
6059            } else {
6060                self.resolve_unique_type_for_declaration(file, &primary, &target_name)
6061            };
6062            let Some(target_primary) = target_primary else {
6063                // A dependent or external RHS cannot be canonicalized from the
6064                // indexed graph. Preserve the alias's direct identity instead
6065                // of inventing a target from its source spelling.
6066                return Ok(primary);
6067            };
6068            let Some(target_arguments) = &alias_target.arguments else {
6069                return Ok(target_primary);
6070            };
6071            let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
6072                .ok_or(CppTemplateResolutionError::Substitution)?;
6073            return self.resolve_template_arguments_inner(
6074                file,
6075                target_primary,
6076                &target_arguments,
6077                seen_aliases,
6078            );
6079        }
6080
6081        let primary_fq_name = self
6082            .cpp_template_metadata
6083            .get(&primary)
6084            .map(|metadata| metadata.primary_fq_name.clone())
6085            .unwrap_or_else(|| primary.fq_name());
6086        let has_specialization_metadata = self
6087            .cpp_template_families
6088            .get(&primary_fq_name)
6089            .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
6090        if !has_specialization_metadata {
6091            return Ok(primary);
6092        }
6093        self.select_template_specialization(file, &primary, arguments)
6094    }
6095
6096    fn select_template_specialization(
6097        &self,
6098        file: &ProjectFile,
6099        resolved: &CodeUnit,
6100        explicit_arguments: &[CppTemplateExpression],
6101    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6102        let primary_fq_name = self
6103            .cpp_template_metadata
6104            .get(resolved)
6105            .map(|metadata| metadata.primary_fq_name.clone())
6106            .unwrap_or_else(|| resolved.fq_name());
6107        let family = self
6108            .cpp_template_families
6109            .get(&primary_fq_name)
6110            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6111        let primary_candidates = family
6112            .iter()
6113            .filter_map(|unit| {
6114                let metadata = self.cpp_template_metadata.get(unit)?;
6115                (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
6116            })
6117            .collect::<Vec<_>>();
6118        let primary_unit = primary_candidates
6119            .iter()
6120            .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
6121            .or_else(|| {
6122                primary_candidates
6123                    .iter()
6124                    .map(|(unit, _)| *unit)
6125                    .min_by_key(|unit| {
6126                        (
6127                            unit.source().to_string(),
6128                            unit.signature().unwrap_or_default(),
6129                        )
6130                    })
6131            })
6132            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6133        let primary_parameters =
6134            cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
6135                .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6136        let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
6137            .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6138
6139        let mut applicable = Vec::new();
6140        for unit in family {
6141            let Some(metadata) = self.cpp_template_metadata.get(unit) else {
6142                continue;
6143            };
6144            if metadata.is_primary() || !self.is_visible(file, unit) {
6145                continue;
6146            }
6147            if !cpp_specialization_matches(metadata, &expanded) {
6148                continue;
6149            }
6150            applicable.push((unit, metadata));
6151        }
6152        if applicable.is_empty() {
6153            return Ok(primary_unit.clone());
6154        }
6155
6156        // A scalar constraint count cannot represent C++ partial ordering:
6157        // e.g. `<T*, U>` and `<T, int>` are incomparable for `<int*, int>`.
6158        // Select only a logical candidate whose structural pattern is strictly
6159        // more specialized than every other distinct applicable candidate.
6160        let winners = applicable
6161            .iter()
6162            .filter(|(candidate, candidate_metadata)| {
6163                applicable.iter().all(|(other, other_metadata)| {
6164                    same_visible_symbol(candidate, other)
6165                        || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
6166                })
6167            })
6168            .copied()
6169            .collect::<Vec<_>>();
6170        let Some((selected, _)) = winners.first() else {
6171            // Mutually incomparable applicable candidates: every one of them
6172            // is a live contender.
6173            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6174                candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
6175            });
6176        };
6177        if winners
6178            .iter()
6179            .any(|(unit, _)| !same_visible_symbol(unit, selected))
6180        {
6181            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6182                candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
6183            });
6184        }
6185        Ok((*selected).clone())
6186    }
6187
6188    pub fn resolve_type_components_lexically(
6189        &self,
6190        analyzer: &CppGraphSource<'_>,
6191        file: &ProjectFile,
6192        components: &[String],
6193        global: bool,
6194        lexical_scope: &[String],
6195    ) -> LexicalTypeResolution {
6196        self.resolve_type_components_lexically_inner(
6197            analyzer,
6198            file,
6199            components,
6200            global,
6201            lexical_scope,
6202            TypeCandidateResolution::Canonical,
6203        )
6204    }
6205
6206    pub fn resolve_type_components_lexically_for_forward(
6207        &self,
6208        analyzer: &CppGraphSource<'_>,
6209        file: &ProjectFile,
6210        components: &[String],
6211        global: bool,
6212        lexical_scope: &[String],
6213    ) -> LexicalTypeResolution {
6214        self.resolve_type_components_lexically_inner(
6215            analyzer,
6216            file,
6217            components,
6218            global,
6219            lexical_scope,
6220            TypeCandidateResolution::PreserveAlias,
6221        )
6222    }
6223
6224    pub fn resolve_type_components_lexically_for_target(
6225        &self,
6226        analyzer: &CppGraphSource<'_>,
6227        file: &ProjectFile,
6228        components: &[String],
6229        global: bool,
6230        lexical_scope: &[String],
6231        target: &CodeUnit,
6232    ) -> LexicalTypeResolution {
6233        #[cfg(any(test, feature = "test-support"))]
6234        self.target_preserving_type_resolution_count
6235            .fetch_add(1, Ordering::Relaxed);
6236        self.resolve_type_components_lexically_inner(
6237            analyzer,
6238            file,
6239            components,
6240            global,
6241            lexical_scope,
6242            TypeCandidateResolution::PreserveTarget(target),
6243        )
6244    }
6245
6246    pub fn coarse_unqualified_type_reference_may_resolve(
6247        &self,
6248        file: &ProjectFile,
6249        name: &str,
6250    ) -> bool {
6251        if name.is_empty() {
6252            return true;
6253        }
6254        self.visible_identifier_candidates(file, name)
6255            .any(|candidate| {
6256                candidate.kind() == CodeUnitType::Class
6257                    || declared_type_alias(&self.cpp_source(), candidate)
6258            })
6259            || self.visible_parser_alias_name_is_visible(file, name)
6260    }
6261
6262    #[allow(clippy::too_many_arguments)]
6263    pub fn structured_type_reference_may_resolve_to_target(
6264        &self,
6265        analyzer: &CppGraphSource<'_>,
6266        file: &ProjectFile,
6267        components: &[String],
6268        global: bool,
6269        lexical_scope: &[String],
6270        target: &CodeUnit,
6271    ) -> bool {
6272        if components.is_empty() {
6273            return true;
6274        }
6275        let Some(terminal) = components.last() else {
6276            return true;
6277        };
6278        let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
6279            .map(|qualified| qualified.join("::"))
6280            .collect::<Vec<_>>();
6281        let target_name = cpp_name_for(target);
6282        if qualified_tiers
6283            .iter()
6284            .any(|qualified| qualified == &target_name)
6285        {
6286            return true;
6287        }
6288
6289        let mut saw_shape_candidate = false;
6290        for candidate in self.visible_identifier_candidates(file, terminal) {
6291            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6292            {
6293                continue;
6294            }
6295            let candidate_name = cpp_name_for(candidate);
6296            let shape_matches = if global || components.len() > 1 {
6297                qualified_tiers
6298                    .iter()
6299                    .any(|qualified| qualified == &candidate_name)
6300            } else {
6301                true
6302            };
6303            if !shape_matches {
6304                continue;
6305            }
6306            saw_shape_candidate = true;
6307            if same_visible_symbol(candidate, target)
6308                || self.c_tag_declaration_family_matches_target(
6309                    analyzer,
6310                    file,
6311                    std::slice::from_ref(&candidate),
6312                    target,
6313                )
6314                || self.compatible_primary_template_redeclarations(candidate, target)
6315                || (declared_type_alias(analyzer, candidate)
6316                    && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
6317            {
6318                return true;
6319            }
6320        }
6321
6322        !saw_shape_candidate
6323    }
6324
6325    /// A C tag's forward declaration and complete definition are one logical
6326    /// type even when their indexed signatures and source files differ. Keep
6327    /// this identity narrow: the complete declaration must be a top-level C
6328    /// tag, every visible candidate must be its physical forward declaration,
6329    /// and the parsed tag kind must agree. Different FQNs, competing complete
6330    /// definitions, aliases, and struct/union mismatches remain ambiguous.
6331    fn cached_c_tag_kind(
6332        &self,
6333        analyzer: &CppGraphSource<'_>,
6334        candidate: &CodeUnit,
6335    ) -> Option<CppCTagKind> {
6336        if let Some(kind) = self
6337            .c_tag_kind_cache
6338            .lock()
6339            .expect("C tag kind cache poisoned")
6340            .get(candidate)
6341        {
6342            return *kind;
6343        }
6344        let kind = indexed_c_tag_kind(analyzer, candidate);
6345        self.c_tag_kind_cache
6346            .lock()
6347            .expect("C tag kind cache poisoned")
6348            .insert(candidate.clone(), kind);
6349        kind
6350    }
6351
6352    fn cached_unique_c_tag_complete_definition(
6353        &self,
6354        analyzer: &CppGraphSource<'_>,
6355        target: &CodeUnit,
6356        target_tag: CppCTagKind,
6357    ) -> Option<CodeUnit> {
6358        if let Some(definition) = self
6359            .c_tag_complete_definition_cache
6360            .lock()
6361            .expect("C tag complete-definition cache poisoned")
6362            .get(target)
6363        {
6364            return definition.clone();
6365        }
6366        let complete_definitions = analyzer
6367            .definitions(&target.fq_name())
6368            .filter(|candidate| {
6369                candidate.is_class()
6370                    && !declared_type_alias(analyzer, candidate)
6371                    && is_c_source_file(candidate.source())
6372                    && analyzer.parent_of(candidate).is_none()
6373                    && cpp_class_declaration_strength(analyzer, candidate)
6374                        == CppClassDeclarationStrength::Full
6375                    && self.cached_c_tag_kind(analyzer, candidate) == Some(target_tag)
6376            })
6377            .collect::<HashSet<_>>();
6378        let definition = (complete_definitions.len() == 1)
6379            .then(|| complete_definitions.into_iter().next())
6380            .flatten()
6381            .filter(|candidate| same_visible_symbol(candidate, target));
6382        self.c_tag_complete_definition_cache
6383            .lock()
6384            .expect("C tag complete-definition cache poisoned")
6385            .insert(target.clone(), definition.clone());
6386        definition
6387    }
6388
6389    pub fn c_tag_declaration_family_matches_target(
6390        &self,
6391        analyzer: &CppGraphSource<'_>,
6392        visible_from: &ProjectFile,
6393        candidates: &[&CodeUnit],
6394        target: &CodeUnit,
6395    ) -> bool {
6396        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
6397            let candidate_evidence = candidates
6398                .iter()
6399                .map(|candidate| {
6400                    (
6401                        candidate.fq_name(),
6402                        candidate.source().rel_path().to_path_buf(),
6403                        cpp_class_declaration_strength(analyzer, candidate),
6404                        indexed_c_tag_kind(analyzer, candidate),
6405                        self.is_physically_visible(visible_from, candidate),
6406                    )
6407                })
6408                .collect::<Vec<_>>();
6409            eprintln!(
6410                "BIFROST_CPP_C_TAG_FAMILY_STATS visible_from={} target=({}, {}, {:?}, {:?}) candidates={candidate_evidence:?}",
6411                visible_from.rel_path().display(),
6412                target.fq_name(),
6413                target.source().rel_path().display(),
6414                cpp_class_declaration_strength(analyzer, target),
6415                indexed_c_tag_kind(analyzer, target),
6416            );
6417        }
6418        if candidates.is_empty()
6419            || !target.is_class()
6420            || declared_type_alias(analyzer, target)
6421            || !is_c_source_file(target.source())
6422            || analyzer.parent_of(target).is_some()
6423            || cpp_class_declaration_strength(analyzer, target) != CppClassDeclarationStrength::Full
6424        {
6425            return false;
6426        }
6427        let Some(target_tag) = self.cached_c_tag_kind(analyzer, target) else {
6428            return false;
6429        };
6430        if self
6431            .cached_unique_c_tag_complete_definition(analyzer, target, target_tag)
6432            .is_none()
6433        {
6434            return false;
6435        }
6436        let mut saw_visible_forward = false;
6437        for candidate in candidates.iter().copied() {
6438            if candidate == target {
6439                continue;
6440            }
6441            if !candidate.is_class()
6442                || declared_type_alias(analyzer, candidate)
6443                || candidate.fq_name() != target.fq_name()
6444                || analyzer.parent_of(candidate).is_some()
6445                || cpp_class_declaration_strength(analyzer, candidate)
6446                    != CppClassDeclarationStrength::Forward
6447                || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6448                || !self.is_physically_visible(visible_from, candidate)
6449            {
6450                return false;
6451            }
6452            saw_visible_forward = true;
6453        }
6454        saw_visible_forward
6455    }
6456
6457    /// Collapse one visible complete C tag and its visible forward declarations
6458    /// before ordinary lexical resolution sees their different signatures as
6459    /// competing types. A second complete definition, a different FQN/tag
6460    /// kind, or an unknown declaration shape remains ambiguous.
6461    fn unique_c_tag_declaration_family(
6462        &self,
6463        analyzer: &CppGraphSource<'_>,
6464        visible_from: &ProjectFile,
6465        candidates: &[&CodeUnit],
6466    ) -> Option<CodeUnit> {
6467        let first = candidates.first()?;
6468        let target_fq_name = first.fq_name();
6469        let target_tag = self.cached_c_tag_kind(analyzer, first)?;
6470        let mut full = None;
6471        let mut saw_forward = false;
6472        for candidate in candidates.iter().copied() {
6473            if !candidate.is_class()
6474                || declared_type_alias(analyzer, candidate)
6475                || candidate.fq_name() != target_fq_name
6476                || analyzer.parent_of(candidate).is_some()
6477                || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6478            {
6479                return None;
6480            }
6481            match cpp_class_declaration_strength(analyzer, candidate) {
6482                CppClassDeclarationStrength::Full
6483                    if is_c_source_file(candidate.source())
6484                        && full.replace(candidate.clone()).is_none() => {}
6485                CppClassDeclarationStrength::Forward
6486                    if self.is_physically_visible(visible_from, candidate) =>
6487                {
6488                    saw_forward = true
6489                }
6490                _ => return None,
6491            }
6492        }
6493        if saw_forward { full } else { None }
6494    }
6495
6496    pub fn target_preserving_reference_namespace(
6497        &self,
6498        analyzer: &CppGraphSource<'_>,
6499        file: &ProjectFile,
6500        identifier: &str,
6501        target: &CodeUnit,
6502    ) -> Option<Vec<String>> {
6503        let mut namespace = None;
6504        for candidate in self.visible_identifier_candidates(file, identifier) {
6505            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6506            {
6507                continue;
6508            }
6509            if !(same_visible_symbol(candidate, target)
6510                || self.compatible_primary_template_redeclarations(candidate, target)
6511                || declared_type_alias(analyzer, candidate)
6512                    && self.structured_alias_primary_preserves_target(
6513                        analyzer, file, candidate, target,
6514                    ))
6515            {
6516                continue;
6517            }
6518            if namespace
6519                .as_ref()
6520                .is_some_and(|existing| existing != candidate.package_name())
6521            {
6522                return None;
6523            }
6524            namespace = Some(candidate.package_name().to_string());
6525        }
6526        let namespace = namespace?;
6527        Some(
6528            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6529                brokk_bifrost_core::analyzer::Language::Cpp,
6530                &namespace,
6531            ),
6532        )
6533    }
6534
6535    pub fn resolve_imported_type_candidate(
6536        &self,
6537        analyzer: &CppGraphSource<'_>,
6538        file: &ProjectFile,
6539        target: &CodeUnit,
6540        target_components: &[String],
6541        direct_target: Option<&CodeUnit>,
6542        preserve_alias: bool,
6543    ) -> LexicalTypeResolution {
6544        let candidates = [target];
6545        let resolution = if preserve_alias {
6546            TypeCandidateResolution::PreserveAlias
6547        } else {
6548            direct_target.map_or(
6549                TypeCandidateResolution::Canonical,
6550                TypeCandidateResolution::PreserveTarget,
6551            )
6552        };
6553        // One candidate goes in, so a failure here is never "choose one of
6554        // these": it is the alias chain leaving the index, which must answer
6555        // missing rather than ambiguous (#1828).
6556        match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6557            Ok(unit) => LexicalTypeResolution::Resolved {
6558                unit,
6559                components: target_components.to_vec(),
6560                candidates: vec![target.clone()],
6561            },
6562            Err(failure) => failure.lexical_resolution(),
6563        }
6564    }
6565
6566    fn resolve_type_components_lexically_inner(
6567        &self,
6568        analyzer: &CppGraphSource<'_>,
6569        file: &ProjectFile,
6570        components: &[String],
6571        global: bool,
6572        lexical_scope: &[String],
6573        resolution: TypeCandidateResolution<'_>,
6574    ) -> LexicalTypeResolution {
6575        if components.is_empty() {
6576            return LexicalTypeResolution::Missing;
6577        }
6578        // A C++ class injects its own name into the class scope.  The indexed
6579        // FqName for that declaration is the class path itself (for example,
6580        // `n::raw_hash_set`), not a synthetic child named
6581        // `n::raw_hash_set::raw_hash_set`.  Ordinary lexical tiers append the
6582        // requested identifier to every scope component, so they cannot
6583        // represent that injected binding when the enclosing class is the
6584        // closest scope.  Recover the binding from the structured class path
6585        // before allowing lookup to fall through to an outer same-spelled
6586        // declaration.
6587        let mut injected = self.resolve_injected_class_name(
6588            analyzer,
6589            file,
6590            components,
6591            global,
6592            lexical_scope,
6593            resolution,
6594        );
6595        for qualified in lexical_component_tiers(components, global, lexical_scope) {
6596            let prefix_len = qualified.len().saturating_sub(components.len());
6597            if injected
6598                .as_ref()
6599                .is_some_and(|(owner_len, _)| prefix_len <= *owner_len)
6600            {
6601                return injected
6602                    .take()
6603                    .expect("injected class resolution was just present")
6604                    .1;
6605            }
6606            let qualified_name = qualified.join("::");
6607            let candidates = self
6608                .type_candidates(file, &qualified_name)
6609                .into_iter()
6610                .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6611                .collect::<Vec<_>>();
6612            if candidates.is_empty() {
6613                if !global && components.len() == 1 {
6614                    match self.resolve_inherited_type_for_lexical_scope(
6615                        analyzer,
6616                        file,
6617                        &qualified[..prefix_len],
6618                        &components[0],
6619                        resolution,
6620                    ) {
6621                        LexicalTypeResolution::Missing => {}
6622                        inherited => return inherited,
6623                    }
6624                }
6625                continue;
6626            }
6627            let candidates =
6628                self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6629            let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6630                Ok(unit) => unit,
6631                Err(failure) => return failure.lexical_resolution(),
6632            };
6633            return LexicalTypeResolution::Resolved {
6634                unit,
6635                components: qualified,
6636                candidates: candidates.into_iter().cloned().collect(),
6637            };
6638        }
6639        LexicalTypeResolution::Missing
6640    }
6641
6642    fn resolve_injected_class_name(
6643        &self,
6644        analyzer: &CppGraphSource<'_>,
6645        file: &ProjectFile,
6646        components: &[String],
6647        global: bool,
6648        lexical_scope: &[String],
6649        resolution: TypeCandidateResolution<'_>,
6650    ) -> Option<(usize, LexicalTypeResolution)> {
6651        if global
6652            || components.len() != 1
6653            || file.rel_path().extension().is_some_and(|ext| ext == "c")
6654            || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
6655        {
6656            return None;
6657        }
6658        let name = components.first()?;
6659        let mut matches: Vec<&CodeUnit> = Vec::new();
6660        let mut owner_len = 0;
6661        for candidate in self.visible_identifier_candidates(file, name) {
6662            if !candidate.is_class()
6663                || declared_type_alias(analyzer, candidate)
6664                || candidate.identifier() != name
6665            {
6666                continue;
6667            }
6668            let candidate_scope = canonical_cpp_scope_components(candidate);
6669            if candidate_scope.len() > lexical_scope.len()
6670                || !lexical_scope.starts_with(&candidate_scope)
6671                || candidate_scope.last().is_none_or(|last| last != name)
6672            {
6673                continue;
6674            }
6675            if candidate_scope.len() > owner_len {
6676                owner_len = candidate_scope.len();
6677                matches.clear();
6678            }
6679            if candidate_scope.len() == owner_len
6680                && !matches
6681                    .iter()
6682                    .any(|existing| same_logical_symbol(existing, candidate))
6683            {
6684                matches.push(candidate);
6685            }
6686        }
6687        if matches.is_empty() {
6688            return None;
6689        }
6690        // The exact current class boundary is the most important injected-name
6691        // case. Ordinary lexical lookup appends the requested name and can
6692        // otherwise select a synthetic constructor-shaped child such as
6693        // `Portal::Impl::Impl` instead of the enclosing `Portal::Impl` class.
6694        // The global and single-component gates above keep qualified receiver
6695        // and static-qualifier contexts out of this recovery.
6696        let owner_components = lexical_scope[..owner_len].to_vec();
6697        let matches = self.candidates_for_type_resolution(analyzer, file, &matches, resolution);
6698        let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
6699            Ok(unit) => LexicalTypeResolution::Resolved {
6700                unit,
6701                components: owner_components,
6702                candidates: matches.into_iter().cloned().collect(),
6703            },
6704            Err(failure) => failure.lexical_resolution(),
6705        };
6706        Some((owner_len, resolution))
6707    }
6708
6709    fn resolve_inherited_type_for_lexical_scope(
6710        &self,
6711        analyzer: &CppGraphSource<'_>,
6712        file: &ProjectFile,
6713        lexical_scope: &[String],
6714        name: &str,
6715        resolution: TypeCandidateResolution<'_>,
6716    ) -> LexicalTypeResolution {
6717        let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
6718            return LexicalTypeResolution::Missing;
6719        };
6720        let lexical_owner_name = lexical_scope.join("::");
6721        if lexical_owner_name.is_empty() {
6722            return LexicalTypeResolution::Missing;
6723        }
6724        let owner_candidates = self
6725            .type_candidates(file, &lexical_owner_name)
6726            .into_iter()
6727            .filter(|candidate| {
6728                canonical_cpp_name_matches(candidate, &lexical_owner_name)
6729                    && !declared_type_alias(analyzer, candidate)
6730            })
6731            .collect::<Vec<_>>();
6732        if owner_candidates.is_empty() {
6733            return LexicalTypeResolution::Missing;
6734        }
6735        // A visible forward declaration and the physical class definition share
6736        // one FQN, but only the definition owns hierarchy facts. When lookup is
6737        // physically inside that definition, do not let an earlier header
6738        // forward declaration erase its base edges (#2240).
6739        let physical_owner_candidates = owner_candidates
6740            .iter()
6741            .copied()
6742            .filter(|candidate| candidate.source() == file)
6743            .collect::<Vec<_>>();
6744        let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
6745            owner_candidates
6746        } else {
6747            physical_owner_candidates
6748        };
6749        let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
6750            return LexicalTypeResolution::Ambiguous;
6751        };
6752
6753        let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
6754        let mut visited_owners = HashSet::default();
6755        while !frontier.is_empty() {
6756            let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
6757            let mut next_frontier = Vec::new();
6758            for owner in frontier {
6759                if !visited_owners.insert(owner.fq_name()) {
6760                    continue;
6761                }
6762                let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
6763                let candidates = self
6764                    .type_candidates(file, &qualified_name)
6765                    .into_iter()
6766                    .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6767                    .collect::<Vec<_>>();
6768                if candidates.is_empty() {
6769                    for ancestor in hierarchy.get_direct_ancestors(&owner) {
6770                        if !next_frontier
6771                            .iter()
6772                            .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
6773                        {
6774                            next_frontier.push(ancestor);
6775                        }
6776                    }
6777                    continue;
6778                }
6779                let candidates =
6780                    self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6781                let unit =
6782                    match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6783                        Ok(unit) => unit,
6784                        Err(failure) => return failure.lexical_resolution(),
6785                    };
6786                level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
6787            }
6788            if let Some((unit, candidates)) = level_matches.first().cloned() {
6789                let Some(first_declaration) = candidates.first() else {
6790                    return LexicalTypeResolution::Ambiguous;
6791                };
6792                if !level_matches.iter().all(|(_, declarations)| {
6793                    declarations
6794                        .iter()
6795                        .all(|declaration| same_logical_symbol(first_declaration, declaration))
6796                }) {
6797                    return LexicalTypeResolution::Ambiguous;
6798                }
6799                let mut components = lexical_scope.to_vec();
6800                components.push(name.to_string());
6801                return LexicalTypeResolution::Resolved {
6802                    unit,
6803                    components,
6804                    candidates,
6805                };
6806            }
6807            frontier = next_frontier;
6808        }
6809        LexicalTypeResolution::Missing
6810    }
6811
6812    /// Resolve a base class through its injected class name at the nearest
6813    /// inheritance tier. Distinct same-named bases at that tier are ambiguous.
6814    ///
6815    /// A base whose canonical full definition cannot be pinned from `file` -
6816    /// a forward declaration the include closure completes with two different
6817    /// full definitions, or an alias chain that leaves the index - stops the
6818    /// walk only when that base is spelled `injected_name`. The mem-initializer
6819    /// names a base by that base's own injected class name, so a base spelled
6820    /// differently can never be the one it names, whichever definition it would
6821    /// have turned out to be; aborting the level on its account instead loses
6822    /// the sibling base that *is* named (#2543). A base that is spelled
6823    /// `injected_name` still fails closed, because choosing a deeper same-named
6824    /// ancestor over it would bind the initializer to the wrong constructor.
6825    /// The skipped base carries its own ancestors out of the walk with it: with
6826    /// no canonical unit, the repeated-base accounting below cannot tell one
6827    /// inherited path through it from two.
6828    pub fn inherited_injected_class_owner(
6829        &self,
6830        analyzer: &CppGraphSource<'_>,
6831        file: &ProjectFile,
6832        enclosing_owner: &CodeUnit,
6833        injected_name: &str,
6834    ) -> Option<CodeUnit> {
6835        let hierarchy = analyzer.type_hierarchy_provider()?;
6836        let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
6837        let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
6838        while !frontier.is_empty() {
6839            let mut level_matches = Vec::new();
6840            let mut next_frontier = Vec::new();
6841            for raw_owner in frontier {
6842                let Some(owner) = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
6843                else {
6844                    if raw_owner.identifier() == injected_name {
6845                        return None;
6846                    }
6847                    continue;
6848                };
6849                let propagated = propagated_counts.entry(owner.clone()).or_default();
6850                if *propagated == 2 {
6851                    continue;
6852                }
6853                *propagated += 1;
6854                if owner.identifier() == injected_name {
6855                    level_matches.push(owner.clone());
6856                }
6857                next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
6858            }
6859            match level_matches.as_slice() {
6860                [owner] => return Some(owner.clone()),
6861                [_, ..] => return None,
6862                [] => {}
6863            }
6864            frontier = next_frontier;
6865        }
6866        None
6867    }
6868
6869    /// The one type the candidates name under `resolution`, or why they do not
6870    /// name one. The two preserving modes only ever reject candidates that
6871    /// disagree with each other, which is ambiguity; canonicalization can also
6872    /// fail because the alias chain leaves the index (#1828).
6873    fn resolve_type_candidates(
6874        &self,
6875        analyzer: &CppGraphSource<'_>,
6876        file: &ProjectFile,
6877        candidates: &[&CodeUnit],
6878        resolution: TypeCandidateResolution<'_>,
6879    ) -> Result<CodeUnit, TypeCandidateFailure> {
6880        if !matches!(resolution, TypeCandidateResolution::PreserveTarget(_))
6881            && let Some(unit) = self.unique_c_tag_declaration_family(analyzer, file, candidates)
6882        {
6883            return Ok(unit);
6884        }
6885        match resolution {
6886            TypeCandidateResolution::Canonical => {
6887                self.canonical_type_candidate_resolution(analyzer, file, candidates)
6888            }
6889            TypeCandidateResolution::PreserveAlias => {
6890                // A generated index can retain identical alias spellings from
6891                // mutually exclusive headers. When the reference file
6892                // physically reaches exactly one of those source declarations,
6893                // include closure is the structured evidence that selects it;
6894                // treating the two source spellings as an overload set makes a
6895                // reachable alias appear ambiguous (#1844).
6896                let same_fqn_alias_family = candidates.len() > 1
6897                    && candidates.iter().all(|candidate| {
6898                        declared_type_alias(analyzer, candidate)
6899                            && same_logical_symbol(candidates[0], candidate)
6900                    })
6901                    && candidates
6902                        .iter()
6903                        .any(|candidate| candidate.source() != candidates[0].source());
6904                if same_fqn_alias_family {
6905                    let physically_visible = candidates
6906                        .iter()
6907                        .copied()
6908                        .filter(|candidate| self.is_physically_visible(file, candidate))
6909                        .collect::<Vec<_>>();
6910                    // The family is one logical declaration only when the
6911                    // reachable spellings agree. Two same-FQN aliases whose
6912                    // written targets differ (`using Choice = Canonical;` in
6913                    // one header, `using Choice = ::Canonical;` in another)
6914                    // are a genuine conflict, and choosing the first indexed
6915                    // one silently binds the reference to an arbitrary owner
6916                    // (#2398). Collapse only a single reachable declaration
6917                    // or reachable declarations with one structured target;
6918                    // everything else stays ambiguous below.
6919                    if let Some(alias) = select_alias_spelling_with_agreed_targets(
6920                        &physically_visible,
6921                        |candidate| self.structured_alias_target(analyzer, candidate),
6922                    ) {
6923                        return Ok(alias.clone());
6924                    }
6925                }
6926                unique_type_candidate_preserving_alias(analyzer, file, candidates)
6927                    .ok_or(TypeCandidateFailure::Ambiguous)
6928            }
6929            TypeCandidateResolution::PreserveTarget(target) => self
6930                .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
6931                .ok_or(TypeCandidateFailure::Ambiguous),
6932        }
6933    }
6934
6935    fn candidates_for_type_resolution<'b>(
6936        &self,
6937        analyzer: &CppGraphSource<'_>,
6938        file: &ProjectFile,
6939        candidates: &[&'b CodeUnit],
6940        resolution: TypeCandidateResolution<'_>,
6941    ) -> Vec<&'b CodeUnit> {
6942        if matches!(resolution, TypeCandidateResolution::PreserveAlias) && candidates.len() > 1 {
6943            let compile_proven = self.compile_proven_type_candidates(analyzer, file, candidates);
6944            if compile_proven.len() == 1 {
6945                return compile_proven;
6946            }
6947        }
6948        candidates.to_vec()
6949    }
6950
6951    /// Narrow a same-name forward lookup to the declaration selected by the
6952    /// translation unit's compile command. The lexical resolver intentionally
6953    /// does not receive a reference node, so this is the only compile-context
6954    /// evidence available at that stage. A single selected candidate is safe:
6955    /// the caller still checks include activation and the reference's own
6956    /// guards before reporting the result as visible.
6957    fn compile_proven_type_candidates<'b>(
6958        &self,
6959        analyzer: &CppGraphSource<'_>,
6960        file: &ProjectFile,
6961        candidates: &[&'b CodeUnit],
6962    ) -> Vec<&'b CodeUnit> {
6963        let proven = self.compile_proven_guards(file);
6964        if proven.is_empty() {
6965            return Vec::new();
6966        }
6967        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6968            return Vec::new();
6969        };
6970        candidates
6971            .iter()
6972            .copied()
6973            .filter(|candidate| {
6974                let declaration_guards =
6975                    declaration_guard_requirements(analyzer, self.cpp, candidate);
6976                if declaration_guards.is_empty() {
6977                    return false;
6978                }
6979                if candidate.source() == file {
6980                    return declaration_guards.iter().any(|(_, required)| {
6981                        guard_requirements_hold_at_reference(required, Some(proven.as_ref()))
6982                    });
6983                }
6984                declaration_guards.iter().any(|(_, required)| {
6985                    self.foreign_declaration_reachable_from_compile_proven_guards(
6986                        file,
6987                        prepared.as_ref(),
6988                        candidate.source(),
6989                        required,
6990                        usize::MAX,
6991                    )
6992                })
6993            })
6994            .collect()
6995    }
6996
6997    pub fn resolve_callable_value_components_lexically(
6998        &self,
6999        analyzer: &CppGraphSource<'_>,
7000        file: &ProjectFile,
7001        owner_components: &[String],
7002        member_name: &str,
7003        global: bool,
7004        lexical_scope: &[String],
7005    ) -> LexicalCallableValueResolution {
7006        if owner_components.is_empty() || member_name.is_empty() {
7007            return LexicalCallableValueResolution::Missing;
7008        }
7009        for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
7010            let owner_name = qualified_owner.join("::");
7011            let type_candidates = self
7012                .type_candidates(file, &owner_name)
7013                .into_iter()
7014                .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
7015                .collect::<Vec<_>>();
7016            let resolved_type = if type_candidates.is_empty() {
7017                None
7018            } else {
7019                let Some(unit) =
7020                    self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
7021                else {
7022                    return LexicalCallableValueResolution::Ambiguous;
7023                };
7024                Some(unit)
7025            };
7026
7027            let mut qualified_callable = qualified_owner;
7028            qualified_callable.push(member_name.to_string());
7029            let callable_name = qualified_callable.join("::");
7030            let free_function = self
7031                .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
7032                .into_iter()
7033                .find(|candidate| {
7034                    canonical_cpp_name_matches(candidate, &callable_name)
7035                        && type_owner_of(analyzer, candidate).is_none()
7036                })
7037                .cloned();
7038
7039            match (resolved_type, free_function) {
7040                (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
7041                (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
7042                (None, Some(function)) => {
7043                    return LexicalCallableValueResolution::FreeFunction(function);
7044                }
7045                (None, None) => {}
7046            }
7047        }
7048        LexicalCallableValueResolution::Missing
7049    }
7050
7051    fn resolve_type_for_declaration(
7052        &self,
7053        visible_from: &ProjectFile,
7054        declaration: &CodeUnit,
7055        raw_name: &str,
7056    ) -> Option<CodeUnit> {
7057        let normalized = normalize_reference_name(raw_name)?;
7058        if !normalized.contains("::")
7059            && let Some(namespace) = cpp_namespace_for(declaration)
7060        {
7061            for prefix in namespace_prefixes(&namespace) {
7062                let qualified = format!("{prefix}::{normalized}");
7063                if let Some(unit) = self
7064                    .type_candidates(visible_from, &qualified)
7065                    .into_iter()
7066                    .next()
7067                {
7068                    return Some(unit.clone());
7069                }
7070            }
7071        }
7072        self.resolve_type(visible_from, raw_name)
7073    }
7074
7075    fn resolve_unique_canonical_type_for_declaration(
7076        &self,
7077        analyzer: &CppGraphSource<'_>,
7078        visible_from: &ProjectFile,
7079        declaration: &CodeUnit,
7080        raw_name: &str,
7081    ) -> Option<CodeUnit> {
7082        let current = self.resolve_defining_type_for_declaration(
7083            analyzer,
7084            visible_from,
7085            declaration,
7086            raw_name,
7087        )?;
7088        self.canonical_type_resolution(analyzer, visible_from, &current)
7089            .ok()
7090    }
7091
7092    pub fn canonical_type_unit(
7093        &self,
7094        analyzer: &CppGraphSource<'_>,
7095        visible_from: &ProjectFile,
7096        unit: &CodeUnit,
7097    ) -> Option<CodeUnit> {
7098        self.canonical_type_resolution(analyzer, visible_from, unit)
7099            .ok()
7100    }
7101
7102    /// Follow an alias only when it is visible at `reference`.
7103    ///
7104    /// The consumer need not spell the alias target. In particular, a
7105    /// conditional include can make `PublicPtr` visible at the reference while
7106    /// ordinary physical-include visibility is false. Prove the public alias
7107    /// with the reference's guard environment, then use the existing structured
7108    /// alias-chain resolver over the consumer's bounded declaration index.
7109    pub fn canonical_type_unit_in_context(
7110        &self,
7111        analyzer: &CppGraphSource<'_>,
7112        visible_from: &ProjectFile,
7113        reference: Node<'_>,
7114        unit: &CodeUnit,
7115    ) -> Option<CodeUnit> {
7116        if !self.external_type_candidate_visible_in_context(analyzer, visible_from, unit, reference)
7117        {
7118            return None;
7119        }
7120        self.canonical_type_resolution(analyzer, visible_from, unit)
7121            .ok()
7122    }
7123
7124    /// Follow `unit`'s alias chain to the class it names, or report why the
7125    /// chain does not end at one indexed class.
7126    ///
7127    /// A chain that leaves the index - an alias to a template parameter, to a
7128    /// standard-library type, or to any other declaration the workspace does
7129    /// not hold - is `Unresolvable`, not `Ambiguous` (#1828). So is a cycle:
7130    /// there is still nothing to choose between.
7131    fn canonical_type_resolution(
7132        &self,
7133        analyzer: &CppGraphSource<'_>,
7134        visible_from: &ProjectFile,
7135        unit: &CodeUnit,
7136    ) -> Result<CodeUnit, TypeCandidateFailure> {
7137        let mut current = unit.clone();
7138        let mut seen_aliases = HashSet::default();
7139        loop {
7140            let Some(target) = self.structured_alias_target(analyzer, &current) else {
7141                return current
7142                    .is_class()
7143                    .then_some(current)
7144                    .ok_or(TypeCandidateFailure::Unresolvable);
7145            };
7146            if matches!(target, StructuredAliasTarget::Builtin) {
7147                return current
7148                    .is_class()
7149                    .then_some(current)
7150                    .ok_or(TypeCandidateFailure::Unresolvable);
7151            }
7152            if !seen_aliases.insert(current.clone()) {
7153                return Err(TypeCandidateFailure::Unresolvable);
7154            }
7155            current = self.structured_alias_target_resolution(visible_from, &current, &target)?;
7156        }
7157    }
7158
7159    pub fn canonical_visible_full_type_unit(
7160        &self,
7161        analyzer: &CppGraphSource<'_>,
7162        visible_from: &ProjectFile,
7163        unit: &CodeUnit,
7164    ) -> Option<CodeUnit> {
7165        let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
7166        if cpp_class_declaration_strength(analyzer, &canonical)
7167            != CppClassDeclarationStrength::Forward
7168        {
7169            return Some(canonical);
7170        }
7171        let mut full = Vec::new();
7172        for candidate in self
7173            .visible_identifier_candidates(visible_from, canonical.identifier())
7174            .filter(|candidate| {
7175                candidate.is_class()
7176                    && candidate.fq_name() == canonical.fq_name()
7177                    && cpp_class_declaration_strength(analyzer, candidate)
7178                        == CppClassDeclarationStrength::Full
7179            })
7180        {
7181            if !full.iter().any(|existing| same_symbol(existing, candidate)) {
7182                full.push(candidate.clone());
7183            }
7184        }
7185        match full.len() {
7186            0 => Some(canonical),
7187            1 => full.pop(),
7188            _ => None,
7189        }
7190    }
7191
7192    fn resolve_structured_alias_target(
7193        &self,
7194        visible_from: &ProjectFile,
7195        declaration: &CodeUnit,
7196        target: &StructuredAliasTarget,
7197    ) -> Option<CodeUnit> {
7198        self.structured_alias_target_resolution(visible_from, declaration, target)
7199            .ok()
7200    }
7201
7202    fn structured_alias_target_resolution(
7203        &self,
7204        visible_from: &ProjectFile,
7205        declaration: &CodeUnit,
7206        target: &StructuredAliasTarget,
7207    ) -> Result<CodeUnit, TypeCandidateFailure> {
7208        let primary =
7209            self.structured_alias_primary_resolution(visible_from, declaration, target)?;
7210        let StructuredAliasTarget::Named { arguments, .. } = target else {
7211            return Err(TypeCandidateFailure::Unresolvable);
7212        };
7213        match arguments {
7214            Some(arguments) => self
7215                .resolve_template_arguments(visible_from, primary, arguments)
7216                .map_err(|error| match error {
7217                    CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
7218                        TypeCandidateFailure::Ambiguous
7219                    }
7220                    _ => TypeCandidateFailure::Unresolvable,
7221                }),
7222            None => Ok(primary),
7223        }
7224    }
7225
7226    fn resolve_structured_alias_primary(
7227        &self,
7228        visible_from: &ProjectFile,
7229        declaration: &CodeUnit,
7230        target: &StructuredAliasTarget,
7231    ) -> Option<CodeUnit> {
7232        self.structured_alias_primary_resolution(visible_from, declaration, target)
7233            .ok()
7234    }
7235
7236    fn structured_alias_primary_resolution(
7237        &self,
7238        visible_from: &ProjectFile,
7239        declaration: &CodeUnit,
7240        target: &StructuredAliasTarget,
7241    ) -> Result<CodeUnit, TypeCandidateFailure> {
7242        let StructuredAliasTarget::Named {
7243            components, global, ..
7244        } = target
7245        else {
7246            return Err(TypeCandidateFailure::Unresolvable);
7247        };
7248        let qualified = components.join("::");
7249        let candidates = if *global {
7250            // `::A::B` anchors at the root scope, so a candidate whose
7251            // canonical path merely ends with the spelled components does not
7252            // qualify. Without this filter a global `::Canonical` target also
7253            // collects `alpha::Canonical`, the lookup reports a false
7254            // ambiguity, and the alias arm silently drops out of its
7255            // conflicting family instead of proving the conflict (#2398).
7256            let mut candidates = self.type_candidates(visible_from, &qualified);
7257            candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
7258            candidates
7259        } else {
7260            self.type_candidates_for_declaration(visible_from, declaration, &qualified)
7261        };
7262        logical_type_candidate(candidates)
7263    }
7264
7265    pub fn structured_alias_primary_preserves_target(
7266        &self,
7267        analyzer: &CppGraphSource<'_>,
7268        visible_from: &ProjectFile,
7269        candidate: &CodeUnit,
7270        target: &CodeUnit,
7271    ) -> bool {
7272        let mut current = candidate.clone();
7273        let mut seen = HashSet::default();
7274        let mut matched_target = false;
7275        loop {
7276            if same_visible_symbol(&current, target)
7277                || self.compatible_primary_template_redeclarations(&current, target)
7278            {
7279                matched_target = true;
7280            }
7281            if !seen.insert(current.clone()) {
7282                return false;
7283            }
7284            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
7285                return matched_target;
7286            };
7287            if matches!(alias_target, StructuredAliasTarget::Builtin) {
7288                return matched_target;
7289            };
7290            let Some(primary) =
7291                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
7292            else {
7293                // A dependent member target such as `Detector<T>::type`
7294                // cannot be reduced to an indexed primary, but a preceding
7295                // structured alias hop may already have proven the requested
7296                // alias identity. Cycles still resolve a primary and are
7297                // rejected by `seen` above.
7298                return matched_target;
7299            };
7300            current = primary;
7301        }
7302    }
7303
7304    fn lexical_type_candidates_match_target(
7305        &self,
7306        analyzer: &CppGraphSource<'_>,
7307        unit: &CodeUnit,
7308        candidates: &[CodeUnit],
7309        target: &CodeUnit,
7310    ) -> bool {
7311        same_visible_symbol(unit, target)
7312            || self.same_template_member_identity(analyzer, unit, target)
7313            || candidates.iter().any(|candidate| {
7314                same_visible_symbol(candidate, target)
7315                    || self.same_template_member_identity(analyzer, candidate, target)
7316            })
7317    }
7318
7319    pub fn structured_class_alias_resolves_to_target(
7320        &self,
7321        analyzer: &CppGraphSource<'_>,
7322        visible_from: &ProjectFile,
7323        alias: &CodeUnit,
7324        target: &CodeUnit,
7325    ) -> bool {
7326        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7327            return false;
7328        };
7329        let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
7330            return false;
7331        };
7332        let StructuredAliasTarget::Named {
7333            components, global, ..
7334        } = &alias_target
7335        else {
7336            return false;
7337        };
7338        let lexical_scope = canonical_cpp_scope_components(&owner);
7339        match self.resolve_type_components_lexically_for_target(
7340            analyzer,
7341            visible_from,
7342            components,
7343            *global,
7344            &lexical_scope,
7345            target,
7346        ) {
7347            LexicalTypeResolution::Resolved {
7348                unit, candidates, ..
7349            } => self.lexical_type_candidates_match_target(analyzer, &unit, &candidates, target),
7350            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7351                self.structured_alias_primary_preserves_target(
7352                    analyzer,
7353                    visible_from,
7354                    alias,
7355                    target,
7356                ) || self.flattened_macro_namespace_alias_target_matches(
7357                    analyzer,
7358                    visible_from,
7359                    alias,
7360                    &alias_target,
7361                    target,
7362                )
7363            }
7364        }
7365    }
7366
7367    /// Return true when a class-owned alias names the requested type as one
7368    /// structured qualifier in its target path.
7369    ///
7370    /// A dependent target such as `Primary<T>::Type` cannot resolve to one
7371    /// indexed class. Forward lookup can still retain `Primary` as its bounded
7372    /// canonical identity. Inverse lookup needs the same evidence when later
7373    /// references use only the alias spelling.
7374    pub fn structured_class_alias_path_preserves_target(
7375        &self,
7376        analyzer: &CppGraphSource<'_>,
7377        visible_from: &ProjectFile,
7378        alias: &CodeUnit,
7379        target: &CodeUnit,
7380    ) -> bool {
7381        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7382            return false;
7383        };
7384        let Some(StructuredAliasTarget::Named {
7385            components, global, ..
7386        }) = self.structured_alias_target(analyzer, alias)
7387        else {
7388            return false;
7389        };
7390        let lexical_scope = canonical_cpp_scope_components(&owner);
7391        (1..components.len()).rev().any(|component_count| {
7392            matches!(
7393                self.resolve_type_components_lexically_for_target(
7394                    analyzer,
7395                    visible_from,
7396                    &components[..component_count],
7397                    global,
7398                    &lexical_scope,
7399                    target,
7400                ),
7401                LexicalTypeResolution::Resolved {
7402                    ref unit,
7403                    ref candidates,
7404                    ..
7405                } if self.lexical_type_candidates_match_target(
7406                    analyzer,
7407                    unit,
7408                    candidates,
7409                    target,
7410                )
7411            )
7412        })
7413    }
7414
7415    fn flattened_macro_namespace_alias_target_matches(
7416        &self,
7417        analyzer: &CppGraphSource<'_>,
7418        visible_from: &ProjectFile,
7419        alias: &CodeUnit,
7420        alias_target: &StructuredAliasTarget,
7421        target: &CodeUnit,
7422    ) -> bool {
7423        let StructuredAliasTarget::Named {
7424            components,
7425            global: false,
7426            arguments: None,
7427        } = alias_target
7428        else {
7429            return false;
7430        };
7431        let Some((target_name, namespace_components)) = components.split_last() else {
7432            return false;
7433        };
7434        if namespace_components.is_empty()
7435            || target_name != target.identifier()
7436            || alias.source() != target.source()
7437            || alias.source() != visible_from
7438            || !target.is_class()
7439            || declared_type_alias(analyzer, target)
7440        {
7441            return false;
7442        }
7443        if self
7444            .resolve_structured_alias_target(visible_from, alias, alias_target)
7445            .is_some()
7446        {
7447            return false;
7448        }
7449
7450        let Some(alias_start) = analyzer
7451            .declaration_source_occurrences(alias)
7452            .into_iter()
7453            .flatten()
7454            .map(|occurrence| occurrence.range.start_byte)
7455            .min()
7456        else {
7457            return false;
7458        };
7459        let has_matching_declaration = analyzer
7460            .declaration_source_occurrences(target)
7461            .into_iter()
7462            .flatten()
7463            .any(|occurrence| {
7464                occurrence.range.end_byte <= alias_start
7465                    && occurrence.fact.flattened_macro_namespace.as_deref()
7466                        == Some(namespace_components)
7467            });
7468        if !has_matching_declaration {
7469            return false;
7470        }
7471
7472        let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
7473        let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
7474        guard_requirement_sets_match(&alias_guards, &target_guards)
7475    }
7476
7477    pub fn template_alias_arguments_preserve_target(
7478        &self,
7479        analyzer: &CppGraphSource<'_>,
7480        visible_from: &ProjectFile,
7481        alias: &CodeUnit,
7482        arguments: &[CppTemplateExpression],
7483        target: &CodeUnit,
7484    ) -> bool {
7485        let Some(metadata) = self.cpp_template_metadata.get(alias) else {
7486            return false;
7487        };
7488        if metadata.alias_target.is_none()
7489            || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
7490        {
7491            return false;
7492        }
7493        self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
7494    }
7495
7496    pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
7497        self.cpp_template_metadata
7498            .get(unit)
7499            .is_some_and(CppTemplateMetadata::is_primary)
7500    }
7501
7502    pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
7503        self.cpp_template_metadata
7504            .get(unit)
7505            .is_some_and(CppTemplateMetadata::is_specialization)
7506    }
7507
7508    pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
7509        same_visible_symbol(left, right)
7510            || self.compatible_primary_template_redeclarations(left, right)
7511    }
7512
7513    pub fn same_template_member_identity(
7514        &self,
7515        analyzer: &CppGraphSource<'_>,
7516        left: &CodeUnit,
7517        right: &CodeUnit,
7518    ) -> bool {
7519        if same_visible_symbol(left, right) {
7520            return true;
7521        }
7522        if left.kind() != right.kind()
7523            || left.identifier() != right.identifier()
7524            || left.signature() != right.signature()
7525        {
7526            return false;
7527        }
7528        let (Some(left_owner), Some(right_owner)) =
7529            (analyzer.parent_of(left), analyzer.parent_of(right))
7530        else {
7531            return false;
7532        };
7533        left_owner.is_class()
7534            && right_owner.is_class()
7535            && self.same_template_owner_identity(&left_owner, &right_owner)
7536    }
7537
7538    fn unique_canonical_type_candidate(
7539        &self,
7540        analyzer: &CppGraphSource<'_>,
7541        visible_from: &ProjectFile,
7542        candidates: &[&CodeUnit],
7543    ) -> Option<CodeUnit> {
7544        self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
7545            .ok()
7546    }
7547
7548    fn canonical_type_candidate_resolution(
7549        &self,
7550        analyzer: &CppGraphSource<'_>,
7551        visible_from: &ProjectFile,
7552        candidates: &[&CodeUnit],
7553    ) -> Result<CodeUnit, TypeCandidateFailure> {
7554        let mut canonical = Vec::new();
7555        for candidate in candidates {
7556            let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
7557            if canonical
7558                .iter()
7559                .any(|existing| same_visible_symbol(existing, &resolved))
7560            {
7561                continue;
7562            }
7563            if let Some(existing) = canonical.iter_mut().find(|existing| {
7564                self.compatible_primary_template_redeclarations(existing, &resolved)
7565            }) {
7566                // A forward declaration and its full primary-template
7567                // definition are one C++ type even when they live in
7568                // different headers and alpha-rename their parameters. The
7569                // target-preserving path already reconciles this family; do
7570                // the same for ordinary canonical lookup so an out-of-line
7571                // member's lexical owner is not made ambiguous by its own
7572                // forward declaration. Retain the strongest physical
7573                // declaration for later owner/range queries.
7574                if matches!(
7575                    (
7576                        cpp_class_declaration_strength(analyzer, existing),
7577                        cpp_class_declaration_strength(analyzer, &resolved),
7578                    ),
7579                    (
7580                        CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
7581                        CppClassDeclarationStrength::Full,
7582                    ) | (
7583                        CppClassDeclarationStrength::Unknown,
7584                        CppClassDeclarationStrength::Forward,
7585                    )
7586                ) {
7587                    *existing = resolved;
7588                }
7589                continue;
7590            }
7591            canonical.push(resolved);
7592            if canonical.len() > 1 {
7593                return Err(TypeCandidateFailure::Ambiguous);
7594            }
7595        }
7596        canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
7597    }
7598
7599    pub fn unique_type_candidate_preserving_target(
7600        &self,
7601        analyzer: &CppGraphSource<'_>,
7602        visible_from: &ProjectFile,
7603        candidates: &[&CodeUnit],
7604        target: &CodeUnit,
7605    ) -> Option<CodeUnit> {
7606        // C++ headers often expose one logical type through mutually exclusive
7607        // physical declarations, for example a class in the fallback branch
7608        // and a `using` alias to the standard-library type in the configured
7609        // branch. The index intentionally retains both declarations so forward
7610        // lookup can report each target. Preserve the requested target when
7611        // that is the only ambiguity: every candidate has the same type kind,
7612        // exact canonical FQN, and source file, and the requested declaration
7613        // itself is one of the physical candidates. Do not merge same-named
7614        // declarations from different files or namespaces; those remain
7615        // ambiguous and fail closed below.
7616        if self.c_tag_declaration_family_matches_target(analyzer, visible_from, candidates, target)
7617            || self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7618        {
7619            return Some(target.clone());
7620        }
7621        let mut resolved_candidates = Vec::new();
7622        for candidate in candidates {
7623            // An ifdef branch that aliases an unindexed system type (for
7624            // example `typedef pthread_mutex_t k5_os_mutex`) cannot be
7625            // canonicalized. That branch does not name `target`. Dropping it
7626            // keeps the branch that does. Failing the whole family here would
7627            // deny every usage of the reachable spelling (#2368).
7628            let Some(resolved) =
7629                self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7630            else {
7631                continue;
7632            };
7633            if resolved_candidates
7634                .iter()
7635                .any(|existing| same_visible_symbol(existing, &resolved))
7636            {
7637                continue;
7638            }
7639            resolved_candidates.push(resolved);
7640        }
7641        match resolved_candidates.as_slice() {
7642            [] => None,
7643            [single] => Some(single.clone()),
7644            // The branches disagree about what the name aliases. When they are
7645            // spellings of one entity (#1845) that disagreement is a build
7646            // configuration, not a choice between types, so it must not deny
7647            // the requested target its reference.
7648            _ => self
7649                .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
7650                .map(|_| target.clone()),
7651        }
7652    }
7653
7654    /// The declaration a same-file same-FQN family stands for when a reference
7655    /// names `target`, or `None` when the candidates are not one family or the
7656    /// family does not name `target`.
7657    ///
7658    /// A translation unit cannot hold two different types under one qualified
7659    /// name, so several same-kind declarations of one FQN in one file are
7660    /// alternate spellings of one entity - the configuration branches of an
7661    /// `#if` family, for example log4cxx's `logchar`, which aliases `char` in
7662    /// the UTF-8 branch and `UniChar` in the unichar branch. Their alias
7663    /// targets differ; canonicalizing each branch on its own and then demanding
7664    /// agreement reports an ambiguity that denies every declaration in the
7665    /// family its usages (#1845). The family names `target` when it declares
7666    /// it, or when one branch's alias chain reaches it.
7667    ///
7668    /// Declarations in different files or namespaces are distinct entities and
7669    /// are deliberately excluded: their disagreement is a real ambiguity.
7670    pub fn same_fqn_type_spelling_for_target<'b>(
7671        &self,
7672        analyzer: &CppGraphSource<'_>,
7673        visible_from: &ProjectFile,
7674        candidates: &[&'b CodeUnit],
7675        target: &CodeUnit,
7676    ) -> Option<&'b CodeUnit> {
7677        let [first, rest @ ..] = candidates else {
7678            return None;
7679        };
7680        if rest.is_empty()
7681            || !rest.iter().all(|candidate| {
7682                candidate.kind() == first.kind()
7683                    && candidate.fq_name() == first.fq_name()
7684                    && candidate.source() == first.source()
7685            })
7686        {
7687            return None;
7688        }
7689        candidates
7690            .iter()
7691            .copied()
7692            .find(|candidate| same_symbol(candidate, target))
7693            .or_else(|| {
7694                candidates.iter().copied().find(|candidate| {
7695                    self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7696                        .is_some_and(|resolved| same_visible_symbol(&resolved, target))
7697                })
7698            })
7699    }
7700
7701    pub fn alternate_same_fqn_type_declarations(
7702        &self,
7703        analyzer: &CppGraphSource<'_>,
7704        candidates: &[&CodeUnit],
7705        target: &CodeUnit,
7706    ) -> bool {
7707        let Some(first) = candidates.first() else {
7708            return false;
7709        };
7710        let same_api = first.kind() == target.kind()
7711            && first.fq_name() == target.fq_name()
7712            && first.source() == target.source()
7713            && candidates.iter().all(|candidate| {
7714                candidate.kind() == target.kind()
7715                    && candidate.fq_name() == target.fq_name()
7716                    && candidate.source() == target.source()
7717            })
7718            && candidates
7719                .iter()
7720                .any(|candidate| same_symbol(candidate, target))
7721            && candidates
7722                .iter()
7723                .any(|candidate| !same_logical_symbol(candidate, target));
7724        if !same_api {
7725            return false;
7726        }
7727
7728        let requirements = candidates
7729            .iter()
7730            .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7731            .collect::<Vec<_>>();
7732        requirements.len() > 1
7733            && requirements
7734                .iter()
7735                .all(|requirement| !requirement.is_empty())
7736            && requirements.iter().enumerate().all(|(index, left)| {
7737                requirements[index + 1..].iter().all(|right| {
7738                    left.iter().all(|(_, left_guards)| {
7739                        right.iter().all(|(_, right_guards)| {
7740                            merge_preprocessor_guards(left_guards, right_guards).is_none()
7741                        })
7742                    })
7743                })
7744            })
7745    }
7746
7747    fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
7748        let mut pending = vec![terms.to_vec()];
7749        while let Some(branch_terms) = pending.pop() {
7750            let mut normalized = Vec::new();
7751            let mut covers_branch = false;
7752            for term in branch_terms {
7753                if term.iter().any(|guard| term.contains(&guard.negated())) {
7754                    continue;
7755                }
7756                if term.is_empty() {
7757                    covers_branch = true;
7758                    break;
7759                }
7760                if !normalized.iter().any(|existing| existing == &term) {
7761                    normalized.push(term);
7762                }
7763            }
7764            if covers_branch {
7765                continue;
7766            }
7767            let Some(split_guard) = normalized
7768                .iter()
7769                .flat_map(|term| term.iter())
7770                .next()
7771                .cloned()
7772            else {
7773                return false;
7774            };
7775            let negated_guard = split_guard.negated();
7776            let mut when_defined = Vec::new();
7777            let mut when_undefined = Vec::new();
7778            for term in normalized {
7779                if term.contains(&negated_guard) {
7780                    // This term cannot hold when `split_guard` is true.
7781                } else if term.contains(&split_guard) {
7782                    let mut reduced = term.clone();
7783                    reduced.remove(&split_guard);
7784                    when_defined.push(reduced);
7785                } else {
7786                    when_defined.push(term.clone());
7787                }
7788                if term.contains(&split_guard) {
7789                    // This term cannot hold when `split_guard` is false.
7790                } else if term.contains(&negated_guard) {
7791                    let mut reduced = term;
7792                    reduced.remove(&negated_guard);
7793                    when_undefined.push(reduced);
7794                } else {
7795                    when_undefined.push(term);
7796                }
7797            }
7798            pending.push(when_defined);
7799            pending.push(when_undefined);
7800        }
7801        true
7802    }
7803
7804    /// The byte range of the one `#if` family with a terminal `#else` that holds
7805    /// every physical declaration of every candidate, or `None` when they do not
7806    /// share one such family.
7807    ///
7808    /// Guard terms alone cannot distinguish one `#if` family from separate blocks
7809    /// whose macros changed between declarations. Require every physical range to
7810    /// belong to one syntax-tree family with a terminal `#else` before the terms
7811    /// can prove branch coverage.
7812    fn declarations_share_exhaustive_conditional_family(
7813        &self,
7814        analyzer: &CppGraphSource<'_>,
7815        candidates: &[&CodeUnit],
7816    ) -> Option<(usize, usize)> {
7817        let mut family_range = None;
7818        for candidate in candidates {
7819            let mut candidate_family = None;
7820            for occurrence in analyzer.declaration_source_occurrences(candidate)? {
7821                let key = occurrence.exhaustive_family?;
7822                if candidate_family.is_some_and(|existing| existing != key) {
7823                    return None;
7824                }
7825                candidate_family = Some(key);
7826            }
7827            let candidate_family = candidate_family?;
7828            if family_range.is_some_and(|existing| existing != candidate_family) {
7829                return None;
7830            }
7831            family_range = Some(candidate_family);
7832        }
7833        family_range
7834    }
7835
7836    pub fn complementary_same_fqn_type_declarations(
7837        &self,
7838        analyzer: &CppGraphSource<'_>,
7839        candidates: &[&CodeUnit],
7840        target: &CodeUnit,
7841    ) -> bool {
7842        if candidates.len() < 2
7843            || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7844            || self
7845                .declarations_share_exhaustive_conditional_family(analyzer, candidates)
7846                .is_none()
7847        {
7848            return false;
7849        }
7850        Self::preprocessor_guard_terms_cover_all_paths(
7851            &self.declaration_family_guard_terms(analyzer, candidates),
7852        )
7853    }
7854
7855    fn declaration_family_guard_terms(
7856        &self,
7857        analyzer: &CppGraphSource<'_>,
7858        candidates: &[&CodeUnit],
7859    ) -> Vec<HashSet<PreprocessorGuard>> {
7860        candidates
7861            .iter()
7862            .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7863            .map(|(_, guards)| guards)
7864            .collect()
7865    }
7866
7867    /// A callable name declared on every branch of one completed `#if`/`#else`
7868    /// family is declared on every configuration path, so a reference below the
7869    /// whole family sees one of the branches whatever the preprocessor decides.
7870    /// Answer the family's end byte: only past `#endif` is every branch's
7871    /// declaration behind the reference.
7872    ///
7873    /// This is the callable analogue of `complementary_same_fqn_type_declarations`
7874    /// and shares both of its primitives. It does not require two distinct
7875    /// `CodeUnit`s: branches that declare the same signature can collapse into
7876    /// one unit carrying one physical range per branch.
7877    ///
7878    /// The branches are alternate spellings of one declaration, never competing
7879    /// declarations, so only the first branch stands for the family. Reporting
7880    /// every branch as visible would turn a name the source declares exactly
7881    /// once into an ambiguity between build configurations.
7882    fn exhaustive_guard_family_activation(
7883        &self,
7884        analyzer: &CppGraphSource<'_>,
7885        candidate: &CodeUnit,
7886        reference: &CallableReferenceContext<'_>,
7887    ) -> Option<usize> {
7888        // Branch coverage says nothing about scope: a block-local declaration
7889        // stays invisible however many branches declare it.
7890        if !analyzer
7891            .declaration_source_occurrences(candidate)?
7892            .iter()
7893            .any(|occurrence| occurrence.fact.callable_activation.is_some())
7894        {
7895            return None;
7896        }
7897        let family = self
7898            .visible_identifier_candidates(candidate.source(), candidate.identifier())
7899            .filter(|peer| {
7900                peer.kind() == candidate.kind()
7901                    && peer.fq_name() == candidate.fq_name()
7902                    && peer.source() == candidate.source()
7903            })
7904            .collect::<Vec<_>>();
7905        let (_, family_end) =
7906            self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
7907        if !Self::preprocessor_guard_terms_cover_all_paths(
7908            &self.declaration_family_guard_terms(analyzer, &family),
7909        ) {
7910            return None;
7911        }
7912        // A reference whose own guards pick one branch already reaches that
7913        // branch through the ordinary same-guard path; the family must not
7914        // resurrect the branch the reference contradicts.
7915        if !declaration_guard_requirements(analyzer, self.cpp, candidate)
7916            .iter()
7917            .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
7918        {
7919            return None;
7920        }
7921        (first_declaration_byte(analyzer, candidate)?
7922            == family
7923                .iter()
7924                .filter_map(|peer| first_declaration_byte(analyzer, peer))
7925                .min()?)
7926        .then_some(family_end)
7927    }
7928
7929    fn type_candidate_preserving_target(
7930        &self,
7931        analyzer: &CppGraphSource<'_>,
7932        visible_from: &ProjectFile,
7933        candidate: &CodeUnit,
7934        target: &CodeUnit,
7935    ) -> Option<CodeUnit> {
7936        let mut current = candidate.clone();
7937        let mut matched_target = same_visible_symbol(&current, target)
7938            || self.compatible_primary_template_redeclarations(&current, target);
7939        let mut seen = HashSet::default();
7940        loop {
7941            if !seen.insert(current.clone()) {
7942                return None;
7943            }
7944            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
7945                return matched_target
7946                    .then(|| target.clone())
7947                    .or_else(|| current.is_class().then_some(current));
7948            };
7949            if self.flattened_macro_namespace_alias_target_matches(
7950                analyzer,
7951                visible_from,
7952                &current,
7953                &alias_target,
7954                target,
7955            ) {
7956                return Some(target.clone());
7957            }
7958            if matches!(alias_target, StructuredAliasTarget::Builtin) {
7959                return matched_target
7960                    .then(|| target.clone())
7961                    .or_else(|| current.is_class().then_some(current));
7962            }
7963            // A non-template alias can name a template alias with explicit
7964            // arguments (for example, `using Result = Expected<int>`).  When
7965            // the requested target is that alias's primary declaration, keep
7966            // the primary identity before expanding the RHS arguments.  The
7967            // expansion would otherwise canonicalize through the underlying
7968            // implementation type and lose the target spelling used by the
7969            // forward resolver.
7970            if !self.cpp_template_metadata.contains_key(&current)
7971                && let Some(primary) =
7972                    self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
7973                && (same_visible_symbol(&primary, target)
7974                    || self.compatible_primary_template_redeclarations(&primary, target))
7975            {
7976                return Some(target.clone());
7977            }
7978            if same_visible_symbol(&current, target) {
7979                return Some(target.clone());
7980            }
7981            if self.cpp_template_metadata.contains_key(&current) {
7982                return None;
7983            }
7984            let Some(next) =
7985                self.resolve_structured_alias_target(visible_from, &current, &alias_target)
7986            else {
7987                return matched_target.then(|| target.clone());
7988            };
7989            current = next;
7990            matched_target |= same_visible_symbol(&current, target)
7991                || self.compatible_primary_template_redeclarations(&current, target);
7992        }
7993    }
7994
7995    fn compatible_primary_template_redeclarations(
7996        &self,
7997        left: &CodeUnit,
7998        right: &CodeUnit,
7999    ) -> bool {
8000        let (Some(left_metadata), Some(right_metadata)) = (
8001            self.cpp_template_metadata.get(left),
8002            self.cpp_template_metadata.get(right),
8003        ) else {
8004            return false;
8005        };
8006        left_metadata.primary_fq_name == right_metadata.primary_fq_name
8007            && left_metadata.is_primary()
8008            && right_metadata.is_primary()
8009            && cpp_reconcile_primary_template_parameters(
8010                &[(left, left_metadata), (right, right_metadata)],
8011                right,
8012            )
8013            .is_some()
8014    }
8015
8016    fn alias_candidate_may_preserve_target(
8017        &self,
8018        analyzer: &CppGraphSource<'_>,
8019        visible_from: &ProjectFile,
8020        candidate: &CodeUnit,
8021        target: &CodeUnit,
8022    ) -> bool {
8023        let mut current = candidate.clone();
8024        let mut seen = HashSet::default();
8025        loop {
8026            if same_visible_symbol(&current, target)
8027                || self.compatible_primary_template_redeclarations(&current, target)
8028            {
8029                return true;
8030            }
8031            if self.cpp_template_metadata.contains_key(&current) {
8032                return true;
8033            }
8034            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
8035                return false;
8036            };
8037            let StructuredAliasTarget::Named {
8038                components,
8039                global,
8040                arguments,
8041            } = alias_target
8042            else {
8043                return false;
8044            };
8045            if arguments.is_some() || !seen.insert(current.clone()) {
8046                return true;
8047            }
8048            let qualified = components.join("::");
8049            let next = if global {
8050                unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
8051            } else {
8052                self.resolve_unique_type_for_declaration(visible_from, &current, &qualified)
8053            };
8054            let Some(next) = next else {
8055                return true;
8056            };
8057            current = next;
8058        }
8059    }
8060
8061    fn alias_candidate_structurally_reaches_target(
8062        &self,
8063        analyzer: &CppGraphSource<'_>,
8064        visible_from: &ProjectFile,
8065        candidate: &CodeUnit,
8066        target: &CodeUnit,
8067    ) -> bool {
8068        let mut current = candidate.clone();
8069        let mut seen = HashSet::default();
8070        loop {
8071            if same_visible_symbol(&current, target)
8072                || self.compatible_primary_template_redeclarations(&current, target)
8073                || self
8074                    .cpp_template_metadata
8075                    .get(&current)
8076                    .zip(self.cpp_template_metadata.get(target))
8077                    .is_some_and(|(current, target)| {
8078                        current.primary_fq_name == target.primary_fq_name
8079                    })
8080            {
8081                return true;
8082            }
8083            if !seen.insert(current.clone()) {
8084                return false;
8085            }
8086            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
8087                return false;
8088            };
8089            if let StructuredAliasTarget::Named { components, .. } = &alias_target
8090                && components[..components.len().saturating_sub(1)]
8091                    .iter()
8092                    .any(|component| {
8093                        self.type_reference_component_directly_names_target(component, target)
8094                    })
8095                && self.structured_class_alias_path_preserves_target(
8096                    analyzer,
8097                    visible_from,
8098                    &current,
8099                    target,
8100                )
8101            {
8102                return true;
8103            }
8104            if matches!(alias_target, StructuredAliasTarget::Builtin) {
8105                return false;
8106            }
8107            let Some(primary) =
8108                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
8109            else {
8110                return false;
8111            };
8112            current = primary;
8113        }
8114    }
8115
8116    /// Whether one indexed alias component in a qualified type reference has
8117    /// a structured path to `target`.
8118    ///
8119    /// Alias terminal names such as `type` are deliberately common. A
8120    /// file-wide name set cannot distinguish `Gen<T>::type`, whose indexed RHS
8121    /// names the target, from thousands of unrelated `Traits<T>::type`
8122    /// references. Match every qualified prefix against the alias's canonical
8123    /// scope before following its structured RHS. An unresolved dependent RHS
8124    /// fails this coarse gate only when no other component names the target;
8125    /// references such as `Identity<Target>::type` are admitted by that target
8126    /// component before this helper is called.
8127    pub fn qualified_alias_reference_may_reach_target(
8128        &self,
8129        analyzer: &CppGraphSource<'_>,
8130        file: &ProjectFile,
8131        components: &[String],
8132        global: bool,
8133        target: &CodeUnit,
8134    ) -> bool {
8135        for end in 0..components.len() {
8136            let spelled = &components[..=end];
8137            for candidate in self
8138                .visible_identifier_candidates(file, &components[end])
8139                .filter(|candidate| declared_type_alias(analyzer, candidate))
8140            {
8141                let candidate_components = canonical_cpp_scope_components(candidate);
8142                let shape_matches = if global {
8143                    candidate_components == spelled
8144                } else {
8145                    candidate_components.ends_with(spelled)
8146                };
8147                if shape_matches
8148                    && self.alias_candidate_structurally_reaches_target(
8149                        analyzer, file, candidate, target,
8150                    )
8151                {
8152                    return true;
8153                }
8154            }
8155        }
8156        false
8157    }
8158
8159    /// Every indexed type declaration `raw_name` names when it is written in
8160    /// `declaration`'s namespace: the innermost enclosing namespace that holds
8161    /// the name wins, otherwise the name is looked up unqualified.
8162    fn type_candidates_for_declaration<'b>(
8163        &'b self,
8164        visible_from: &ProjectFile,
8165        declaration: &CodeUnit,
8166        raw_name: &str,
8167    ) -> Vec<&'b CodeUnit> {
8168        let Some(normalized) = normalize_reference_name(raw_name) else {
8169            return Vec::new();
8170        };
8171        if let Some(namespace) = cpp_namespace_for(declaration) {
8172            for prefix in namespace_prefixes(&namespace) {
8173                let qualified = format!("{prefix}::{normalized}");
8174                let candidates = self.type_candidates(visible_from, &qualified);
8175                if !candidates.is_empty() {
8176                    return candidates;
8177                }
8178            }
8179        }
8180        self.type_candidates(visible_from, &normalized)
8181    }
8182
8183    fn resolve_unique_type_for_declaration(
8184        &self,
8185        visible_from: &ProjectFile,
8186        declaration: &CodeUnit,
8187        raw_name: &str,
8188    ) -> Option<CodeUnit> {
8189        unique_logical_type_candidate(self.type_candidates_for_declaration(
8190            visible_from,
8191            declaration,
8192            raw_name,
8193        ))
8194    }
8195
8196    /// The type `raw_name` names at `declaration`, answered with the
8197    /// declaration that defines it.
8198    ///
8199    /// C spells one type twice. `typedef struct T T;` in the header that
8200    /// publishes the handle indexes an incomplete `struct T` beside the
8201    /// complete `struct T { ... }` in the header that declares the members, and
8202    /// both carry the same kind and fully qualified name, so
8203    /// [`logical_type_candidate`] is free to answer with either. mbedtls writes
8204    /// exactly that: `include/mbedtls/ssl.h` forward-declares
8205    /// `mbedtls_ssl_handshake_params` for the pointer member
8206    /// `mbedtls_ssl_handshake_params *MBEDTLS_PRIVATE(handshake)` while
8207    /// `library/ssl_misc.h` defines its fields. A receiver typed from a field
8208    /// declaration is asked for its members on the next link of the chain, and
8209    /// only the definition owns them (#2982).
8210    ///
8211    /// Competing definitions leave nothing to choose between, so keep the
8212    /// logical answer rather than picking one of them.
8213    fn resolve_defining_type_for_declaration(
8214        &self,
8215        analyzer: &CppGraphSource<'_>,
8216        visible_from: &ProjectFile,
8217        declaration: &CodeUnit,
8218        raw_name: &str,
8219    ) -> Option<CodeUnit> {
8220        let candidates = self.type_candidates_for_declaration(visible_from, declaration, raw_name);
8221        let logical = unique_logical_type_candidate(candidates.clone())?;
8222        let mut defining = candidates.into_iter().filter(|candidate| {
8223            candidate.is_class()
8224                && cpp_class_declaration_strength(analyzer, candidate)
8225                    == CppClassDeclarationStrength::Full
8226        });
8227        match (defining.next(), defining.next()) {
8228            (Some(unique_definition), None) => Some(unique_definition.clone()),
8229            _ => Some(logical),
8230        }
8231    }
8232
8233    pub fn resolves_to_type(
8234        &self,
8235        analyzer: &CppGraphSource<'_>,
8236        file: &ProjectFile,
8237        raw_name: &str,
8238        target: &CodeUnit,
8239    ) -> bool {
8240        let Some(normalized) = normalize_reference_name(raw_name) else {
8241            return false;
8242        };
8243        let candidates = self.type_candidates(file, &normalized);
8244        if candidates.is_empty() {
8245            return self.parser_alias_resolves_to_type(file, raw_name, target);
8246        }
8247        let Some(resolved) =
8248            self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
8249        else {
8250            return false;
8251        };
8252        same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
8253    }
8254
8255    pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
8256        let mut current = alias.clone();
8257        let mut visited = HashSet::default();
8258        while visited.insert(current.clone()) {
8259            let raw_target = cpp_canonical_alias_target_text(&self.cpp_source(), &current)?;
8260            let resolved =
8261                self.resolve_type_for_declaration(current.source(), &current, &raw_target)?;
8262            if resolved.is_class() {
8263                return Some(resolved);
8264            }
8265            if !declared_type_alias(&self.cpp_source(), &resolved) {
8266                return None;
8267            }
8268            current = resolved;
8269        }
8270        None
8271    }
8272
8273    /// Whether two callable declarations declare one function.
8274    ///
8275    /// [`same_logical_symbol`] compares the persisted signature strings, which
8276    /// embed each parameter type exactly as it was spelled. A header
8277    /// declaration written inside `namespace zmq { class dist_t { ... } }` says
8278    /// `send_to_matching(msg_t *)` while its out-of-line body at file scope
8279    /// says `zmq::msg_t *`, so the string comparison reports two symbols where
8280    /// C++ ([basic.def], [dcl.fct]) sees one declaration and one definition.
8281    /// This resolves the written parameter names before comparing them and
8282    /// reports the same answer the language does for the cases it can prove.
8283    ///
8284    /// Everything it cannot prove stays two symbols: a template declaration, a
8285    /// parameter with no comparable shape, a name that resolves on one side
8286    /// only, and an alias chain it cannot follow safely (#2010).
8287    pub fn same_logical_callable(
8288        &self,
8289        analyzer: &CppGraphSource<'_>,
8290        left: &CodeUnit,
8291        right: &CodeUnit,
8292    ) -> bool {
8293        if same_logical_symbol(left, right) {
8294            return true;
8295        }
8296        if left.kind() != right.kind()
8297            || !left.is_callable()
8298            || !right.is_callable()
8299            || left.fq_name() != right.fq_name()
8300        {
8301            return false;
8302        }
8303        // A template declaration and its out-of-line body can also diverge
8304        // outside the parameter list - `template <class T>` against
8305        // `template <typename T>` - and the template head is part of the
8306        // persisted signature. Deciding template-head equivalence is a
8307        // separate question, so templates keep string identity.
8308        if self.callable_is_template_declaration(analyzer, left)
8309            || self.callable_is_template_declaration(analyzer, right)
8310        {
8311            return false;
8312        }
8313        let (Some(left_comparable), Some(right_comparable)) = (
8314            self.callable_comparable(analyzer, left),
8315            self.callable_comparable(analyzer, right),
8316        ) else {
8317            return false;
8318        };
8319        // The trailing member `const`, ref-qualifier, `noexcept`, trailing
8320        // return type and requires-clause are part of C++ callable identity and
8321        // an out-of-line definition repeats them verbatim, so they must agree
8322        // as written.
8323        if left_comparable.suffix != right_comparable.suffix
8324            || left_comparable.shapes.len() != right_comparable.shapes.len()
8325        {
8326            return false;
8327        }
8328        left_comparable
8329            .shapes
8330            .iter()
8331            .zip(right_comparable.shapes.iter())
8332            .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
8333                (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
8334                (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
8335                    self.comparable_shapes_agree(analyzer, left_shape, right_shape)
8336                }
8337                // An unstructured parameter records that the reduction failed,
8338                // not that the two spellings mean the same type, so it agrees
8339                // with nothing - including another unstructured parameter.
8340                _ => false,
8341            })
8342    }
8343
8344    /// Compare two parameter shapes node by node with an explicit paired stack.
8345    ///
8346    /// Shape variants and cv-qualifiers must agree exactly at every level; only
8347    /// the named leaves may be spelled differently, and they agree when they
8348    /// resolve to one type declaration.
8349    fn comparable_shapes_agree(
8350        &self,
8351        analyzer: &CppGraphSource<'_>,
8352        left: &CppComparableParameter,
8353        right: &CppComparableParameter,
8354    ) -> bool {
8355        let mut stack = vec![(left.root(), right.root())];
8356        while let Some((left_index, right_index)) = stack.pop() {
8357            match (left.node(left_index), right.node(right_index)) {
8358                (
8359                    CppComparableNode::Named {
8360                        name: left_name,
8361                        primitive: left_primitive,
8362                        konst: left_konst,
8363                        volatil: left_volatil,
8364                    },
8365                    CppComparableNode::Named {
8366                        name: right_name,
8367                        primitive: right_primitive,
8368                        konst: right_konst,
8369                        volatil: right_volatil,
8370                    },
8371                ) => {
8372                    if left_konst != right_konst
8373                        || left_volatil != right_volatil
8374                        || left_primitive != right_primitive
8375                        || !self.comparable_names_agree(
8376                            analyzer,
8377                            left_name,
8378                            right_name,
8379                            *left_primitive,
8380                        )
8381                    {
8382                        return false;
8383                    }
8384                }
8385                (
8386                    CppComparableNode::Pointer {
8387                        inner: left_inner,
8388                        konst: left_konst,
8389                        volatil: left_volatil,
8390                    },
8391                    CppComparableNode::Pointer {
8392                        inner: right_inner,
8393                        konst: right_konst,
8394                        volatil: right_volatil,
8395                    },
8396                ) => {
8397                    if left_konst != right_konst || left_volatil != right_volatil {
8398                        return false;
8399                    }
8400                    stack.push((*left_inner, *right_inner));
8401                }
8402                (
8403                    CppComparableNode::Reference { inner: left_inner },
8404                    CppComparableNode::Reference { inner: right_inner },
8405                )
8406                | (
8407                    CppComparableNode::Array { inner: left_inner },
8408                    CppComparableNode::Array { inner: right_inner },
8409                ) => stack.push((*left_inner, *right_inner)),
8410                (
8411                    CppComparableNode::Generic {
8412                        base: left_base,
8413                        arguments: left_arguments,
8414                    },
8415                    CppComparableNode::Generic {
8416                        base: right_base,
8417                        arguments: right_arguments,
8418                    },
8419                ) => {
8420                    if left_arguments.len() != right_arguments.len() {
8421                        return false;
8422                    }
8423                    stack.push((*left_base, *right_base));
8424                    stack.extend(
8425                        left_arguments.iter().zip(right_arguments.iter()).map(
8426                            |(left_argument, right_argument)| (*left_argument, *right_argument),
8427                        ),
8428                    );
8429                }
8430                _ => return false,
8431            }
8432        }
8433        true
8434    }
8435
8436    /// Whether two written type names denote one type.
8437    ///
8438    /// A primitive denotes the same type in every scope, so its recorded
8439    /// lexical scope is noise and its spelling decides. A nominal name is
8440    /// resolved on each side independently: two resolved names agree when they
8441    /// reach one type declaration, and two unresolved names agree only on
8442    /// exact agreement of what was written, which is no weaker than the
8443    /// whole-signature string equality this comparison replaces. Resolution on
8444    /// one side only is evidence of difference, never of agreement.
8445    fn comparable_names_agree(
8446        &self,
8447        analyzer: &CppGraphSource<'_>,
8448        left: &StructuredTypeName,
8449        right: &StructuredTypeName,
8450        primitive: bool,
8451    ) -> bool {
8452        if primitive {
8453            return left.path() == right.path();
8454        }
8455        match (
8456            self.comparable_name_terminal(analyzer, left),
8457            self.comparable_name_terminal(analyzer, right),
8458        ) {
8459            (Some(left_terminal), Some(right_terminal)) => {
8460                same_logical_symbol(&left_terminal, &right_terminal)
8461            }
8462            (None, None) => {
8463                left.path() == right.path() && left.is_absolute() == right.is_absolute()
8464            }
8465            _ => false,
8466        }
8467    }
8468
8469    /// The class declaration a written type name denotes, or `None` when the
8470    /// workspace cannot prove one.
8471    ///
8472    /// The lookup is a closure-independent lexical-scope prefix walk over the
8473    /// stored C++ identifier index rather than a visibility lookup: a body's
8474    /// `.cpp` is almost never in the reference file's include closure. Any name
8475    /// this walk resolves is one an enclosing-scope lookup could resolve, so it
8476    /// cannot invent a type the compiler could not see; `using`-directives are
8477    /// not modelled, and a name that needs one stays unresolved.
8478    fn comparable_name_terminal(
8479        &self,
8480        analyzer: &CppGraphSource<'_>,
8481        name: &StructuredTypeName,
8482    ) -> Option<CodeUnit> {
8483        let mut current = self.comparable_name_declaration(analyzer, name)?;
8484        let mut visited = HashSet::default();
8485        for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
8486            // The alias question is asked before the class question, and
8487            // through `declared_type_alias` rather than `is_type_alias`,
8488            // because extraction records `using A8 = A7;` as a *Class* unit
8489            // whose signature is the alias declaration. Reading the kind first
8490            // would end the chase on the alias itself and report an alias
8491            // spelling and its underlying class as two types (#2010).
8492            if !declared_type_alias(analyzer, &current) {
8493                return current.is_class().then_some(current);
8494            }
8495            if !visited.insert(current.clone()) {
8496                return None;
8497            }
8498            let facts = analyzer.declaration_source_properties(&current)?;
8499            if facts.iter().any(|fact| fact.adds_indirection) {
8500                return None;
8501            }
8502            let raw_target = cpp_canonical_alias_target_text(analyzer, &current)?;
8503            current = self.comparable_alias_target(analyzer, &current, &raw_target)?;
8504        }
8505        None
8506    }
8507
8508    /// The declaration one alias hop lands on: the type `raw_target` names,
8509    /// looked up from the alias declaration's own enclosing namespace.
8510    ///
8511    /// The hop takes the same closure-independent prefix walk the first lookup
8512    /// took, and deliberately not `resolve_type_for_declaration`: that one
8513    /// answers out of the `VisibilityIndex`, which is rooted at the reference
8514    /// file, while the alias declaration this hop starts from is reached
8515    /// through the workspace definition index and its file need not be in that
8516    /// root's include closure - where the visibility lookup answers nothing and
8517    /// the chase would stop on the alias itself (#2010).
8518    fn comparable_alias_target(
8519        &self,
8520        analyzer: &CppGraphSource<'_>,
8521        alias: &CodeUnit,
8522        raw_target: &str,
8523    ) -> Option<CodeUnit> {
8524        // `raw_target` is the alias declaration's written type text, so it is a
8525        // plain `::`-joined qualified-id: the same domain the shared symbol-path
8526        // parser reads, and the same leading `::` that marks an absolute name
8527        // everywhere else this crate normalizes a reference.
8528        let absolute = raw_target.trim_start().starts_with("::");
8529        let normalized = normalize_reference_name(raw_target)?;
8530        let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8531            brokk_bifrost_core::analyzer::Language::Cpp,
8532            &normalized,
8533        );
8534        let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
8535            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8536                brokk_bifrost_core::analyzer::Language::Cpp,
8537                &namespace,
8538            )
8539        });
8540        let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
8541        self.comparable_name_declaration(analyzer, &name)
8542    }
8543
8544    /// The one type declaration `name` names, by enclosing scope, innermost
8545    /// first.
8546    ///
8547    /// The first prefix depth that names anything decides: an inner scope hides
8548    /// an outer one, so a match there is the answer even when an outer scope
8549    /// also declares the name. Several logically distinct declarations at that
8550    /// depth are an ambiguity this comparison must not guess at.
8551    fn comparable_name_declaration(
8552        &self,
8553        analyzer: &CppGraphSource<'_>,
8554        name: &StructuredTypeName,
8555    ) -> Option<CodeUnit> {
8556        // The same parameter-name pair is commonly compared once for every
8557        // physical declaration in a candidate set. Keep the index lookup under
8558        // the cache lock so concurrent workers cannot all miss the same key
8559        // and repeat the workspace candidate filter. `None` is a meaningful
8560        // result too: unresolved and ambiguous names must remain unresolved on
8561        // every later comparison.
8562        let mut cache = self
8563            .comparable_name_declarations
8564            .lock()
8565            .expect("C++ comparable name declaration cache poisoned");
8566        if let Some(cached) = cache.get(name) {
8567            return cached.clone();
8568        }
8569        let interner = segment_interner();
8570        let identifier = name.path().last()?;
8571        let candidates_by_identifier = self.cpp.visibility_identifier_candidates(identifier);
8572        let first_depth = if name.is_absolute() {
8573            0
8574        } else {
8575            name.lexical_scope().len()
8576        };
8577        let mut resolved = None;
8578        for depth in (0..=first_depth).rev() {
8579            let mut structured = FqName::new();
8580            for component in name.lexical_scope()[..depth].iter().chain(name.path()) {
8581                structured.push(interner.intern(component, SegmentKind::Unknown));
8582            }
8583            let mut candidates = candidates_by_identifier
8584                .iter()
8585                .filter(|unit| unit.fq().same_segment_texts(&structured))
8586                .filter(|unit| {
8587                    unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
8588                })
8589                .cloned();
8590            let Some(first) = candidates.next() else {
8591                continue;
8592            };
8593            resolved = candidates
8594                .all(|unit| same_logical_symbol(&unit, &first))
8595                .then_some(first);
8596            break;
8597        }
8598        cache.insert(name.clone(), resolved.clone());
8599        resolved
8600    }
8601
8602    /// The comparison inputs of one callable declaration, extracted once.
8603    ///
8604    /// The comparison itself runs only when two candidates share kind and fully
8605    /// qualified name but not signature, which is rare; re-reading the same
8606    /// declaration for every pair in a candidate set is not.
8607    fn callable_comparable(
8608        &self,
8609        analyzer: &CppGraphSource<'_>,
8610        unit: &CodeUnit,
8611    ) -> Option<Arc<ExtractedComparable>> {
8612        if let Some(cached) = self
8613            .callable_comparables
8614            .lock()
8615            .expect("C++ callable comparable cache poisoned")
8616            .get(unit)
8617            .cloned()
8618        {
8619            return cached;
8620        }
8621        let extracted = self
8622            .extract_callable_comparable(analyzer, unit)
8623            .map(Arc::new);
8624        self.callable_comparables
8625            .lock()
8626            .expect("C++ callable comparable cache poisoned")
8627            .insert(unit.clone(), extracted.clone());
8628        extracted
8629    }
8630
8631    fn extract_callable_comparable(
8632        &self,
8633        analyzer: &CppGraphSource<'_>,
8634        unit: &CodeUnit,
8635    ) -> Option<ExtractedComparable> {
8636        let fact = analyzer
8637            .declaration_source_properties(unit)?
8638            .into_iter()
8639            .find(|fact| fact.callable_comparable_shapes.is_some())?;
8640        Some(ExtractedComparable {
8641            shapes: fact.callable_comparable_shapes?,
8642            suffix: fact.callable_identity_suffix?,
8643        })
8644    }
8645
8646    pub fn canonical_type_for_reference(
8647        &self,
8648        file: &ProjectFile,
8649        raw_name: &str,
8650    ) -> Option<CodeUnit> {
8651        let resolved = self.resolve_type(file, raw_name)?;
8652        self.alias_target(&resolved).or(Some(resolved))
8653    }
8654
8655    pub fn parser_alias_resolves_to_type(
8656        &self,
8657        file: &ProjectFile,
8658        raw_name: &str,
8659        target: &CodeUnit,
8660    ) -> bool {
8661        let Some(alias_name) = normalize_reference_name(raw_name) else {
8662            return false;
8663        };
8664        any_visible_source_file_matches(&self.visible_source_files_by_root, file, |source_file| {
8665            self.file_alias_matches(self.cpp, source_file, &alias_name, target)
8666        })
8667    }
8668
8669    fn file_alias_matches(
8670        &self,
8671        cpp: &dyn CppSource,
8672        file: &ProjectFile,
8673        alias_name: &str,
8674        target: &CodeUnit,
8675    ) -> bool {
8676        self.canonical_alias_cell(cpp, file).is_some_and(|cell| {
8677            cell.get()
8678                .expect("published canonical aliases")
8679                .iter()
8680                .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
8681        })
8682    }
8683
8684    fn canonical_alias_cell(
8685        &self,
8686        cpp: &dyn CppSource,
8687        file: &ProjectFile,
8688    ) -> Option<Arc<OnceLock<Box<[CppAlias]>>>> {
8689        let cell = {
8690            let mut cells = self.alias_cells.lock().expect("alias cell map lock");
8691            Arc::clone(
8692                cells
8693                    .entry(file.clone())
8694                    .or_insert_with(|| Arc::new(OnceLock::new())),
8695            )
8696        };
8697        if cell.get().is_none() {
8698            let aliases = aliases_from_canonical_source(cpp, self.token, file)?;
8699            cell.get_or_init(|| {
8700                self.parser_alias_source_parses
8701                    .fetch_add(1, Ordering::Relaxed);
8702                #[cfg(any(test, feature = "test-support"))]
8703                {
8704                    *self
8705                        .alias_source_parse_counts
8706                        .lock()
8707                        .expect("alias source parse count lock")
8708                        .entry(file.clone())
8709                        .or_default() += 1;
8710                }
8711                aliases.into_boxed_slice()
8712            });
8713        }
8714        Some(cell)
8715    }
8716
8717    #[cfg(any(test, feature = "test-support"))]
8718    pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
8719        self.visible_source_files_by_root
8720            .get(file)
8721            .cloned()
8722            .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
8723    }
8724
8725    #[cfg(any(test, feature = "test-support"))]
8726    pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
8727        self.alias_source_parse_counts
8728            .lock()
8729            .expect("alias source parse count lock")
8730            .get(file)
8731            .copied()
8732            .unwrap_or(0)
8733    }
8734
8735    #[cfg(any(test, feature = "test-support"))]
8736    pub fn parser_alias_fallback_file_count_for_test(&self) -> usize {
8737        self.parser_alias_fallback_files.load(Ordering::Relaxed)
8738    }
8739
8740    pub fn resolve_named(
8741        &self,
8742        file: &ProjectFile,
8743        raw_name: &str,
8744        kind: TargetKind,
8745    ) -> Option<CodeUnit> {
8746        let normalized = normalize_reference_name(raw_name)?;
8747        self.named_candidates_for_normalized(file, &normalized, kind)
8748            .into_iter()
8749            .next()
8750            .cloned()
8751    }
8752
8753    pub fn contains_named_symbol(
8754        &self,
8755        file: &ProjectFile,
8756        raw_name: &str,
8757        kind: TargetKind,
8758        target: &CodeUnit,
8759    ) -> bool {
8760        let Some(normalized) = normalize_reference_name(raw_name) else {
8761            return false;
8762        };
8763        self.named_candidates_for_normalized(file, &normalized, kind)
8764            .into_iter()
8765            .any(|unit| {
8766                matches_kind_for_lookup(unit, kind)
8767                    && reference_matches_unit(&normalized, unit)
8768                    && same_visible_symbol(unit, target)
8769            })
8770    }
8771
8772    pub fn named_candidates(
8773        &self,
8774        file: &ProjectFile,
8775        raw_name: &str,
8776        kind: TargetKind,
8777    ) -> Vec<CodeUnit> {
8778        let Some(normalized) = normalize_reference_name(raw_name) else {
8779            return Vec::new();
8780        };
8781        self.named_candidates_for_normalized(file, &normalized, kind)
8782            .into_iter()
8783            .cloned()
8784            .collect()
8785    }
8786
8787    pub fn resolve_known_non_target(
8788        &self,
8789        file: &ProjectFile,
8790        raw_name: &str,
8791        kind: TargetKind,
8792        target: &CodeUnit,
8793    ) -> bool {
8794        let Some(normalized) = normalize_reference_name(raw_name) else {
8795            return false;
8796        };
8797        normalized.contains("::")
8798            && self
8799                .named_candidates_for_normalized(file, &normalized, kind)
8800                .into_iter()
8801                .any(|unit| {
8802                    matches_kind_for_lookup(unit, kind)
8803                        && reference_matches_unit(&normalized, unit)
8804                        && !same_visible_symbol(unit, target)
8805                })
8806    }
8807
8808    pub fn resolve_call_return_binding(
8809        &self,
8810        analyzer: &CppGraphSource<'_>,
8811        file: &ProjectFile,
8812        raw_name: &str,
8813        arity: usize,
8814        lexical_namespace: Option<&str>,
8815        direct_type: Option<&CodeUnit>,
8816    ) -> Option<CppScanBinding> {
8817        let normalized = normalize_reference_name(raw_name)?;
8818        let mut candidates = Vec::new();
8819        for function in
8820            self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8821        {
8822            if cpp_callable_arity(analyzer, function).is_none_or(|known| known.accepts(arity))
8823                && !direct_type.is_some_and(|direct_type| {
8824                    self.callable_is_constructor_declaration(analyzer, function)
8825                        && type_owner_of(analyzer, function)
8826                            .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8827                })
8828            {
8829                candidates.push(function.clone());
8830            }
8831        }
8832        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8833        unanimous_return_binding(analyzer, self, file, &candidates)
8834    }
8835
8836    pub fn resolve_call_return_binding_without_arity(
8837        &self,
8838        analyzer: &CppGraphSource<'_>,
8839        file: &ProjectFile,
8840        raw_name: &str,
8841        lexical_namespace: Option<&str>,
8842        direct_type: Option<&CodeUnit>,
8843    ) -> (bool, Option<CppScanBinding>) {
8844        let Some(normalized) = normalize_reference_name(raw_name) else {
8845            return (false, None);
8846        };
8847        let mut candidates = self
8848            .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8849            .into_iter()
8850            .filter(|function| {
8851                function.is_function()
8852                    && !direct_type.is_some_and(|direct_type| {
8853                        self.callable_is_constructor_declaration(analyzer, function)
8854                            && type_owner_of(analyzer, function)
8855                                .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8856                    })
8857            })
8858            .cloned()
8859            .collect::<Vec<_>>();
8860        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8861        let has_candidates = !candidates.is_empty();
8862        (
8863            has_candidates,
8864            unanimous_return_binding(analyzer, self, file, &candidates),
8865        )
8866    }
8867
8868    pub fn visible_identifier_candidates<'b>(
8869        &'b self,
8870        file: &ProjectFile,
8871        identifier: &str,
8872    ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
8873        self.visible_by_identifier
8874            .get(file)
8875            .and_then(|by_name| by_name.get(identifier))
8876            .into_iter()
8877            .flatten()
8878    }
8879
8880    /// Whether a grammar component is the direct spelling of `target`.
8881    ///
8882    /// Concrete template specializations are indexed with their arguments in
8883    /// `identifier`, while a reference's name component contains only the
8884    /// primary name. Argument matching remains the later resolver's job.
8885    pub fn type_reference_component_directly_names_target(
8886        &self,
8887        component: &str,
8888        target: &CodeUnit,
8889    ) -> bool {
8890        component == target.identifier()
8891            || self
8892                .cpp_template_metadata
8893                .get(target)
8894                .is_some_and(|metadata| component == metadata.primary_name)
8895    }
8896
8897    /// Return terminal reference names that can denote `target` from `file`.
8898    ///
8899    /// The indexed candidate table covers ordinary declarations and aliases;
8900    /// Parser-only aliases are tested lazily when their spelling is actually
8901    /// encountered in a scanned type node. Enumerating them here would parse
8902    /// every source in the include closure even when the target's direct name
8903    /// is the only spelling present in the file.
8904    pub fn visible_type_reference_component_names_for_target(
8905        &self,
8906        analyzer: &CppGraphSource<'_>,
8907        file: &ProjectFile,
8908        target: &CodeUnit,
8909    ) -> HashSet<String> {
8910        let mut names = HashSet::from_iter([target.identifier().to_string()]);
8911        if let Some(metadata) = self.cpp_template_metadata.get(target) {
8912            names.insert(metadata.primary_name.clone());
8913        }
8914
8915        if let Some(by_identifier) = self.visible_by_identifier.get(file) {
8916            for (identifier, candidates) in by_identifier {
8917                if candidates.iter().any(|candidate| {
8918                    (candidate.is_class()
8919                        && (same_visible_symbol(candidate, target)
8920                            || self.compatible_primary_template_redeclarations(candidate, target)))
8921                        || (declared_type_alias(analyzer, candidate)
8922                            && self.alias_candidate_structurally_reaches_target(
8923                                analyzer, file, candidate, target,
8924                            ))
8925                }) {
8926                    names.insert(identifier.clone());
8927                }
8928            }
8929        }
8930
8931        names
8932    }
8933
8934    pub fn indexed_structural_class_scope(
8935        &self,
8936        file: &ProjectFile,
8937        class: Node<'_>,
8938        source: &str,
8939    ) -> Option<Vec<String>> {
8940        let key = (file.clone(), class.start_byte(), class.end_byte());
8941        if let Some(cached) = self
8942            .indexed_structural_class_scopes
8943            .lock()
8944            .expect("C++ indexed structural-class scope cache poisoned")
8945            .get(&key)
8946            .cloned()
8947        {
8948            return cached;
8949        }
8950        let resolved = (|| {
8951            let name = class.child_by_field_name("name")?;
8952            let identifier = if name.kind() == "template_type" {
8953                node_text(name.child_by_field_name("name")?, source).to_string()
8954            } else {
8955                let mut components = Vec::new();
8956                append_cpp_name_components(name, source, &mut components)?;
8957                components.last()?.clone()
8958            };
8959            let visible = self
8960                .visible_identifier_candidates(file, &identifier)
8961                .cloned()
8962                .collect::<Vec<_>>();
8963            let mut visible = visible;
8964            for candidate in
8965                self.visible_by_file
8966                    .get(file)
8967                    .into_iter()
8968                    .flatten()
8969                    .filter(|candidate| {
8970                        self.cpp_template_metadata
8971                            .get(candidate)
8972                            .is_some_and(|metadata| metadata.primary_name == identifier)
8973                    })
8974            {
8975                if !visible
8976                    .iter()
8977                    .any(|existing| same_logical_symbol(existing, candidate))
8978                {
8979                    visible.push(candidate.clone());
8980                }
8981            }
8982            // Built once per call rather than per candidate; `cpp_source` rebuilds
8983            // the five-field source from the same `self.cpp` on every call.
8984            let cpp_source = self.cpp_source();
8985            let candidates = visible
8986                .iter()
8987                .filter(|candidate| {
8988                    candidate.source() == file
8989                        && candidate.is_class()
8990                        && !declared_type_alias(&cpp_source, candidate)
8991                        && self.cpp.ranges(candidate).iter().any(|range| {
8992                            range.start_byte <= class.start_byte()
8993                                && class.end_byte() <= range.end_byte
8994                        })
8995                })
8996                .collect::<Vec<_>>();
8997            let owner = if name.kind() == "template_type" {
8998                let expected = normalize_cpp_whitespace(node_text(name, source));
8999                let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
9000                let exact = candidates
9001                    .iter()
9002                    .copied()
9003                    .filter(|candidate| {
9004                        candidate
9005                            .fq()
9006                            .segments()
9007                            .iter()
9008                            .rev()
9009                            .find_map(|&segment| {
9010                                let (text, kind) = interner.resolve(segment);
9011                                matches!(
9012                                    kind,
9013                                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
9014                                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
9015                                )
9016                                .then_some(text)
9017                            })
9018                            .is_some_and(|text| text == expected)
9019                    })
9020                    .collect::<Vec<_>>();
9021                unique_logical_type_candidate(exact)
9022                    .or_else(|| unique_logical_type_candidate(candidates.clone()))?
9023            } else {
9024                unique_logical_type_candidate(candidates)?
9025            };
9026            Some(canonical_cpp_scope_components(&owner))
9027        })();
9028        self.indexed_structural_class_scopes
9029            .lock()
9030            .expect("C++ indexed structural-class scope cache poisoned")
9031            .insert(key, resolved.clone());
9032        resolved
9033    }
9034
9035    pub fn indexed_enclosing_owner_scope(
9036        &self,
9037        analyzer: &CppGraphSource<'_>,
9038        file: &ProjectFile,
9039        node: Node<'_>,
9040    ) -> Option<Vec<String>> {
9041        let anchor = std::iter::successors(Some(node), |current| current.parent())
9042            .find(|current| {
9043                matches!(
9044                    current.kind(),
9045                    "function_definition"
9046                        | "class_specifier"
9047                        | "struct_specifier"
9048                        | "union_specifier"
9049                )
9050            })
9051            .unwrap_or(node);
9052        let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
9053        if let Some(cached) = self
9054            .indexed_enclosing_owner_scopes
9055            .lock()
9056            .expect("C++ indexed enclosing-owner scope cache poisoned")
9057            .get(&key)
9058            .cloned()
9059        {
9060            return cached;
9061        }
9062        let resolved = (|| {
9063            let range = Range {
9064                start_byte: node.start_byte(),
9065                end_byte: node.end_byte(),
9066                start_line: node.start_position().row,
9067                end_line: node.end_position().row,
9068            };
9069            let start = analyzer.enclosing_code_unit(file, &range)?;
9070            let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
9071                start,
9072                |unit| self.cached_precise_parent_of(analyzer, unit),
9073            )
9074            .find(|unit| {
9075                unit.is_class()
9076                    && !analyzer
9077                        .type_alias_provider()
9078                        .is_some_and(|provider| provider.is_type_alias(unit))
9079            })?;
9080            Some(canonical_cpp_scope_components(&owner))
9081        })();
9082        self.indexed_enclosing_owner_scopes
9083            .lock()
9084            .expect("C++ indexed enclosing-owner scope cache poisoned")
9085            .insert(key, resolved.clone());
9086        resolved
9087    }
9088
9089    fn cached_precise_parent_of(
9090        &self,
9091        analyzer: &CppGraphSource<'_>,
9092        code_unit: &CodeUnit,
9093    ) -> Option<CodeUnit> {
9094        if let Some(cached) = self
9095            .precise_parent_cache
9096            .lock()
9097            .expect("C++ precise-parent cache poisoned")
9098            .get(code_unit)
9099            .cloned()
9100        {
9101            return cached;
9102        }
9103        let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
9104        self.precise_parent_cache
9105            .lock()
9106            .expect("C++ precise-parent cache poisoned")
9107            .insert(code_unit.clone(), resolved.clone());
9108        resolved
9109    }
9110
9111    pub fn callable_is_constructor_declaration(
9112        &self,
9113        analyzer: &CppGraphSource<'_>,
9114        candidate: &CodeUnit,
9115    ) -> bool {
9116        if !candidate.is_function() {
9117            return false;
9118        }
9119        if analyzer
9120            .declaration_source_properties(candidate)
9121            .is_some_and(|facts| facts.iter().any(|fact| fact.callable_is_constructor))
9122        {
9123            return true;
9124        }
9125        let metadata = analyzer.signature_metadata(candidate);
9126        !metadata.is_empty()
9127            && metadata
9128                .iter()
9129                .all(|signature| signature.return_type_text().is_none())
9130    }
9131
9132    /// Whether a callable declaration is a class-template deduction guide.
9133    ///
9134    /// Tree-sitter represents `Box(T) -> Box<T>;` as a declaration with no
9135    /// type field whose function declarator owns a trailing return type. This
9136    /// structured shape distinguishes a guide from both a constructor (no
9137    /// trailing return) and an ordinary trailing-return function (an `auto`
9138    /// type field).
9139    pub fn callable_is_deduction_guide_declaration(
9140        &self,
9141        analyzer: &CppGraphSource<'_>,
9142        candidate: &CodeUnit,
9143    ) -> bool {
9144        callable_declaration_has_fact(analyzer, candidate, |fact| fact.callable_is_deduction_guide)
9145    }
9146
9147    /// Whether a callable occurrence is directly wrapped by a C++ template
9148    /// declaration. This deliberately inspects declaration syntax instead of
9149    /// inferring template status from the rendered signature.
9150    pub fn callable_is_template_declaration(
9151        &self,
9152        analyzer: &CppGraphSource<'_>,
9153        candidate: &CodeUnit,
9154    ) -> bool {
9155        callable_declaration_has_fact(analyzer, candidate, |fact| fact.callable_is_template)
9156    }
9157
9158    pub fn type_name_candidates<'b>(
9159        &'b self,
9160        file: &ProjectFile,
9161        normalized: &str,
9162    ) -> Vec<&'b CodeUnit> {
9163        self.candidate_units(file, normalized, TargetKind::Type)
9164    }
9165
9166    pub fn visible_members_for_owner_name<'b>(
9167        &'b self,
9168        file: &ProjectFile,
9169        owner: &CodeUnit,
9170        name: &str,
9171    ) -> Vec<&'b CodeUnit> {
9172        self.visible_identifier_candidates(file, name)
9173            .filter(|unit| {
9174                // Structured owner pop on the unit's own `fq()` (shared with
9175                // `CodeUnitIndex::parent_of`), not a re-split of its rendered fqn
9176                // string.
9177                brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
9178                    .is_some_and(|parent| parent == owner.fq_name())
9179            })
9180            .collect()
9181    }
9182
9183    pub fn visible_member_for_owner_name(
9184        &self,
9185        file: &ProjectFile,
9186        owner: &CodeUnit,
9187        name: &str,
9188    ) -> VisibleMemberResolution {
9189        let candidates = self.visible_members_for_owner_name(file, owner, name);
9190        let mut callables = Vec::new();
9191        let mut non_callable = None;
9192        for candidate in candidates {
9193            if candidate.is_function() {
9194                callables.push(candidate.clone());
9195            } else if non_callable.is_none() {
9196                non_callable = Some(candidate.clone());
9197            }
9198        }
9199        match (callables.is_empty(), non_callable) {
9200            (false, None) => VisibleMemberResolution::Callable(callables),
9201            (true, Some(_)) => VisibleMemberResolution::NonCallable,
9202            (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
9203            (true, None) => VisibleMemberResolution::Missing,
9204        }
9205    }
9206
9207    fn field_declared_type_fact(
9208        &self,
9209        analyzer: &CppGraphSource<'_>,
9210        field: &CodeUnit,
9211    ) -> Option<DeclaredFieldTypeFact> {
9212        if let Some(cached) = self
9213            .field_type_facts
9214            .lock()
9215            .expect("C++ field type fact cache poisoned")
9216            .get(field)
9217            .cloned()
9218        {
9219            return cached;
9220        }
9221        let decoded = decode_field_declared_type_fact(analyzer, field);
9222        self.field_type_facts
9223            .lock()
9224            .expect("C++ field type fact cache poisoned")
9225            .insert(field.clone(), decoded.clone());
9226        decoded
9227    }
9228
9229    fn structured_alias_target(
9230        &self,
9231        analyzer: &CppGraphSource<'_>,
9232        unit: &CodeUnit,
9233    ) -> Option<StructuredAliasTarget> {
9234        if let Some(cached) = self
9235            .structured_alias_targets
9236            .lock()
9237            .expect("C++ structured alias target cache poisoned")
9238            .get(unit)
9239            .cloned()
9240        {
9241            return cached;
9242        }
9243        let decoded = decode_structured_alias_target(analyzer, unit);
9244        self.structured_alias_targets
9245            .lock()
9246            .expect("C++ structured alias target cache poisoned")
9247            .insert(unit.clone(), decoded.clone());
9248        decoded
9249    }
9250
9251    pub fn type_candidates<'b>(
9252        &'b self,
9253        file: &ProjectFile,
9254        normalized: &str,
9255    ) -> Vec<&'b CodeUnit> {
9256        let mut candidates = self
9257            .candidate_units(file, normalized, TargetKind::Type)
9258            .into_iter()
9259            .filter(|unit| {
9260                unit.kind() == CodeUnitType::Class || declared_type_alias(&self.cpp_source(), unit)
9261            })
9262            .collect::<Vec<_>>();
9263        dedup_unit_refs(&mut candidates);
9264        candidates
9265    }
9266
9267    pub fn named_candidates_for_normalized<'b>(
9268        &'b self,
9269        file: &ProjectFile,
9270        normalized: &str,
9271        kind: TargetKind,
9272    ) -> Vec<&'b CodeUnit> {
9273        let mut candidates = self
9274            .candidate_units(file, normalized, kind)
9275            .into_iter()
9276            .filter(|unit| {
9277                matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
9278            })
9279            .collect::<Vec<_>>();
9280        dedup_unit_refs(&mut candidates);
9281        candidates
9282    }
9283
9284    pub fn candidate_units<'b>(
9285        &'b self,
9286        file: &ProjectFile,
9287        normalized: &str,
9288        kind: TargetKind,
9289    ) -> Vec<&'b CodeUnit> {
9290        if normalized.contains("::") {
9291            // `normalized` comes from `normalize_cpp_reference_text`, which
9292            // truncates at the first `(`/`{`/`<`, leaving a plain `::`-joined
9293            // qualified-id with no embedded `.`/`/`/`\` and operator tokens
9294            // kept intact by the shared splitter's operator merge — the same
9295            // domain `cpp_reference_fqn_candidates` below already parses with
9296            // the shared splitter. Re-tokenizing and taking the last segment
9297            // reproduces `rsplit("::").find(non-empty)`'s terminal-component
9298            // scan exactly.
9299            let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9300                brokk_bifrost_core::analyzer::Language::Cpp,
9301                normalized,
9302            );
9303            let Some(identifier) = parts.last() else {
9304                return Vec::new();
9305            };
9306            let fqns = cpp_reference_fqn_candidates_from_parts(&parts, kind);
9307            return self
9308                .visible_identifier_candidates(file, identifier)
9309                .filter(|unit| {
9310                    #[cfg(any(test, feature = "test-support"))]
9311                    self.qualified_candidate_inspections
9312                        .fetch_add(1, Ordering::Relaxed);
9313                    fqns.iter().any(|fqn| unit.fq_name() == *fqn)
9314                        || canonical_cpp_name_matches(unit, normalized)
9315                })
9316                .collect();
9317        }
9318        self.visible_identifier_candidates(file, normalized)
9319            .collect()
9320    }
9321
9322    #[cfg(any(test, feature = "test-support"))]
9323    pub fn reset_qualified_candidate_inspections(&self) {
9324        self.qualified_candidate_inspections
9325            .store(0, Ordering::Relaxed);
9326    }
9327
9328    #[cfg(any(test, feature = "test-support"))]
9329    pub fn qualified_candidate_inspections(&self) -> usize {
9330        self.qualified_candidate_inspections.load(Ordering::Relaxed)
9331    }
9332
9333    #[cfg(any(test, feature = "test-support"))]
9334    pub fn visibility_identifier_lookup_count(&self) -> usize {
9335        self.visibility_identifier_lookup_count
9336    }
9337
9338    #[cfg(any(test, feature = "test-support"))]
9339    pub fn visibility_identifier_batch_count(&self) -> usize {
9340        self.visibility_identifier_batch_count
9341    }
9342
9343    #[cfg(any(test, feature = "test-support"))]
9344    pub fn reset_target_preserving_type_resolution_count(&self) {
9345        self.target_preserving_type_resolution_count
9346            .store(0, Ordering::Relaxed);
9347    }
9348
9349    #[cfg(any(test, feature = "test-support"))]
9350    pub fn target_preserving_type_resolution_count(&self) -> usize {
9351        self.target_preserving_type_resolution_count
9352            .load(Ordering::Relaxed)
9353    }
9354
9355    #[cfg(any(test, feature = "test-support"))]
9356    pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
9357        self.visible_parser_alias_name_set_build_count
9358            .load(Ordering::Relaxed)
9359    }
9360}
9361
9362fn callable_declaration_has_fact(
9363    analyzer: &CppGraphSource<'_>,
9364    candidate: &CodeUnit,
9365    matches: impl Fn(&brokk_bifrost_core::analyzer::cpp_facts::CppDeclarationSourceFact) -> bool,
9366) -> bool {
9367    candidate.is_function()
9368        && analyzer
9369            .declaration_source_properties(candidate)
9370            .is_some_and(|facts| facts.iter().any(matches))
9371}
9372
9373#[derive(Default)]
9374struct IncludeGraph {
9375    targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
9376}
9377
9378impl IncludeGraph {
9379    fn extend_with<F>(
9380        &mut self,
9381        root: &ProjectFile,
9382        cancellation: Option<&CancellationToken>,
9383        targets_for: &mut F,
9384    ) where
9385        F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9386    {
9387        let mut stack = vec![root.clone()];
9388        while let Some(file) = stack.pop() {
9389            if cancellation.is_some_and(CancellationToken::is_cancelled) {
9390                break;
9391            }
9392            if self.targets_by_file.contains_key(&file) {
9393                continue;
9394            }
9395            let targets = targets_for(&file);
9396            stack.extend(targets.iter().cloned());
9397            self.targets_by_file.insert(file, targets);
9398        }
9399    }
9400
9401    fn files(&self) -> impl Iterator<Item = &ProjectFile> {
9402        self.targets_by_file.keys()
9403    }
9404
9405    fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
9406        self.targets_by_file
9407            .get(file)
9408            .map(Vec::as_slice)
9409            .unwrap_or_default()
9410    }
9411
9412    fn reachable_files(
9413        &self,
9414        root: &ProjectFile,
9415        cancellation: Option<&CancellationToken>,
9416    ) -> HashSet<ProjectFile> {
9417        let mut pending = vec![root.clone()];
9418        let mut visited = HashSet::default();
9419        while let Some(file) = pending.pop() {
9420            if cancellation.is_some_and(CancellationToken::is_cancelled) {
9421                break;
9422            }
9423            if visited.insert(file.clone()) {
9424                pending.extend(self.targets(&file).iter().cloned());
9425            }
9426        }
9427        visited
9428    }
9429}
9430
9431fn build_bounded_visible_declarations(
9432    cpp: &dyn CppSource,
9433    token: QueryToken<'_>,
9434    analyzer: &CppGraphSource<'_>,
9435    roots: &HashSet<ProjectFile>,
9436    visible_sources: &HashMap<ProjectFile, HashSet<ProjectFile>>,
9437    cancellation: Option<&CancellationToken>,
9438    stats: &mut BoundedVisibilityStats,
9439) -> HashMap<ProjectFile, HashSet<CodeUnit>> {
9440    let mut candidates_by_identifier = HashMap::default();
9441    let mut declarations_by_source_and_reading: HashMap<
9442        (ProjectFile, bool),
9443        BoundedVisibilityDeclarations,
9444    > = HashMap::default();
9445    let mut dependency_names_by_unit: HashMap<CodeUnit, HashSet<String>> = HashMap::default();
9446    roots
9447        .iter()
9448        .map(|root| {
9449            let reading_is_c = analyzer.reference_uses_c_semantics(root);
9450            let root_declarations = declarations_by_source_and_reading
9451                .entry((root.clone(), reading_is_c))
9452                .or_insert_with(|| {
9453                    bounded_visibility_declarations(cpp, analyzer, root, reading_is_c, stats)
9454                });
9455            let mut visible = root_declarations
9456                .all
9457                .iter()
9458                .cloned()
9459                .collect::<HashSet<_>>();
9460            let mut pending_names = HashSet::default();
9461            if let Some(prepared) = cpp.prepared_syntax(token, root) {
9462                // One cursor for the whole file walk: `named_child(index)`
9463                // re-steps the sibling list from the first child on every
9464                // access, which made this whole-file scan quadratic in the
9465                // fan-out of each node (#3097).
9466                let mut cursor = prepared.tree().walk();
9467                let mut pending_nodes = vec![prepared.tree().root_node()];
9468                while let Some(node) = pending_nodes.pop() {
9469                    if matches!(
9470                        node.kind(),
9471                        "identifier"
9472                            | "type_identifier"
9473                            | "field_identifier"
9474                            | "namespace_identifier"
9475                    ) {
9476                        pending_names.insert(node_text(node, prepared.source()).to_string());
9477                    }
9478                    if node.kind() == "preproc_arg" {
9479                        for reference in
9480                            object_macro_replacement_type_references(node, prepared.source())
9481                        {
9482                            pending_names.extend(reference.components);
9483                        }
9484                    }
9485                    pending_nodes.extend(node.named_children(&mut cursor));
9486                }
9487            }
9488            stats.root_names += pending_names.len();
9489            let mut completed_names = HashSet::default();
9490            while !pending_names.is_empty() {
9491                stats.rounds += 1;
9492                let round_names = std::mem::take(&mut pending_names);
9493                let mut requested_names_by_source: HashMap<ProjectFile, HashSet<String>> =
9494                    HashMap::default();
9495                let mut identifiers = Vec::new();
9496                for identifier in round_names {
9497                    if !completed_names.insert(identifier.clone())
9498                        || cancellation.is_some_and(CancellationToken::is_cancelled)
9499                    {
9500                        continue;
9501                    }
9502                    identifiers.push(identifier);
9503                }
9504                let missing_identifiers = identifiers
9505                    .iter()
9506                    .filter(|identifier| !candidates_by_identifier.contains_key(*identifier))
9507                    .cloned()
9508                    .collect::<HashSet<_>>();
9509                if !missing_identifiers.is_empty() {
9510                    let lookup_started = Instant::now();
9511                    let mut candidates = cpp
9512                        .visibility_identifier_candidates_batch(&missing_identifiers, cancellation);
9513                    stats.lookup_elapsed += lookup_started.elapsed();
9514                    stats.identifier_lookups += missing_identifiers.len();
9515                    stats.identifier_batches += 1;
9516                    if cancellation.is_some_and(CancellationToken::is_cancelled) {
9517                        break;
9518                    }
9519                    for identifier in missing_identifiers {
9520                        let units = candidates.remove(&identifier).unwrap_or_default();
9521                        let candidate_count = units.len();
9522                        let candidate_sources = units
9523                            .into_iter()
9524                            .map(|unit| unit.source().clone())
9525                            .collect::<HashSet<_>>();
9526                        candidates_by_identifier
9527                            .insert(identifier, (candidate_sources, candidate_count));
9528                    }
9529                }
9530                for identifier in identifiers {
9531                    let (candidate_sources, candidate_count) = candidates_by_identifier
9532                        .get(&identifier)
9533                        .expect("every missing identifier was inserted after the batch lookup");
9534                    stats.candidate_units += *candidate_count;
9535                    for source in candidate_sources.iter().cloned() {
9536                        if source != *root
9537                            && visible_sources
9538                                .get(root)
9539                                .is_some_and(|files| files.contains(&source))
9540                        {
9541                            requested_names_by_source
9542                                .entry(source)
9543                                .or_default()
9544                                .insert(identifier.clone());
9545                        }
9546                    }
9547                }
9548                stats.candidate_sources += requested_names_by_source.len();
9549                for (source, requested_names) in requested_names_by_source {
9550                    let declarations = declarations_by_source_and_reading
9551                        .entry((source.clone(), reading_is_c))
9552                        .or_insert_with(|| {
9553                            bounded_visibility_declarations(
9554                                cpp,
9555                                analyzer,
9556                                &source,
9557                                reading_is_c,
9558                                stats,
9559                            )
9560                        });
9561                    let mut selected = HashSet::default();
9562                    for name in requested_names {
9563                        if let Some(units) = declarations.by_identifier.get(&name) {
9564                            selected.extend(units.iter().cloned());
9565                        }
9566                    }
9567                    for unit in selected {
9568                        stats.selected_units += 1;
9569                        let dependency_names = dependency_names_by_unit
9570                            .entry(unit.clone())
9571                            .or_insert_with(|| {
9572                                let facts_started = Instant::now();
9573                                let mut dependency_names = HashSet::default();
9574                                if let Some(facts) = analyzer.declaration_source_properties(&unit) {
9575                                    for fact in facts {
9576                                        for name in fact.dependency_type_names {
9577                                            stats.dependency_source_names += 1;
9578                                            #[cfg(any(test, feature = "test-support"))]
9579                                            BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT
9580                                                .with(|count| count.set(count.get() + 1));
9581                                            dependency_names.insert(name);
9582                                        }
9583                                    }
9584                                    stats.dependency_source_elapsed += facts_started.elapsed();
9585                                }
9586                                dependency_names
9587                            });
9588                        for name in dependency_names.iter() {
9589                            if !completed_names.contains(name) && pending_names.insert(name.clone())
9590                            {
9591                                stats.dependency_names += 1;
9592                            }
9593                        }
9594                        visible.insert(unit);
9595                    }
9596                }
9597            }
9598            (root.clone(), visible)
9599        })
9600        .collect()
9601}
9602
9603struct BoundedVisibilityDeclarations {
9604    all: Vec<CodeUnit>,
9605    by_identifier: HashMap<String, Vec<CodeUnit>>,
9606}
9607
9608fn bounded_visibility_declarations(
9609    cpp: &dyn CppSource,
9610    analyzer: &CppGraphSource<'_>,
9611    file: &ProjectFile,
9612    c_semantics: bool,
9613    stats: &mut BoundedVisibilityStats,
9614) -> BoundedVisibilityDeclarations {
9615    let declarations_started = Instant::now();
9616    let all = bounded_visibility_declarations_in_reading(analyzer, file, c_semantics)
9617        .into_iter()
9618        .collect::<Vec<_>>();
9619    stats.declaration_elapsed += declarations_started.elapsed();
9620    stats.declaration_reads += 1;
9621    stats.declaration_units += all.len();
9622
9623    let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
9624    for unit in &all {
9625        by_identifier
9626            .entry(unit.identifier().to_string())
9627            .or_default()
9628            .push(unit.clone());
9629        if unit.is_class()
9630            && let Some(metadata) = cpp.template_metadata(unit)
9631            && metadata.primary_name != unit.identifier()
9632        {
9633            by_identifier
9634                .entry(metadata.primary_name)
9635                .or_default()
9636                .push(unit.clone());
9637        }
9638    }
9639    BoundedVisibilityDeclarations { all, by_identifier }
9640}
9641
9642#[derive(Default)]
9643struct BoundedVisibilityStats {
9644    rounds: usize,
9645    root_names: usize,
9646    identifier_lookups: usize,
9647    identifier_batches: usize,
9648    candidate_units: usize,
9649    candidate_sources: usize,
9650    declaration_reads: usize,
9651    declaration_units: usize,
9652    selected_units: usize,
9653    dependency_source_names: usize,
9654    dependency_names: usize,
9655    lookup_elapsed: Duration,
9656    declaration_elapsed: Duration,
9657    dependency_source_elapsed: Duration,
9658}
9659
9660fn bounded_visibility_declarations_in_reading(
9661    analyzer: &CppGraphSource<'_>,
9662    file: &ProjectFile,
9663    c_semantics: bool,
9664) -> BTreeSet<CodeUnit> {
9665    #[cfg(any(test, feature = "test-support"))]
9666    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(count.get() + 1));
9667    analyzer.declarations_in_reading(file, c_semantics)
9668}
9669
9670#[cfg(any(test, feature = "test-support"))]
9671pub fn reset_bounded_visibility_declaration_read_count_for_test() {
9672    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(0));
9673}
9674
9675#[cfg(any(test, feature = "test-support"))]
9676pub fn bounded_visibility_declaration_read_count_for_test() -> usize {
9677    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(Cell::get)
9678}
9679
9680#[cfg(any(test, feature = "test-support"))]
9681pub fn reset_bounded_visibility_dependency_ast_node_count_for_test() {
9682    BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(|count| count.set(0));
9683}
9684
9685#[cfg(any(test, feature = "test-support"))]
9686pub fn bounded_visibility_dependency_ast_node_count_for_test() -> usize {
9687    BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(Cell::get)
9688}
9689
9690pub struct VisibilityData {
9691    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
9692    pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
9693}
9694
9695/// Build the per-root include closure and the declarations each root can see
9696/// through it.
9697///
9698/// `declarations_for` takes the reading to answer in (issue #1970): a root
9699/// compiled as C sees the C reading of every file in its closure, a root
9700/// compiled as C++ sees the C++ reading, and `reading_is_c_for` decides which
9701/// per root. The two readings agree for all but a handful of headers, so the
9702/// C map is built only when some root actually asks for it, and only over the
9703/// files that root reaches.
9704pub fn build_visibility_data<F, R, D>(
9705    roots: &HashSet<ProjectFile>,
9706    cancellation: Option<&CancellationToken>,
9707    mut targets_for: F,
9708    mut reading_is_c_for: R,
9709    mut declarations_for: D,
9710) -> VisibilityData
9711where
9712    F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9713    R: FnMut(&ProjectFile) -> bool,
9714    D: FnMut(&ProjectFile, bool) -> BTreeSet<CodeUnit>,
9715{
9716    let mut include_graph = IncludeGraph::default();
9717    for file in roots {
9718        if cancellation.is_some_and(CancellationToken::is_cancelled) {
9719            break;
9720        }
9721        include_graph.extend_with(file, cancellation, &mut targets_for);
9722    }
9723    let cpp_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
9724        .files()
9725        .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
9726        .map(|file| (file.clone(), declarations_for(file, false)))
9727        .collect();
9728    let mut c_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = HashMap::default();
9729    let mut visible_by_file = HashMap::default();
9730    let mut visible_source_files_by_root = HashMap::default();
9731    for file in roots {
9732        if cancellation.is_some_and(CancellationToken::is_cancelled) {
9733            break;
9734        }
9735        let mut visited = HashSet::default();
9736        let mut visible = HashSet::default();
9737        let declarations_by_file = if reading_is_c_for(file) {
9738            for reached in cpp_declarations_by_file.keys() {
9739                if !c_declarations_by_file.contains_key(reached) {
9740                    let declarations = declarations_for(reached, true);
9741                    c_declarations_by_file.insert(reached.clone(), declarations);
9742                }
9743            }
9744            &c_declarations_by_file
9745        } else {
9746            &cpp_declarations_by_file
9747        };
9748        collect_visible_declarations(
9749            &include_graph,
9750            declarations_by_file,
9751            file,
9752            &mut visited,
9753            &mut visible,
9754            cancellation,
9755        );
9756        visible_by_file.insert(file.clone(), visible);
9757        visible_source_files_by_root.insert(file.clone(), visited);
9758    }
9759    VisibilityData {
9760        visible_by_file,
9761        visible_source_files_by_root,
9762    }
9763}
9764
9765/// Admit the class that an out-of-line definition proves is in scope.
9766///
9767/// `Owner::member(...) { ... }` in a file is structured proof that `Owner`
9768/// names a class-like entity in that file's scope: a member declaration can
9769/// live in a file other than its class's only when it is written out of line.
9770/// A file a build concatenates rather than compiles carries no `#include` edge
9771/// to the header declaring `Owner` -- google/wuffs
9772/// `internal/cgen/auxiliary/image.cc` defines
9773/// `DecodeImageResult::DecodeImageResult` and never includes `image.hh` -- so
9774/// every unqualified member and constructor reference in it had no candidate at
9775/// all (#1832).
9776///
9777/// The evidence is the indexed declaration's own owner name, taken from its
9778/// `FqName`, so this stays a structured answer rather than a text fallback.
9779/// Only an owner the file cannot already see is admitted: that is what keeps a
9780/// header declaring its own class from additionally seeing every same-named
9781/// class in the workspace, and it makes the pass free for the ordinary file
9782/// whose owners are all visible.
9783#[derive(Default)]
9784struct OutOfLineOwnerBindingStats {
9785    unseen_owners: usize,
9786    definition_lookups: usize,
9787    admitted: usize,
9788}
9789
9790fn extend_with_out_of_line_owner_bindings(
9791    cpp: &dyn CppSource,
9792    visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
9793) -> OutOfLineOwnerBindingStats {
9794    let mut stats = OutOfLineOwnerBindingStats::default();
9795    for (file, visible) in visible_by_file.iter_mut() {
9796        // The include-closure walk seeds every root with its own declarations,
9797        // so the file's members are already here; re-reading them from the
9798        // analyzer would pay for the same declaration set twice.
9799        let mut unseen_owners: HashSet<String> = visible
9800            .iter()
9801            .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
9802            .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
9803            .collect();
9804        if unseen_owners.is_empty() {
9805            continue;
9806        }
9807        for unit in visible.iter().filter(|unit| unit.is_class()) {
9808            unseen_owners.remove(&unit.fq_name());
9809        }
9810        stats.unseen_owners += unseen_owners.len();
9811        stats.definition_lookups += unseen_owners.len();
9812        let admitted = unseen_owners
9813            .iter()
9814            .flat_map(|owner| cpp.definitions(owner))
9815            .filter(CodeUnit::is_class)
9816            .collect::<Vec<_>>();
9817        stats.admitted += admitted.len();
9818        visible.extend(admitted);
9819    }
9820    stats
9821}
9822
9823pub enum VisibleMemberResolution {
9824    Callable(Vec<CodeUnit>),
9825    NonCallable,
9826    AmbiguousKind,
9827    Missing,
9828}
9829
9830#[derive(Clone)]
9831pub enum EnclosingMemberOwnerResolution {
9832    Owner(CodeUnit),
9833    Ambiguous,
9834    Missing,
9835}
9836
9837pub fn resolve_declaring_member_owner(
9838    analyzer: &CppGraphSource<'_>,
9839    visibility: &VisibilityIndex<'_>,
9840    file: &ProjectFile,
9841    receiver_owner: &CodeUnit,
9842    member_name: &str,
9843) -> EnclosingMemberOwnerResolution {
9844    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
9845        return EnclosingMemberOwnerResolution::Missing;
9846    };
9847    let Some(receiver_owner) =
9848        visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
9849    else {
9850        return EnclosingMemberOwnerResolution::Ambiguous;
9851    };
9852    let resolve_level = |frontier: &[CodeUnit]| {
9853        let mut member_owners = Vec::new();
9854        for raw_owner in frontier {
9855            let Some(owner) =
9856                visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
9857            else {
9858                return EnclosingMemberOwnerResolution::Ambiguous;
9859            };
9860            for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
9861                // A type nested in an owner shares the owner's member-name
9862                // index, but it cannot be the value receiver of `owner.name`.
9863                // Keep value members here so an anonymous aggregate field
9864                // does not become ambiguous with its promoted receiver type.
9865                if !member.is_field() && !member.is_function() {
9866                    continue;
9867                }
9868                let Some(member_owner) = type_owner_of(analyzer, member) else {
9869                    return EnclosingMemberOwnerResolution::Ambiguous;
9870                };
9871                if !member_owners
9872                    .iter()
9873                    .any(|existing| same_visible_symbol(existing, &member_owner))
9874                {
9875                    member_owners.push(member_owner);
9876                }
9877            }
9878        }
9879        match member_owners.len() {
9880            0 => EnclosingMemberOwnerResolution::Missing,
9881            1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
9882            _ => EnclosingMemberOwnerResolution::Ambiguous,
9883        }
9884    };
9885    // The first declaration on each structured base path hides deeper names,
9886    // regardless of whether its callable overload is applicable at a particular
9887    // call site. Applicability is checked only after this owner is established.
9888    let direct = resolve_level(std::slice::from_ref(&receiver_owner));
9889    if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
9890        return direct;
9891    }
9892    let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
9893    let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
9894    let mut path_matches = Vec::new();
9895    while let Some(raw_owner) = stack.pop() {
9896        let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
9897        else {
9898            return EnclosingMemberOwnerResolution::Ambiguous;
9899        };
9900        // Persisted hierarchy edges do not encode virtual-base or base-subobject paths.
9901        // Propagate at most two occurrences of each owner: that preserves the distinction
9902        // between one and multiple resolving base paths without exponential diamond walks.
9903        let propagated = propagated_counts.entry(owner.clone()).or_default();
9904        if *propagated == 2 {
9905            continue;
9906        }
9907        *propagated += 1;
9908        match resolve_level(std::slice::from_ref(&owner)) {
9909            EnclosingMemberOwnerResolution::Owner(owner) => {
9910                path_matches.push(owner);
9911                if path_matches.len() == 2 {
9912                    return EnclosingMemberOwnerResolution::Ambiguous;
9913                }
9914            }
9915            EnclosingMemberOwnerResolution::Ambiguous => {
9916                return EnclosingMemberOwnerResolution::Ambiguous;
9917            }
9918            EnclosingMemberOwnerResolution::Missing => {
9919                stack.extend(hierarchy.get_direct_ancestors(&owner));
9920            }
9921        }
9922    }
9923    match path_matches.len() {
9924        0 => EnclosingMemberOwnerResolution::Missing,
9925        1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
9926        _ => unreachable!("base-path matches are capped at one before returning"),
9927    }
9928}
9929
9930/// Resolve the declaring owner of a callable after applying a member
9931/// `using <Base>::<member>;` declaration to one exact call arity.
9932///
9933/// Ordinary member lookup is intentionally name-based: the first class that
9934/// declares a name hides the same name on deeper bases. A member
9935/// using-declaration is the one exception. When none of the declarations on
9936/// that first owner accepts the call arity, it can reintroduce an applicable
9937/// overload from the named base. If a declaration on the first owner does
9938/// accept the arity, argument types would be needed to choose between it and
9939/// a same-arity introduced overload, so this resolver conservatively keeps the
9940/// ordinary owner (#1835/#1843).
9941///
9942/// The caller supplies ordinary name-based owner resolution so a file scan can
9943/// reuse its existing owner cache before applying this callable-only exception.
9944pub fn resolve_declaring_callable_owner(
9945    analyzer: &CppGraphSource<'_>,
9946    visibility: &VisibilityIndex<'_>,
9947    file: &ProjectFile,
9948    ordinary: EnclosingMemberOwnerResolution,
9949    member_name: &str,
9950    call_arity: usize,
9951) -> EnclosingMemberOwnerResolution {
9952    let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
9953        return ordinary;
9954    };
9955    if visibility
9956        .visible_members_for_owner_name(file, ordinary_owner, member_name)
9957        .into_iter()
9958        .any(|unit| {
9959            unit.is_function()
9960                && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(call_arity))
9961        })
9962    {
9963        return ordinary;
9964    }
9965
9966    let mut pending = match member_using_declaration_bases(
9967        analyzer,
9968        visibility,
9969        file,
9970        ordinary_owner,
9971        member_name,
9972    ) {
9973        Ok(bases) => bases,
9974        Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
9975    };
9976    let mut visited = HashSet::default();
9977    let mut introduced_owners = Vec::new();
9978    while let Some(owner) = pending.pop() {
9979        if !visited.insert(owner.clone()) {
9980            continue;
9981        }
9982        let accepts_arity = visibility
9983            .visible_members_for_owner_name(file, &owner, member_name)
9984            .into_iter()
9985            .any(|unit| {
9986                unit.is_function()
9987                    && cpp_callable_arity(analyzer, unit)
9988                        .is_none_or(|known| known.accepts(call_arity))
9989            });
9990        if accepts_arity {
9991            if !introduced_owners
9992                .iter()
9993                .any(|existing| same_visible_symbol(existing, &owner))
9994            {
9995                introduced_owners.push(owner);
9996            }
9997            continue;
9998        }
9999        match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
10000            Ok(bases) => pending.extend(bases),
10001            Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
10002        }
10003    }
10004    match introduced_owners.as_slice() {
10005        [] => ordinary,
10006        [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
10007        _ => EnclosingMemberOwnerResolution::Ambiguous,
10008    }
10009}
10010
10011fn member_using_declaration_bases(
10012    analyzer: &CppGraphSource<'_>,
10013    visibility: &VisibilityIndex<'_>,
10014    file: &ProjectFile,
10015    owner: &CodeUnit,
10016    member_name: &str,
10017) -> Result<Vec<CodeUnit>, ()> {
10018    let Some(facts) = analyzer.declaration_source_properties(owner) else {
10019        return Err(());
10020    };
10021    let scopes: Vec<_> = facts
10022        .iter()
10023        .flat_map(|fact| &fact.member_usings)
10024        .filter(|using| using.member == member_name)
10025        .map(|using| using.scope.join("::"))
10026        .collect();
10027    if scopes.is_empty() {
10028        return Ok(Vec::new());
10029    }
10030    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
10031        return Ok(Vec::new());
10032    };
10033    let mut bases = Vec::new();
10034    for raw_ancestor in hierarchy.get_ancestors(owner) {
10035        let Some(ancestor) =
10036            visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
10037        else {
10038            return Err(());
10039        };
10040        let qualified = cpp_name_for(&ancestor);
10041        if scopes
10042            .iter()
10043            .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
10044            && !bases
10045                .iter()
10046                .any(|existing| same_visible_symbol(existing, &ancestor))
10047        {
10048            bases.push(ancestor);
10049        }
10050    }
10051    Ok(bases)
10052}
10053
10054pub fn lexical_component_tiers<'a>(
10055    components: &'a [String],
10056    global: bool,
10057    lexical_scope: &'a [String],
10058) -> impl Iterator<Item = Vec<String>> + 'a {
10059    let first_prefix_len = if global { 0 } else { lexical_scope.len() };
10060    (0..=first_prefix_len).rev().map(move |prefix_len| {
10061        let mut qualified = Vec::with_capacity(prefix_len + components.len());
10062        qualified.extend_from_slice(&lexical_scope[..prefix_len]);
10063        qualified.extend_from_slice(components);
10064        qualified
10065    })
10066}
10067
10068pub fn build_visible_identifier_index(
10069    analyzer: &CppGraphSource<'_>,
10070    visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
10071    visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
10072    global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
10073) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
10074    let mut out = HashMap::default();
10075    for (file, visible) in visible_by_file {
10076        let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
10077        for unit in visible {
10078            if unit.is_field()
10079                && !visible_source_files_by_root
10080                    .get(file)
10081                    .is_some_and(|sources| sources.contains(unit.source()))
10082                && cpp_global_field_has_internal_linkage_cached(
10083                    analyzer,
10084                    global_field_internal_linkage,
10085                    unit,
10086                )
10087            {
10088                continue;
10089            }
10090            by_identifier
10091                .entry(unit.identifier().to_string())
10092                .or_default()
10093                .push(unit.clone());
10094        }
10095        for units in by_identifier.values_mut() {
10096            sort_lookup_units(units);
10097            units.dedup();
10098        }
10099        out.insert(file.clone(), by_identifier);
10100    }
10101    out
10102}
10103
10104fn sort_lookup_units(units: &mut [CodeUnit]) {
10105    units.sort_by(|left, right| {
10106        left.fq_name()
10107            .cmp(&right.fq_name())
10108            .then_with(|| left.signature().cmp(&right.signature()))
10109            .then_with(|| left.source().cmp(right.source()))
10110            .then_with(|| left.kind().cmp(&right.kind()))
10111            .then_with(|| {
10112                left.package_segment_count()
10113                    .cmp(&right.package_segment_count())
10114            })
10115            .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
10116            .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
10117    });
10118}
10119
10120fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
10121    let interner = segment_interner();
10122    for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
10123        let (left_text, left_kind) = interner.resolve(left_id);
10124        let (right_text, right_kind) = interner.resolve(right_id);
10125        let order = left_text
10126            .cmp(right_text)
10127            .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
10128        if order != CmpOrdering::Equal {
10129            return order;
10130        }
10131    }
10132    left.len().cmp(&right.len())
10133}
10134
10135const fn segment_kind_order(kind: SegmentKind) -> u8 {
10136    match kind {
10137        SegmentKind::Path => 0,
10138        SegmentKind::Package => 1,
10139        SegmentKind::Type => 2,
10140        SegmentKind::Companion => 3,
10141        SegmentKind::Nested => 4,
10142        SegmentKind::Member => 5,
10143        SegmentKind::Unknown => 6,
10144    }
10145}
10146
10147fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
10148    *units = brokk_bifrost_core::analyzer::usages::inverted_edges::dedup_preserving_order(
10149        std::mem::take(units),
10150    );
10151}
10152
10153pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
10154    // Same domain as `candidate_units` above: `reference` is a plain
10155    // `::`-joined qualified-id with operator tokens kept intact by the shared
10156    // splitter's operator merge.
10157    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10158        brokk_bifrost_core::analyzer::Language::Cpp,
10159        reference,
10160    );
10161    cpp_reference_fqn_candidates_from_parts(&parts, kind)
10162}
10163
10164fn cpp_reference_fqn_candidates_from_parts(parts: &[String], kind: TargetKind) -> Vec<String> {
10165    if parts.is_empty() {
10166        return Vec::new();
10167    }
10168
10169    let mut candidates = Vec::new();
10170    for package_len in 0..parts.len() {
10171        let package = parts[..package_len].join("::");
10172        let rest = &parts[package_len..];
10173        if rest.is_empty() {
10174            continue;
10175        }
10176        match kind {
10177            TargetKind::Type | TargetKind::Constructor => {
10178                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
10179                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10180            }
10181            TargetKind::FreeFunction
10182            | TargetKind::Method
10183            | TargetKind::GlobalField
10184            | TargetKind::MemberField
10185            | TargetKind::Macro => {
10186                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10187                if rest.len() > 1 {
10188                    let owner = rest[..rest.len() - 1].join("$");
10189                    let short = format!("{}.{}", owner, rest[rest.len() - 1]);
10190                    push_cpp_fqn_candidate(&mut candidates, &package, &short);
10191                }
10192            }
10193        }
10194    }
10195    candidates
10196}
10197
10198fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
10199    let fqn = if package.is_empty() {
10200        short.to_string()
10201    } else {
10202        format!("{package}.{short}")
10203    };
10204    if !out.contains(&fqn) {
10205        out.push(fqn);
10206    }
10207}
10208
10209pub fn infer_cpp_initializer_type(
10210    analyzer: &CppGraphSource<'_>,
10211    visibility: &VisibilityIndex<'_>,
10212    file: &ProjectFile,
10213    source: &str,
10214    node: Node<'_>,
10215) -> Option<CodeUnit> {
10216    infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
10217        .and_then(|binding| binding.unit)
10218}
10219
10220pub fn infer_cpp_initializer_binding(
10221    analyzer: &CppGraphSource<'_>,
10222    visibility: &VisibilityIndex<'_>,
10223    file: &ProjectFile,
10224    source: &str,
10225    node: Node<'_>,
10226    receiver_resolver: Option<&ReceiverResolver<'_>>,
10227) -> Option<CppScanBinding> {
10228    match node.kind() {
10229        "new_expression" => {
10230            let type_node = cpp_new_expression_type_node(node)?;
10231            let type_text = node_text(type_node, source);
10232            let name = normalize_cpp_type_name(type_text);
10233            Some(CppScanBinding::from_type_name(
10234                name.clone(),
10235                visibility.resolve_type(file, &name),
10236                1,
10237            ))
10238        }
10239        "call_expression" => node.child_by_field_name("function").and_then(|function| {
10240            // `a().b()` and `p->b()` invoke a member on a receiver *value*. The
10241            // callee's source text is an expression, not a name, and every name
10242            // lookup below normalizes a reference by truncating at the first
10243            // `(`: `first().second` would read as `first`, so the chained call
10244            // would take the type of `first()` instead of the type of
10245            // `first().second()` (#2178). Only the member path can answer for
10246            // this shape, so route to it from the callee's node kind.
10247            if function.kind() == "field_expression" {
10248                let arity = visibility.call_arity_evidence(file, node, source).exact()?;
10249                return resolve_field_method_call_return_binding(
10250                    analyzer,
10251                    visibility,
10252                    file,
10253                    source,
10254                    function,
10255                    arity,
10256                    receiver_resolver,
10257                );
10258            }
10259            let function_text = node_text(function, source);
10260            let direct_type_binding = visibility
10261                .resolve_type(file, function_text)
10262                .map(|unit| CppScanBinding::from_unit(unit, 0));
10263            if function.kind() == "template_function" && direct_type_binding.is_some() {
10264                let lexical_namespace = enclosing_namespace_context(node, source);
10265                let arity = visibility.call_arity_evidence(file, node, source).exact();
10266                if let Some(arity) = arity
10267                    && let Some(binding) = visibility.resolve_call_return_binding(
10268                        analyzer,
10269                        file,
10270                        function_text,
10271                        arity,
10272                        lexical_namespace.as_deref(),
10273                        direct_type_binding
10274                            .as_ref()
10275                            .and_then(|binding| binding.unit.as_ref()),
10276                    )
10277                {
10278                    return Some(binding);
10279                }
10280                let (has_callable, callable_binding) = visibility
10281                    .resolve_call_return_binding_without_arity(
10282                        analyzer,
10283                        file,
10284                        function_text,
10285                        lexical_namespace.as_deref(),
10286                        direct_type_binding
10287                            .as_ref()
10288                            .and_then(|binding| binding.unit.as_ref()),
10289                    );
10290                if let Some(binding) = callable_binding {
10291                    return Some(binding);
10292                }
10293                if has_callable {
10294                    return None;
10295                }
10296                return direct_type_binding;
10297            }
10298            // Only the return-typed branches need the argument count. An
10299            // unknown arity leaves them out, exactly as in the template arm
10300            // above, and still constructs the direct type: `File(getPath())`
10301            // names `File` whether or not `getPath()`'s expansion is provable.
10302            let arity = visibility.call_arity_evidence(file, node, source).exact();
10303            if let Some(arity) = arity {
10304                let direct_type_binding_for_call = direct_type_binding.clone();
10305                if let Some(binding) = resolve_static_method_call_return_binding(
10306                    analyzer, visibility, file, source, function, arity,
10307                )
10308                .or_else(|| {
10309                    // An applicable free function supplies the receiver value
10310                    // before an unrelated visible type with the same terminal
10311                    // name. The direct type still excludes its own constructor
10312                    // declaration below and remains the construction fallback.
10313                    visibility.resolve_call_return_binding(
10314                        analyzer,
10315                        file,
10316                        function_text,
10317                        arity,
10318                        enclosing_namespace_context(node, source).as_deref(),
10319                        direct_type_binding_for_call
10320                            .as_ref()
10321                            .and_then(|binding| binding.unit.as_ref()),
10322                    )
10323                }) {
10324                    return Some(binding);
10325                }
10326            }
10327            direct_type_binding
10328        }),
10329        _ => None,
10330    }
10331}
10332
10333/// Return the type specifier carried by a C++ `new_expression`.
10334///
10335/// The grammar names this child with the `type` field. If recovery omits that
10336/// field, recover only from type-specifier nodes retained in the parse tree;
10337/// skip placement arguments, the array declarator, and initializer arguments.
10338pub fn cpp_new_expression_type_node(expression: Node<'_>) -> Option<Node<'_>> {
10339    debug_assert_eq!(expression.kind(), "new_expression");
10340    if let Some(type_node) = expression.child_by_field_name("type") {
10341        return Some(type_node);
10342    }
10343
10344    let mut pending = Vec::new();
10345    let mut cursor = expression.walk();
10346    pending.extend(
10347        expression
10348            .named_children(&mut cursor)
10349            .filter(|child| {
10350                !matches!(
10351                    child.kind(),
10352                    "argument_list" | "initializer_list" | "new_declarator"
10353                )
10354            })
10355            .collect::<Vec<_>>()
10356            .into_iter()
10357            .rev(),
10358    );
10359    while let Some(node) = pending.pop() {
10360        if is_cpp_type_specifier_node(node) {
10361            return Some(node);
10362        }
10363        if node.kind() == "ERROR" {
10364            let mut cursor = node.walk();
10365            pending.extend(
10366                node.named_children(&mut cursor)
10367                    .collect::<Vec<_>>()
10368                    .into_iter()
10369                    .rev(),
10370            );
10371        }
10372    }
10373    None
10374}
10375
10376fn is_cpp_type_specifier_node(node: Node<'_>) -> bool {
10377    matches!(
10378        node.kind(),
10379        "class_specifier"
10380            | "decltype"
10381            | "dependent_type"
10382            | "enum_specifier"
10383            | "placeholder_type_specifier"
10384            | "primitive_type"
10385            | "qualified_identifier"
10386            | "sized_type_specifier"
10387            | "struct_specifier"
10388            | "template_type"
10389            | "type_identifier"
10390            | "union_specifier"
10391    )
10392}
10393
10394fn resolve_static_method_call_return_binding(
10395    analyzer: &CppGraphSource<'_>,
10396    visibility: &VisibilityIndex<'_>,
10397    file: &ProjectFile,
10398    source: &str,
10399    function: Node<'_>,
10400    arity: usize,
10401) -> Option<CppScanBinding> {
10402    if function.kind() != "qualified_identifier" {
10403        return None;
10404    }
10405    let qualified = normalize_cpp_reference_text(node_text(function, source));
10406    // A C++ qualified-id is `::`-joined with no embedded delimiters in any
10407    // single component (the shared splitter's operator-token merge keeps
10408    // `operator+`-style names intact), so re-tokenizing with the shared
10409    // structured splitter and peeling the terminal segment reproduces
10410    // `rsplit_once("::")`'s (owner, member) split exactly — same shape as
10411    // `cpp_out_of_line_function_owner`'s `qualified` split above.
10412    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10413        brokk_bifrost_core::analyzer::Language::Cpp,
10414        &qualified,
10415    );
10416    let (owner_text, member_name) = match parts.split_last() {
10417        Some((member, owner_parts)) if !owner_parts.is_empty() => {
10418            (owner_parts.join("::"), member.clone())
10419        }
10420        _ => {
10421            let scope = function.child_by_field_name("scope")?;
10422            let name = function.child_by_field_name("name")?;
10423            (
10424                node_text(scope, source).to_string(),
10425                node_text(name, source).to_string(),
10426            )
10427        }
10428    };
10429    let owner = visibility.resolve_type(file, &owner_text)?;
10430    let candidates =
10431        callable_members_accepting_arity(analyzer, visibility, file, &owner, &member_name, arity);
10432    unanimous_return_binding(analyzer, visibility, file, &candidates)
10433}
10434
10435fn resolve_field_method_call_return_binding(
10436    analyzer: &CppGraphSource<'_>,
10437    visibility: &VisibilityIndex<'_>,
10438    file: &ProjectFile,
10439    source: &str,
10440    function: Node<'_>,
10441    arity: usize,
10442    receiver_resolver: Option<&ReceiverResolver<'_>>,
10443) -> Option<CppScanBinding> {
10444    debug_assert_eq!(
10445        function.kind(),
10446        "field_expression",
10447        "the member-call return binding answers only for a field-expression callee"
10448    );
10449    let receiver_resolver = receiver_resolver?;
10450    let field = function.child_by_field_name("field")?;
10451    let member_name = node_text(function_terminal_node(field), source);
10452    let receiver = function
10453        .child_by_field_name("argument")
10454        .or_else(|| function.named_child(0))?;
10455    let owners = receiver_resolver(receiver, source);
10456    let mut candidates = Vec::new();
10457    for owner in owners {
10458        let declaring_owner =
10459            match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
10460                EnclosingMemberOwnerResolution::Owner(owner) => owner,
10461                EnclosingMemberOwnerResolution::Missing => continue,
10462                EnclosingMemberOwnerResolution::Ambiguous => return None,
10463            };
10464        candidates.extend(callable_members_accepting_arity(
10465            analyzer,
10466            visibility,
10467            file,
10468            &declaring_owner,
10469            member_name,
10470            arity,
10471        ));
10472    }
10473    unanimous_return_binding(analyzer, visibility, file, &candidates)
10474}
10475
10476fn callable_members_accepting_arity(
10477    analyzer: &CppGraphSource<'_>,
10478    visibility: &VisibilityIndex<'_>,
10479    file: &ProjectFile,
10480    owner: &CodeUnit,
10481    member_name: &str,
10482    arity: usize,
10483) -> Vec<CodeUnit> {
10484    visibility
10485        .visible_members_for_owner_name(file, owner, member_name)
10486        .into_iter()
10487        .filter(|unit| {
10488            unit.is_function()
10489                && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(arity))
10490        })
10491        .cloned()
10492        .collect()
10493}
10494
10495fn unanimous_return_binding(
10496    analyzer: &CppGraphSource<'_>,
10497    visibility: &VisibilityIndex<'_>,
10498    file: &ProjectFile,
10499    candidates: &[CodeUnit],
10500) -> Option<CppScanBinding> {
10501    let mut resolved_return: Option<CppScanBinding> = None;
10502    for function in candidates {
10503        let metadata = analyzer.signature_metadata(function);
10504        let return_types = if metadata.is_empty() {
10505            vec![cpp_function_return_type_text(analyzer, function)?]
10506        } else {
10507            metadata
10508                .iter()
10509                .map(|metadata| metadata.return_type_text().map(str::to_string))
10510                .collect::<Option<Vec<_>>>()?
10511        };
10512        for return_text in return_types {
10513            let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
10514            let name = normalize_cpp_type_name(&return_text);
10515            let binding = CppScanBinding::from_type_name(
10516                name.clone(),
10517                visibility
10518                    .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
10519                indirection,
10520            );
10521            if let Some(existing) = resolved_return.as_ref()
10522                && (existing.indirection != binding.indirection
10523                    || match (&existing.unit, &binding.unit) {
10524                        (Some(left), Some(right)) => !same_visible_symbol(left, right),
10525                        (None, None) => existing.type_name != binding.type_name,
10526                        (Some(_), None) | (None, Some(_)) => true,
10527                    })
10528            {
10529                return None;
10530            }
10531            resolved_return = Some(binding);
10532        }
10533    }
10534    resolved_return
10535}
10536
10537fn aliases_from_canonical_source(
10538    cpp: &dyn CppSource,
10539    token: QueryToken<'_>,
10540    file: &ProjectFile,
10541) -> Option<Vec<CppAlias>> {
10542    let source = cpp.declaration_source_facts(token, file)?;
10543    Some(
10544        source
10545            .facts
10546            .declarations
10547            .iter()
10548            .filter_map(|fact| {
10549                let alias = fact.file_scope_alias.as_ref()?;
10550                Some(CppAlias {
10551                    name: alias.name.clone(),
10552                    target: alias.target.clone(),
10553                    namespace: alias.namespace.clone(),
10554                })
10555            })
10556            .collect(),
10557    )
10558}
10559
10560pub(crate) fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
10561    let mut current = node.parent();
10562    while let Some(parent) = current {
10563        match parent.kind() {
10564            "translation_unit"
10565            | "namespace_definition"
10566            | "declaration_list"
10567            | "linkage_specification" => current = parent.parent(),
10568            "template_declaration" => current = parent.parent(),
10569            _ => return false,
10570        }
10571    }
10572    true
10573}
10574
10575pub fn collect_include_closure(
10576    analyzer: &CppGraphSource<'_>,
10577    include_targets: &IncludeTargetIndex,
10578    file: &ProjectFile,
10579    out: &mut HashSet<ProjectFile>,
10580    cancellation: Option<&CancellationToken>,
10581) {
10582    let mut stack = vec![file.clone()];
10583    while let Some(file) = stack.pop() {
10584        if cancellation.is_some_and(CancellationToken::is_cancelled) {
10585            break;
10586        }
10587        if !out.insert(file.clone()) {
10588            continue;
10589        }
10590        for include in analyzer.canonical_include_paths(&file).unwrap_or_default() {
10591            for target in resolve_include_targets_with_index(&file, &include, include_targets) {
10592                stack.push(target);
10593            }
10594        }
10595    }
10596}
10597
10598fn collect_visible_declarations(
10599    include_graph: &IncludeGraph,
10600    declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
10601    file: &ProjectFile,
10602    visited: &mut HashSet<ProjectFile>,
10603    out: &mut HashSet<CodeUnit>,
10604    cancellation: Option<&CancellationToken>,
10605) {
10606    let mut stack = vec![file.clone()];
10607    while let Some(file) = stack.pop() {
10608        if cancellation.is_some_and(CancellationToken::is_cancelled) {
10609            break;
10610        }
10611        if !visited.insert(file.clone()) {
10612            continue;
10613        }
10614        if let Some(declarations) = declarations_by_file.get(&file) {
10615            out.extend(declarations.iter().cloned());
10616        }
10617        stack.extend(include_graph.targets(&file).iter().cloned());
10618    }
10619}
10620
10621fn parse_macro_parameter_list<T>(
10622    replacement: &str,
10623    read: impl FnOnce(Node<'_>, &str) -> T,
10624) -> Option<T> {
10625    let source = format!("void __bifrost_macro_parameters({replacement});");
10626    let mut parser = Parser::new();
10627    parser
10628        .set_language(&tree_sitter_cpp::LANGUAGE.into())
10629        .ok()?;
10630    let tree = parser.parse(&source, None)?;
10631    let root = tree.root_node();
10632    if root.has_error() {
10633        return None;
10634    }
10635    let declaration = root.named_child(0)?;
10636    let declarator = declaration.child_by_field_name("declarator")?;
10637    let parameters = declarator.child_by_field_name("parameters")?;
10638    Some(read(parameters, &source))
10639}
10640
10641fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
10642    parse_macro_parameter_list(replacement, crate::declarations::cpp_callable_arity)
10643}
10644
10645/// Interpret opaque macro replacement parameters with the primary producer's
10646/// canonical type spelling, preserving cv/ref qualifiers for overload hiding.
10647pub fn cpp_macro_parameter_types(replacement: &str) -> Option<Vec<String>> {
10648    parse_macro_parameter_list(
10649        replacement,
10650        crate::declarations::cpp_callable_parameter_types,
10651    )
10652}
10653
10654pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> Option<CallableArity> {
10655    brokk_bifrost_core::analyzer::model::first_callable_arity(analyzer.signature_metadata(unit))
10656}
10657
10658pub fn cpp_callable_parameter_types(
10659    analyzer: &CppGraphSource<'_>,
10660    unit: &CodeUnit,
10661) -> Option<Vec<String>> {
10662    brokk_bifrost_core::analyzer::model::first_callable_parameter_types(
10663        analyzer.signature_metadata(unit),
10664    )
10665}
10666
10667fn merge_compatible_callable_arities(
10668    left: CallableArity,
10669    right: CallableArity,
10670) -> Option<CallableArity> {
10671    let total = left.total();
10672    let left_repeated = left.accepts(total.saturating_add(1));
10673    let right_repeated = right.accepts(right.total().saturating_add(1));
10674    if total != right.total() || left_repeated != right_repeated {
10675        return None;
10676    }
10677    let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
10678    Some(CallableArity::new(required, total, left_repeated))
10679}
10680
10681fn collect_macro_events(
10682    cpp: &dyn CppSource,
10683    token: QueryToken<'_>,
10684    file: &ProjectFile,
10685) -> Vec<MacroEvent> {
10686    let Some(prepared) = cpp.prepared_syntax(token, file) else {
10687        return Vec::new();
10688    };
10689    let source = prepared.source();
10690    let mut events = Vec::new();
10691    let root = prepared.tree().root_node();
10692    let mut stack = vec![root];
10693    while let Some(node) = stack.pop() {
10694        match node.kind() {
10695            "preproc_def" | "preproc_function_def" => {
10696                let Some(name) = node.child_by_field_name("name") else {
10697                    continue;
10698                };
10699                let name = node_text(name, source).to_string();
10700                events.push(MacroEvent::Define {
10701                    name,
10702                    binding: MacroBinding {
10703                        source: file.clone(),
10704                        declaration_byte: node.start_byte(),
10705                        definition: VisibilityIndex::decode_macro_definition(node, source),
10706                        exact: true,
10707                    },
10708                    byte: node.start_byte(),
10709                    conditionals: owning_preprocessor_conditionals(root, node, source),
10710                });
10711                continue;
10712            }
10713            "preproc_include" => {
10714                let Some(path) = node.child_by_field_name("path") else {
10715                    events.push(MacroEvent::Include {
10716                        targets: Vec::new(),
10717                        byte: node.start_byte(),
10718                        conditionals: owning_preprocessor_conditionals(root, node, source),
10719                    });
10720                    continue;
10721                };
10722                let include = crate::imports::include_path_literal_value(path, source);
10723                let targets = include.map_or_else(Vec::new, |include| {
10724                    resolve_include_targets_with_index(file, include, cpp.include_target_index())
10725                });
10726                // An include no indexed file can answer -- its final path component names
10727                // no analyzable workspace file -- crosses into sources this index does not
10728                // hold, so it cannot hide indexed macro state and must not poison every
10729                // later local macro binding. The include's spelling does not decide that:
10730                // a quoted include falls back to the same search path an angle include
10731                // uses, and esphome's `core/log.h` reaches every component with
10732                // `#include "WString.h"`, an Arduino header absent from the tree (#3057).
10733                // A computed include, and a spelling some indexed file name does match
10734                // without resolving to one target, may still hide indexed macro state and
10735                // therefore fail closed.
10736                if targets.is_empty()
10737                    && include.is_some_and(|include| {
10738                        !cpp.include_target_index().names_indexed_file(include)
10739                    })
10740                {
10741                    continue;
10742                }
10743                events.push(MacroEvent::Include {
10744                    targets,
10745                    byte: node.start_byte(),
10746                    conditionals: owning_preprocessor_conditionals(root, node, source),
10747                });
10748                continue;
10749            }
10750            "preproc_call" => {
10751                let Some(directive) = node.child_by_field_name("directive") else {
10752                    continue;
10753                };
10754                if node_text(directive, source) != "#undef" {
10755                    continue;
10756                }
10757                let name = node
10758                    .child_by_field_name("argument")
10759                    .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
10760                if let Some(name) = name {
10761                    events.push(MacroEvent::Undef {
10762                        name,
10763                        byte: node.start_byte(),
10764                        conditionals: owning_preprocessor_conditionals(root, node, source),
10765                    });
10766                } else {
10767                    events.push(MacroEvent::Invalidate {
10768                        byte: node.start_byte(),
10769                    });
10770                }
10771                continue;
10772            }
10773            _ => {}
10774        }
10775        push_named_children_reversed(node, &mut stack);
10776    }
10777    events.sort_by_key(MacroEvent::byte);
10778    events
10779}
10780
10781fn find_include_activation(
10782    cpp: &dyn CppSource,
10783    token: QueryToken<'_>,
10784    file: &ProjectFile,
10785    prepared: &PreparedSyntaxTree,
10786    donor_source: &ProjectFile,
10787    language_guard_is_stable: &dyn Fn() -> bool,
10788) -> Option<usize> {
10789    let include_targets = cpp.include_target_index();
10790    let reference_is_c = file
10791        .rel_path()
10792        .extension()
10793        .and_then(|extension| extension.to_str())
10794        == Some("c");
10795    let mut direct_includes = Vec::new();
10796    let mut nodes = vec![prepared.tree().root_node()];
10797    // An include activates for the whole file, so only an unconditional
10798    // directive counts here.
10799    while let Some(node) = nodes.pop() {
10800        if node.kind() == "preproc_include" {
10801            let Some(required_guards) = include_directive_guard_requirements(
10802                node,
10803                prepared.source(),
10804                reference_is_c,
10805                language_guard_is_stable,
10806            ) else {
10807                continue;
10808            };
10809            if required_guards.all.is_empty() && required_guards.partial.is_empty() {
10810                for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
10811                    if let Some(target) = unique_include_target(resolve_include_targets_with_index(
10812                        file,
10813                        include,
10814                        include_targets,
10815                    )) {
10816                        direct_includes.push((node.end_byte(), target));
10817                    }
10818                }
10819            }
10820            continue;
10821        }
10822        push_named_children_reversed(node, &mut nodes);
10823    }
10824    direct_includes.sort_by_key(|(activation, _)| *activation);
10825    let mut known_missing = HashSet::default();
10826    direct_includes
10827        .into_iter()
10828        .find(|(_, direct)| {
10829            unconditional_include_reaches(
10830                cpp,
10831                token,
10832                direct,
10833                donor_source,
10834                file,
10835                &mut known_missing,
10836                language_guard_is_stable,
10837            )
10838        })
10839        .map(|(activation, _)| activation)
10840}
10841
10842fn find_conditional_include_projection_index(
10843    cpp: &dyn CppSource,
10844    token: QueryToken<'_>,
10845    file: &ProjectFile,
10846    prepared: &PreparedSyntaxTree,
10847    language_guard_is_stable: &dyn Fn() -> bool,
10848    on_state: &dyn Fn(),
10849) -> ConditionalIncludeProjectionIndex {
10850    let reference_is_c = reference_uses_c_semantics(cpp, file);
10851    let include_targets = cpp.include_target_index();
10852    let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
10853        HashMap::default();
10854    let mut pending = Vec::new();
10855    let mut nodes = vec![prepared.tree().root_node()];
10856    while let Some(node) = nodes.pop() {
10857        if node.kind() == "preproc_include" {
10858            let Some(required_guards) = include_directive_guard_requirements(
10859                node,
10860                prepared.source(),
10861                reference_is_c,
10862                language_guard_is_stable,
10863            ) else {
10864                continue;
10865            };
10866            for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
10867                let Some(target) = unique_include_target(resolve_include_targets_with_index(
10868                    file,
10869                    include,
10870                    include_targets,
10871                )) else {
10872                    continue;
10873                };
10874                pending.push((target, node.end_byte(), required_guards.clone()));
10875            }
10876            continue;
10877        }
10878        push_named_children_reversed(node, &mut nodes);
10879    }
10880
10881    // One reached file can have several distinct compatible guard paths. Each
10882    // (file, activation byte) key keeps only the inclusion-minimal guard sets:
10883    // the consumers ask existence questions whose answers are monotone in the
10884    // guard set -- a path whose requirements hold, stay stable, and stay
10885    // compatible under one environment does so under every subset as well --
10886    // so a state subsumed by an existing subset cannot witness anything its
10887    // subset does not, and inserting a smaller set evicts the supersets it
10888    // subsumes. Exact-set dedup still terminated cycles, but dense `#ifdef`
10889    // lattices (QMK's per-keyboard feature guards) enumerated the powerset of
10890    // path-union guard sets through it: the state space, the per-key linear
10891    // scans, and resident memory all grew without bound (#2365).
10892    let mut expanded: HashMap<(ProjectFile, usize), Vec<HashSet<PreprocessorGuard>>> =
10893        HashMap::default();
10894    while let Some((current_file, activation_byte, path)) = pending.pop() {
10895        let guard_sets = expanded
10896            .entry((current_file.clone(), activation_byte))
10897            .or_default();
10898        // Minimality is keyed on the full guard set alone: a path's undecided
10899        // guards are the per-conditional subset of that set, so a subset path
10900        // carries a subset of them too.
10901        if guard_sets
10902            .iter()
10903            .any(|existing| existing.is_subset(&path.all))
10904        {
10905            continue;
10906        }
10907        let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
10908            .drain(..)
10909            .partition(|existing| path.all.is_subset(existing));
10910        *guard_sets = kept;
10911        guard_sets.push(path.all.clone());
10912        if !evicted.is_empty()
10913            && let Some(projections) = projections_by_source.get_mut(&current_file)
10914        {
10915            projections.retain(|projection| {
10916                projection.activation_byte != activation_byte
10917                    || !evicted.contains(&projection.required_guards)
10918            });
10919        }
10920        on_state();
10921
10922        // A fresh minimal set has no equal in the store: equality would have
10923        // been caught by the subset check above.
10924        projections_by_source
10925            .entry(current_file.clone())
10926            .or_default()
10927            .push(ConditionalIncludeProjection {
10928                activation_byte,
10929                required_guards: path.all.clone(),
10930                partial_guards: path.partial.clone(),
10931            });
10932
10933        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
10934            continue;
10935        };
10936        let mut nodes = vec![current_prepared.tree().root_node()];
10937        while let Some(node) = nodes.pop() {
10938            if node.kind() == "preproc_include" {
10939                let Some(include_guards) = include_directive_guard_requirements(
10940                    node,
10941                    current_prepared.source(),
10942                    reference_is_c,
10943                    language_guard_is_stable,
10944                ) else {
10945                    continue;
10946                };
10947                let Some(reached) = path.merged(&include_guards) else {
10948                    continue;
10949                };
10950                for (include, _) in
10951                    include_path_from_node(node, current_prepared.source()).into_iter()
10952                {
10953                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
10954                        &current_file,
10955                        include,
10956                        include_targets,
10957                    )) else {
10958                        continue;
10959                    };
10960                    pending.push((target, activation_byte, reached.clone()));
10961                }
10962                continue;
10963            }
10964            push_named_children_reversed(node, &mut nodes);
10965        }
10966    }
10967
10968    projections_by_source
10969        .into_iter()
10970        .map(|(source, mut projections)| {
10971            projections.sort_by_key(|projection| projection.activation_byte);
10972            (source, Arc::from(projections))
10973        })
10974        .collect()
10975}
10976
10977/// Decide one conditional include target without materializing every source
10978/// reached by every guard combination. A path `admission` already rejects
10979/// cannot become admissible after adding nested include guards -- both rules
10980/// only ever add requirements -- so discard it before expanding the next
10981/// header.
10982#[allow(clippy::too_many_arguments)]
10983fn find_conditional_include_projection_for_source(
10984    cpp: &dyn CppSource,
10985    token: QueryToken<'_>,
10986    file: &ProjectFile,
10987    prepared: &PreparedSyntaxTree,
10988    donor_source: &ProjectFile,
10989    admission: IncludePathAdmission,
10990    reference_guards: Option<&HashSet<PreprocessorGuard>>,
10991    reference_byte: usize,
10992    language_guard_is_stable: &dyn Fn() -> bool,
10993    on_state: &dyn Fn(),
10994) -> bool {
10995    let Some(reference_guards) = reference_guards else {
10996        return false;
10997    };
10998    let reference_is_c = reference_uses_c_semantics(cpp, file);
10999    let include_targets = cpp.include_target_index();
11000    let mut pending = Vec::new();
11001    let mut nodes = vec![prepared.tree().root_node()];
11002    while let Some(node) = nodes.pop() {
11003        if node.kind() == "preproc_include" {
11004            let Some(required_guards) = include_directive_guard_requirements(
11005                node,
11006                prepared.source(),
11007                reference_is_c,
11008                language_guard_is_stable,
11009            ) else {
11010                continue;
11011            };
11012            if node.end_byte() > reference_byte
11013                || !admission.admits(
11014                    &required_guards.all,
11015                    &required_guards.partial,
11016                    Some(reference_guards),
11017                )
11018            {
11019                continue;
11020            }
11021            for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
11022                let Some(target) = unique_include_target(resolve_include_targets_with_index(
11023                    file,
11024                    include,
11025                    include_targets,
11026                )) else {
11027                    continue;
11028                };
11029                if &target == donor_source {
11030                    return true;
11031                }
11032                pending.push((target, required_guards.clone()));
11033            }
11034            continue;
11035        }
11036        push_named_children_reversed(node, &mut nodes);
11037    }
11038
11039    let mut expanded: HashMap<ProjectFile, Vec<HashSet<PreprocessorGuard>>> = HashMap::default();
11040    while let Some((current_file, path)) = pending.pop() {
11041        let guard_sets = expanded.entry(current_file.clone()).or_default();
11042        if guard_sets.contains(&path.all) {
11043            continue;
11044        }
11045        guard_sets.push(path.all.clone());
11046        on_state();
11047
11048        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
11049            continue;
11050        };
11051        let mut nodes = vec![current_prepared.tree().root_node()];
11052        while let Some(node) = nodes.pop() {
11053            if node.kind() == "preproc_include" {
11054                let Some(include_guards) = include_directive_guard_requirements(
11055                    node,
11056                    current_prepared.source(),
11057                    reference_is_c,
11058                    language_guard_is_stable,
11059                ) else {
11060                    continue;
11061                };
11062                let Some(reached) = path.merged(&include_guards) else {
11063                    continue;
11064                };
11065                if !admission.admits(&reached.all, &reached.partial, Some(reference_guards)) {
11066                    continue;
11067                }
11068                for (include, _) in
11069                    include_path_from_node(node, current_prepared.source()).into_iter()
11070                {
11071                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
11072                        &current_file,
11073                        include,
11074                        include_targets,
11075                    )) else {
11076                        continue;
11077                    };
11078                    if &target == donor_source {
11079                        return true;
11080                    }
11081                    pending.push((target, reached.clone()));
11082                }
11083                continue;
11084            }
11085            push_named_children_reversed(node, &mut nodes);
11086        }
11087    }
11088    false
11089}
11090
11091/// Whether `translation_unit`'s unconditional `#include` closure reaches
11092/// `header`, directly or through any chain of headers.
11093///
11094/// The include-closure question asked on its own, for
11095/// [`crate::identity::cpp_header_body_files_are_related`]. The walk resolves
11096/// each include the way visibility does -- to a unique target or to nothing --
11097/// so a duplicated basename relates nothing, and it is memoized per file pair
11098/// and language-proof state on the analyzer.
11099///
11100/// The reference position is `translation_unit` itself: the question is
11101/// whether that unit compiles the header, so that unit's own dialect and
11102/// preprocessor context govern the walk.
11103pub fn cpp_include_closure_reaches(
11104    cpp: &dyn CppSource,
11105    token: QueryToken<'_>,
11106    translation_unit: &ProjectFile,
11107    header: &ProjectFile,
11108) -> bool {
11109    let language_guard_stable = OnceCell::new();
11110    unconditional_include_reaches(
11111        cpp,
11112        token,
11113        translation_unit,
11114        header,
11115        translation_unit,
11116        &mut HashSet::default(),
11117        &|| {
11118            *language_guard_stable.get_or_init(|| {
11119                let mut macro_events =
11120                    |file: &ProjectFile| collect_macro_events(cpp, token, file).into_boxed_slice();
11121                include_language_guard_is_stable_with(
11122                    cpp,
11123                    token,
11124                    translation_unit,
11125                    &mut macro_events,
11126                    &mut |_: &ProjectFile, prepared: &PreparedSyntaxTree| {
11127                        collect_structured_include_facts(prepared)
11128                    },
11129                )
11130            })
11131        },
11132    )
11133}
11134
11135fn unconditional_include_reaches(
11136    cpp: &dyn CppSource,
11137    token: QueryToken<'_>,
11138    first: &ProjectFile,
11139    donor_source: &ProjectFile,
11140    reference_file: &ProjectFile,
11141    known_missing: &mut HashSet<ProjectFile>,
11142    language_guard_is_stable: &dyn Fn() -> bool,
11143) -> bool {
11144    if first == donor_source {
11145        return true;
11146    }
11147    if known_missing.contains(first) {
11148        return false;
11149    }
11150    let reference_is_c = reference_file
11151        .rel_path()
11152        .extension()
11153        .and_then(|extension| extension.to_str())
11154        == Some("c");
11155    if let Some(reaches) =
11156        cpp.cached_unconditional_include_reachability(first, donor_source, reference_file)
11157    {
11158        return reaches;
11159    }
11160    let mut visited = HashSet::default();
11161    let mut files = vec![first.clone()];
11162    let include_targets = cpp.include_target_index();
11163    while let Some(file) = files.pop() {
11164        if file == *donor_source {
11165            cpp.cache_unconditional_include_reachability(first, donor_source, reference_file, true);
11166            return true;
11167        }
11168        if known_missing.contains(&file) || !visited.insert(file.clone()) {
11169            continue;
11170        }
11171        let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11172            continue;
11173        };
11174        // One cursor for the whole-file walk; see `named_children_iter` (#3097).
11175        let mut cursor = prepared.tree().walk();
11176        let mut nodes = vec![prepared.tree().root_node()];
11177        while let Some(node) = nodes.pop() {
11178            if node.kind() == "preproc_include" {
11179                let Some(required_guards) = include_directive_guard_requirements(
11180                    node,
11181                    prepared.source(),
11182                    reference_is_c,
11183                    language_guard_is_stable,
11184                ) else {
11185                    continue;
11186                };
11187                if required_guards.all.is_empty() && required_guards.partial.is_empty() {
11188                    for (include, _) in include_path_from_node(node, prepared.source()).into_iter()
11189                    {
11190                        if let Some(target) = unique_include_target(
11191                            resolve_include_targets_with_index(&file, include, include_targets),
11192                        ) {
11193                            files.push(target);
11194                        }
11195                    }
11196                }
11197                continue;
11198            }
11199            let first_pushed = nodes.len();
11200            nodes.extend(node.named_children(&mut cursor));
11201            nodes[first_pushed..].reverse();
11202        }
11203    }
11204    known_missing.extend(visited);
11205    cpp.cache_unconditional_include_reachability(first, donor_source, reference_file, false);
11206    false
11207}
11208
11209fn include_language_guard_is_stable_with<MacroEvents, IncludeFacts>(
11210    cpp: &dyn CppSource,
11211    token: QueryToken<'_>,
11212    file: &ProjectFile,
11213    mut macro_events: MacroEvents,
11214    mut include_facts: IncludeFacts,
11215) -> bool
11216where
11217    MacroEvents: FnMut(&ProjectFile) -> Box<[MacroEvent]>,
11218    IncludeFacts: FnMut(&ProjectFile, &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]>,
11219{
11220    // A header has no intrinsic dialect. An opaque incoming include can hide
11221    // its compiler context from reverse include attribution. Only a
11222    // translation unit can supply the source-only language fact.
11223    if !crate::imports::is_cpp_translation_unit(file) {
11224        return false;
11225    }
11226    // When compiler data exists, keep its existing authoritative guard proof.
11227    // Source-only dialect facts cannot reconstruct -D/-U ordering.
11228    if !cpp.compile_contexts_for(file).is_empty()
11229        || cpp
11230            .reaching_translation_units(file)
11231            .iter()
11232            .any(|tu| !cpp.compile_contexts_for(tu).is_empty())
11233    {
11234        return false;
11235    }
11236    let mut pending = vec![file.clone()];
11237    let mut visited = HashSet::default();
11238    while let Some(file) = pending.pop() {
11239        if !visited.insert(file.clone()) {
11240            continue;
11241        }
11242        let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11243            return false;
11244        };
11245        if macro_events(&file).iter().any(|event| match event {
11246            MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
11247                name == "__cplusplus"
11248            }
11249            MacroEvent::Invalidate { .. } => true,
11250            MacroEvent::Include { targets, .. } => targets.is_empty(),
11251        }) {
11252            return false;
11253        }
11254        // Unlike indexed macro-binding replay, intrinsic-macro proof also
11255        // needs every include source: an opaque header may change it.
11256        for fact in include_facts(&file, prepared.as_ref()).iter() {
11257            let targets =
11258                resolve_include_targets_with_index(&file, &fact.path, cpp.include_target_index());
11259            if targets.is_empty() {
11260                return false;
11261            }
11262            pending.extend(targets);
11263        }
11264    }
11265    true
11266}
11267
11268fn declaration_guard_requirements(
11269    analyzer: &CppGraphSource<'_>,
11270    cpp: &dyn CppSource,
11271    candidate: &CodeUnit,
11272) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
11273    declaration_guard_sites(analyzer, cpp, candidate, None)
11274        .into_iter()
11275        .map(|site| (site.byte, site.guards))
11276        .collect()
11277}
11278
11279/// One declaration range's preprocessor facts, as a visible-at-a-reference
11280/// question reads them.
11281struct DeclarationGuardSite {
11282    /// The byte at which the range introduces the name.
11283    byte: usize,
11284    /// The preprocessor requirements the range's declaration stands under.
11285    guards: HashSet<PreprocessorGuard>,
11286    /// Whether the range is one branch of completed `#if`/`#else` families
11287    /// that all close before the reference. Always false without a reference
11288    /// position.
11289    completed_family_before_reference: bool,
11290}
11291
11292/// The per-range facts behind [`declaration_guard_requirements`], plus the
11293/// reference-relative fact the completed-family rule asks for.
11294fn declaration_guard_sites(
11295    analyzer: &CppGraphSource<'_>,
11296    cpp: &dyn CppSource,
11297    candidate: &CodeUnit,
11298    reference_byte: Option<usize>,
11299) -> Vec<DeclarationGuardSite> {
11300    let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
11301        return Vec::new();
11302    };
11303    let root = prepared.tree().root_node();
11304    analyzer
11305        .declaration_source_occurrences(candidate)
11306        .into_iter()
11307        .flatten()
11308        .filter_map(|occurrence| {
11309            let guards = cpp_guard_set_to_runtime(occurrence.fact.guard_requirements.as_ref()?)?;
11310            Some(DeclarationGuardSite {
11311                byte: occurrence.range.start_byte,
11312                guards,
11313                completed_family_before_reference: reference_byte.is_some_and(|reference_byte| {
11314                    root.descendant_for_byte_range(
11315                        occurrence.range.start_byte,
11316                        occurrence.range.end_byte,
11317                    )
11318                    .is_some_and(|node| {
11319                        declaration_follows_completed_family_before_reference(
11320                            node,
11321                            prepared.source(),
11322                            reference_byte,
11323                        )
11324                    })
11325                }),
11326            })
11327        })
11328        .collect()
11329}
11330
11331/// Whether every conditional enclosing `node` is a completed
11332/// `#if`/`#elif`/`#else` family that closes before `reference_byte`.
11333///
11334/// A completed family is an exhaustive case analysis: every configuration
11335/// takes exactly one of its branches. A reference sited below the whole family
11336/// is compiled in a configuration that either takes the declaration's branch,
11337/// in which case the declaration is what the name resolves to there, or takes
11338/// another branch, in which case the declaration is simply not part of that
11339/// configuration. Nothing in the reference's own position settles the family,
11340/// so the declaration is one resolution the source presents at the reference
11341/// rather than a contradiction of it (#3297).
11342///
11343/// A lone `#if` is not a case analysis: nothing in the source says any
11344/// configuration defines its macro, so a declaration under one stays
11345/// unprovable and the implication rule keeps it out. Neither does a family
11346/// whose end is not behind the reference: a reference inside the other branch
11347/// contradicts the declaration, and a reference inside the declaration's own
11348/// branch is already covered by implication.
11349fn declaration_follows_completed_family_before_reference(
11350    node: Node<'_>,
11351    source: &str,
11352    reference_byte: usize,
11353) -> bool {
11354    callable_guard_completion_byte(node, source).is_some_and(|end| end > 0 && end <= reference_byte)
11355}
11356
11357/// Producer-owned completion boundary for all enclosing conditional families.
11358/// Zero is the neutral boundary for a declaration outside those families.
11359pub(crate) fn callable_guard_completion_byte(node: Node<'_>, source: &str) -> Option<usize> {
11360    let mut cache = PreprocessorContextCache::default();
11361    callable_guard_completion_byte_with_cache(node, source, &mut cache)
11362}
11363
11364pub(crate) fn callable_guard_completion_byte_with_cache(
11365    node: Node<'_>,
11366    source: &str,
11367    cache: &mut PreprocessorContextCache,
11368) -> Option<usize> {
11369    let mut completion_byte = 0;
11370    let mut ancestor = node.parent();
11371    while let Some(conditional) = ancestor {
11372        if matches!(
11373            conditional.kind(),
11374            "preproc_if" | "preproc_ifdef" | "preproc_elif"
11375        ) && !is_file_covering_include_guard(conditional, source)
11376            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11377            && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
11378        {
11379            let family = preprocessor_conditional_family_root(conditional);
11380            if !preprocessor_conditional_family_has_terminal_else(family) {
11381                return None;
11382            }
11383            completion_byte = completion_byte.max(family.end_byte());
11384        }
11385        ancestor = conditional.parent();
11386    }
11387    Some(completion_byte)
11388}
11389
11390fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
11391    analyzer
11392        .ranges(candidate)
11393        .into_iter()
11394        .map(|range| range.start_byte)
11395        .min()
11396}
11397
11398/// The macro names every configuration in `contexts` defines -- the fact set
11399/// one file's compile-database coverage proves (#2011). `None` when the
11400/// database has no entry for the file, which is different from an empty
11401/// intersection: no entry means no coverage, while an empty intersection is
11402/// covered-and-proves-nothing.
11403fn context_fact_names(contexts: &[CppCompileContext]) -> Option<HashSet<String>> {
11404    let (first, rest) = contexts.split_first()?;
11405    Some(
11406        first
11407            .defined_macros
11408            .iter()
11409            .filter(|name| {
11410                rest.iter()
11411                    .all(|context| context.defined_macros.contains(*name))
11412            })
11413            .cloned()
11414            .collect(),
11415    )
11416}
11417
11418pub fn guard_requirements_hold_at_reference(
11419    required: &HashSet<PreprocessorGuard>,
11420    reference: Option<&HashSet<PreprocessorGuard>>,
11421) -> bool {
11422    reference.is_some_and(|active| {
11423        required
11424            .iter()
11425            .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
11426    })
11427}
11428
11429fn preprocessor_guard_holds_at_reference(
11430    required: &PreprocessorGuard,
11431    active: &HashSet<PreprocessorGuard>,
11432) -> bool {
11433    if active.contains(required) {
11434        return true;
11435    }
11436    let active_expression = BooleanGuardExpression::all(
11437        active
11438            .iter()
11439            .filter_map(PreprocessorGuard::as_boolean_expression),
11440    );
11441    required
11442        .as_boolean_expression()
11443        .is_some_and(|required| active_expression.implies(&required))
11444}
11445
11446/// Cross-file guard rule: two guard sets are compatible when neither one
11447/// contradicts the other. Use this instead of the subset test whenever the
11448/// guards come from a foreign file, which resolves its own conditionals
11449/// independently of the reference.
11450fn guards_compatible_at_reference(
11451    declaration: &HashSet<PreprocessorGuard>,
11452    reference: Option<&HashSet<PreprocessorGuard>>,
11453) -> bool {
11454    reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
11455}
11456
11457/// The byte range of the `#if`/`#elif`/`#else` chain that encloses the smallest
11458/// node covering `[start_byte, end_byte)`, or `None` when nothing there is
11459/// conditional.
11460///
11461/// Two declarations of one name that report the same chain stand in different
11462/// branches of it, so at most one of them is compiled in any configuration.
11463/// They are alternate spellings of a single declaration, not competing
11464/// declarations, and navigation must not present them as an ambiguity.
11465pub fn preprocessor_conditional_family_range(
11466    root: Node<'_>,
11467    start_byte: usize,
11468    end_byte: usize,
11469) -> Option<(usize, usize)> {
11470    let node = root.descendant_for_byte_range(start_byte, end_byte)?;
11471    preprocessor_conditional_family_node(node)
11472        .map(|family| (family.start_byte(), family.end_byte()))
11473}
11474
11475pub(crate) fn preprocessor_conditional_family_node(node: Node<'_>) -> Option<Node<'_>> {
11476    let mut ancestor = Some(node);
11477    while let Some(current) = ancestor {
11478        if is_cpp_preprocessor_conditional(current)
11479            && preprocessor_conditional_contains_descendant(current, node)
11480        {
11481            return Some(preprocessor_conditional_family_root(current));
11482        }
11483        ancestor = current.parent();
11484    }
11485    None
11486}
11487
11488pub(crate) fn preprocessor_conditional_family_for_declaration_with_cache<'tree>(
11489    node: Node<'tree>,
11490    cache: &mut PreprocessorContextCache,
11491) -> Option<Node<'tree>> {
11492    let mut ancestor = node.parent();
11493    while let Some(current) = ancestor {
11494        if is_cpp_preprocessor_conditional(current)
11495            && preprocessor_conditional_contains_descendant_with_cache(current, node, cache)
11496        {
11497            let family = preprocessor_conditional_family_root(current);
11498            if preprocessor_conditional_family_has_terminal_else(family) {
11499                return Some(family);
11500            }
11501        }
11502        ancestor = current.parent();
11503    }
11504    None
11505}
11506
11507fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
11508    while let Some(parent) = conditional.parent() {
11509        let is_alternative = parent
11510            .child_by_field_name("alternative")
11511            .is_some_and(|alternative| {
11512                alternative.start_byte() == conditional.start_byte()
11513                    && alternative.end_byte() == conditional.end_byte()
11514            });
11515        if !is_alternative {
11516            break;
11517        }
11518        conditional = parent;
11519    }
11520    conditional
11521}
11522
11523fn preprocessor_conditional_family_has_terminal_else(mut conditional: Node<'_>) -> bool {
11524    loop {
11525        let Some(alternative) = conditional.child_by_field_name("alternative") else {
11526            return false;
11527        };
11528        match alternative.kind() {
11529            "preproc_else" => return true,
11530            "preproc_elif" => conditional = alternative,
11531            _ => return false,
11532        }
11533    }
11534}
11535
11536/// The undecidable preprocessor guards an `#include` directive stands under,
11537/// or `None` when its conditional context keeps the directive out of a
11538/// translation unit of the reference's language: a false constant, a
11539/// condition this analyzer cannot read, or a `__cplusplus` branch that
11540/// language never takes.
11541///
11542/// This is [`callable_declaration_guard_requirements`]'s rule for the include
11543/// edge, and the language test is what keeps
11544/// [`IncludePathAdmission::Compatible`] honest: an empty active guard set
11545/// never implies `defined(__cplusplus)`, but it never contradicts it either,
11546/// so without this a `.c` file would reach the C++ arm of every
11547/// `#ifdef __cplusplus` / `#else` include pair.
11548fn include_directive_guard_requirements(
11549    node: Node<'_>,
11550    source: &str,
11551    reference_is_c: bool,
11552    language_guard_is_stable: &dyn Fn() -> bool,
11553) -> Option<PreprocessorGuardEnvironment> {
11554    let mut required = preprocessor_guard_environment_by_family(node, source)?;
11555    let excluded_by_language = required
11556        .all
11557        .iter()
11558        .any(|guard| preprocessor_guard_excludes_language(guard, reference_is_c));
11559    if excluded_by_language {
11560        return None;
11561    }
11562    let is_language_guard = |guard: &PreprocessorGuard| {
11563        matches!(guard, PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
11564            if name == "__cplusplus")
11565    };
11566    // The dialect decides this guard only when the admitted source closure
11567    // cannot mutate the builtin. Unknown include sources keep it undecided.
11568    if required.all.iter().any(is_language_guard) && language_guard_is_stable() {
11569        for guards in [&mut required.all, &mut required.partial] {
11570            guards.retain(|guard| !is_language_guard(guard));
11571        }
11572    }
11573    Some(required)
11574}
11575
11576fn preprocessor_guard_excludes_language(guard: &PreprocessorGuard, reference_is_c: bool) -> bool {
11577    match guard {
11578        PreprocessorGuard::Defined(name) => reference_is_c && name == "__cplusplus",
11579        PreprocessorGuard::Undefined(name) => !reference_is_c && name == "__cplusplus",
11580        _ => false,
11581    }
11582}
11583
11584pub fn preprocessor_guard_environment(
11585    node: Node<'_>,
11586    source: &str,
11587) -> Option<HashSet<PreprocessorGuard>> {
11588    let mut cache = PreprocessorContextCache::default();
11589    preprocessor_guard_environment_with_cache(node, source, &mut cache)
11590}
11591
11592pub(crate) fn preprocessor_guard_environment_with_cache(
11593    node: Node<'_>,
11594    source: &str,
11595    cache: &mut PreprocessorContextCache,
11596) -> Option<HashSet<PreprocessorGuard>> {
11597    preprocessor_guard_environment_by_family_with_cache(node, source, cache)
11598        .map(|environment| environment.all)
11599}
11600
11601/// The preprocessor guards `node` stands under, split by whether the
11602/// conditional that contributes each one belongs to a family that covers every
11603/// configuration.
11604///
11605/// A complete `#if`/`#else` or `#if`/`#elif`/`#else` family is a case analysis
11606/// every configuration takes exactly one branch of, which is why the resolver
11607/// already reads a declaration through one (`exhaustive_guard_family_activation`,
11608/// `complementary_same_fqn_type_declarations`). A lone `#if` is not: nothing in
11609/// the source says any configuration defines that macro, so only the build can
11610/// decide it, and it stays in `partial`.
11611#[derive(Clone)]
11612struct PreprocessorGuardEnvironment {
11613    all: HashSet<PreprocessorGuard>,
11614    partial: HashSet<PreprocessorGuard>,
11615}
11616
11617impl PreprocessorGuardEnvironment {
11618    /// The environment of a path standing under both `self` and `other`, or
11619    /// `None` when the two contradict. A guard a lone `#if` contributes on
11620    /// either side stays undecided for the whole path.
11621    fn merged(&self, other: &Self) -> Option<Self> {
11622        Some(Self {
11623            all: merge_preprocessor_guards(&self.all, &other.all)?,
11624            partial: self.partial.union(&other.partial).cloned().collect(),
11625        })
11626    }
11627}
11628
11629fn preprocessor_guard_environment_by_family(
11630    node: Node<'_>,
11631    source: &str,
11632) -> Option<PreprocessorGuardEnvironment> {
11633    let mut cache = PreprocessorContextCache::default();
11634    preprocessor_guard_environment_by_family_with_cache(node, source, &mut cache)
11635}
11636
11637fn preprocessor_guard_environment_by_family_with_cache(
11638    node: Node<'_>,
11639    source: &str,
11640    cache: &mut PreprocessorContextCache,
11641) -> Option<PreprocessorGuardEnvironment> {
11642    let mut all = HashSet::default();
11643    let mut partial = HashSet::default();
11644    let mut ancestor = node.parent();
11645    while let Some(conditional) = ancestor {
11646        if matches!(
11647            conditional.kind(),
11648            "preproc_if" | "preproc_ifdef" | "preproc_elif"
11649        ) && !is_file_covering_include_guard(conditional, source)
11650            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11651            && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
11652        {
11653            let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
11654            match guard {
11655                PreprocessorGuard::Constant(true) => {
11656                    ancestor = conditional.parent();
11657                    continue;
11658                }
11659                PreprocessorGuard::Constant(false) => return None,
11660                _ => {}
11661            }
11662            if all.contains(&guard.negated()) {
11663                return None;
11664            }
11665            if !preprocessor_conditional_family_has_terminal_else(
11666                preprocessor_conditional_family_root(conditional),
11667            ) {
11668                partial.insert(guard.clone());
11669            }
11670            all.insert(guard);
11671        }
11672        ancestor = conditional.parent();
11673    }
11674    if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
11675        match guard {
11676            PreprocessorGuard::Constant(true) => {}
11677            PreprocessorGuard::Constant(false) => return None,
11678            _ => {
11679                if all.contains(&guard.negated()) {
11680                    return None;
11681                }
11682                // The family of a conditional the parser split across a
11683                // statement boundary is not readable from the tree, so it
11684                // counts as undecided.
11685                partial.insert(guard.clone());
11686                all.insert(guard);
11687            }
11688        }
11689    }
11690    Some(PreprocessorGuardEnvironment { all, partial })
11691}
11692
11693pub(crate) fn callable_preprocessor_guard_requirements_with_cache(
11694    node: Node<'_>,
11695    source: &str,
11696    cache: &mut PreprocessorContextCache,
11697) -> Option<HashSet<PreprocessorGuard>> {
11698    let mut guards = HashSet::default();
11699    for guard in callable_preprocessor_ancestor_guards_with_cache(node, source, cache)? {
11700        match guard {
11701            PreprocessorGuard::Constant(true) => {}
11702            PreprocessorGuard::Constant(false) => return None,
11703            _ => {
11704                if guards.contains(&guard.negated()) {
11705                    return None;
11706                }
11707                guards.insert(guard);
11708            }
11709        }
11710    }
11711    Some(guards)
11712}
11713
11714fn callable_preprocessor_ancestor_guards(
11715    node: Node<'_>,
11716    source: &str,
11717) -> Option<Vec<PreprocessorGuard>> {
11718    let mut cache = PreprocessorContextCache::default();
11719    callable_preprocessor_ancestor_guards_with_cache(node, source, &mut cache)
11720}
11721
11722fn callable_preprocessor_ancestor_guards_with_cache(
11723    node: Node<'_>,
11724    source: &str,
11725    cache: &mut PreprocessorContextCache,
11726) -> Option<Vec<PreprocessorGuard>> {
11727    let mut guards = Vec::new();
11728    let mut ancestor = node.parent();
11729    while let Some(conditional) = ancestor {
11730        if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
11731            && !is_file_covering_include_guard(conditional, source)
11732            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11733            && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
11734        {
11735            guards.push(preprocessor_guard_for_descendant(
11736                conditional,
11737                node,
11738                source,
11739            )?);
11740        }
11741        ancestor = conditional.parent();
11742    }
11743    Some(guards)
11744}
11745
11746fn fragmented_statement_preprocessor_guard(
11747    descendant: Node<'_>,
11748    source: &str,
11749) -> Option<PreprocessorGuard> {
11750    // A conditional that starts before `} else if (...) {` crosses the
11751    // enclosing statement's grammar boundary. tree-sitter leaves its opener
11752    // as a `preproc_if` with a missing terminator in the consequence and
11753    // reparses the real `#endif` as a `preproc_call` in the alternative. Pair
11754    // those structured nodes before restoring the guard to intervening uses.
11755    let mut ancestor = descendant.parent();
11756    while let Some(statement) = ancestor {
11757        if statement.kind() == "if_statement"
11758            && let (Some(consequence), Some(alternative)) = (
11759                statement.child_by_field_name("consequence"),
11760                statement.child_by_field_name("alternative"),
11761            )
11762            && alternative.start_byte() <= descendant.start_byte()
11763            && descendant.end_byte() <= alternative.end_byte()
11764        {
11765            let mut cursor = consequence.walk();
11766            let openers = consequence
11767                .named_children(&mut cursor)
11768                .filter(|child| {
11769                    matches!(child.kind(), "preproc_if" | "preproc_ifdef")
11770                        && child
11771                            .child(child.child_count().saturating_sub(1))
11772                            .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
11773                })
11774                .collect::<Vec<_>>();
11775            if openers.len() != 1 {
11776                ancestor = statement.parent();
11777                continue;
11778            }
11779
11780            let mut terminators = Vec::new();
11781            let mut stack = vec![alternative];
11782            while let Some(node) = stack.pop() {
11783                if node.kind() == "preproc_call"
11784                    && node.start_byte() >= descendant.end_byte()
11785                    && node
11786                        .child_by_field_name("directive")
11787                        .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
11788                {
11789                    terminators.push(node);
11790                    continue;
11791                }
11792                push_named_children_reversed(node, &mut stack);
11793            }
11794            if terminators.len() == 1 {
11795                return simple_preprocessor_guard(openers[0], source);
11796            }
11797        }
11798        ancestor = statement.parent();
11799    }
11800    None
11801}
11802
11803pub(crate) fn preprocessor_guard_for_descendant(
11804    conditional: Node<'_>,
11805    descendant: Node<'_>,
11806    source: &str,
11807) -> Option<PreprocessorGuard> {
11808    let mut guard = simple_preprocessor_guard(conditional, source)?;
11809    if conditional
11810        .child_by_field_name("alternative")
11811        .is_some_and(|alternative| {
11812            alternative.start_byte() <= descendant.start_byte()
11813                && descendant.end_byte() <= alternative.end_byte()
11814        })
11815    {
11816        let alternative = conditional.child_by_field_name("alternative")?;
11817        // Tree-sitter nests an `#elif` chain in each `alternative` field. A
11818        // descendant in any later branch must first exclude the parent branch,
11819        // then collect the nested `preproc_elif` guard from its own ancestor.
11820        if !matches!(alternative.kind(), "preproc_else" | "preproc_elif") {
11821            return None;
11822        }
11823        guard = guard.negated();
11824    }
11825    Some(guard)
11826}
11827
11828pub(crate) fn preprocessor_conditional_contains_descendant(
11829    conditional: Node<'_>,
11830    descendant: Node<'_>,
11831) -> bool {
11832    cpp_displaced_preprocessor_boundary(conditional)
11833        .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
11834}
11835
11836fn preprocessor_conditional_contains_descendant_with_cache(
11837    conditional: Node<'_>,
11838    descendant: Node<'_>,
11839    cache: &mut PreprocessorContextCache,
11840) -> bool {
11841    cache
11842        .displaced_boundary_end(conditional)
11843        .is_none_or(|end_byte| descendant.end_byte() <= end_byte)
11844}
11845
11846pub fn merge_preprocessor_guards(
11847    left: &HashSet<PreprocessorGuard>,
11848    right: &HashSet<PreprocessorGuard>,
11849) -> Option<HashSet<PreprocessorGuard>> {
11850    let mut merged = left.clone();
11851    for guard in right {
11852        let boolean_negation = guard
11853            .as_boolean_expression()
11854            .map(|expression| expression.negated());
11855        if merged.contains(&guard.negated())
11856            || boolean_negation.is_some_and(|negated| {
11857                merged
11858                    .iter()
11859                    .filter_map(PreprocessorGuard::as_boolean_expression)
11860                    .any(|existing| existing == negated)
11861            })
11862        {
11863            return None;
11864        }
11865        merged.insert(guard.clone());
11866    }
11867    Some(merged)
11868}
11869
11870fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
11871    if conditional.kind() == "preproc_ifdef" {
11872        let name = conditional.child_by_field_name("name")?;
11873        let name = node_text(name, source).to_string();
11874        return match conditional.child(0)?.kind() {
11875            "#ifdef" => Some(PreprocessorGuard::Defined(name)),
11876            "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
11877            _ => None,
11878        };
11879    }
11880    let condition = conditional.child_by_field_name("condition")?;
11881    simple_preprocessor_expression_guard(condition, source).or_else(|| {
11882        Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
11883            node_text(condition, source),
11884        )))
11885    })
11886}
11887
11888fn simple_preprocessor_expression_guard(
11889    expression: Node<'_>,
11890    source: &str,
11891) -> Option<PreprocessorGuard> {
11892    match expression.kind() {
11893        "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
11894            node_text(expression, source).to_string(),
11895        ))),
11896        "number_literal" => match node_text(expression, source).trim() {
11897            "0" => Some(PreprocessorGuard::Constant(false)),
11898            "1" => Some(PreprocessorGuard::Constant(true)),
11899            _ => None,
11900        },
11901        "preproc_defined" => {
11902            let identifier = (0..expression.named_child_count())
11903                .filter_map(|index| expression.named_child(index))
11904                .find(|child| child.kind() == "identifier")?;
11905            Some(PreprocessorGuard::Defined(
11906                node_text(identifier, source).to_string(),
11907            ))
11908        }
11909        "unary_expression"
11910            if expression
11911                .child_by_field_name("operator")
11912                .is_some_and(|operator| operator.kind() == "!") =>
11913        {
11914            simple_preprocessor_expression_guard(
11915                expression.child_by_field_name("argument")?,
11916                source,
11917            )
11918            .map(|guard| guard.negated())
11919        }
11920        "parenthesized_expression" => (0..expression.named_child_count())
11921            .filter_map(|index| expression.named_child(index))
11922            .next()
11923            .and_then(|child| simple_preprocessor_expression_guard(child, source)),
11924        "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
11925            expression, source,
11926        ))),
11927        _ => None,
11928    }
11929}
11930
11931fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
11932    match expression.kind() {
11933        "number_literal" => match node_text(expression, source).trim() {
11934            "0" => BooleanGuardExpression::Constant(false),
11935            "1" => BooleanGuardExpression::Constant(true),
11936            _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11937                expression, source,
11938            ))),
11939        },
11940        "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
11941        "preproc_defined" => {
11942            let identifier = (0..expression.named_child_count())
11943                .filter_map(|index| expression.named_child(index))
11944                .find(|child| child.kind() == "identifier");
11945            identifier.map_or_else(
11946                || {
11947                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11948                        expression, source,
11949                    )))
11950                },
11951                |identifier| {
11952                    BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
11953                },
11954            )
11955        }
11956        "unary_expression"
11957            if expression
11958                .child_by_field_name("operator")
11959                .is_some_and(|operator| operator.kind() == "!") =>
11960        {
11961            expression.child_by_field_name("argument").map_or_else(
11962                || {
11963                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11964                        expression, source,
11965                    )))
11966                },
11967                |argument| boolean_preprocessor_expression(argument, source).negated(),
11968            )
11969        }
11970        "parenthesized_expression" => (0..expression.named_child_count())
11971            .filter_map(|index| expression.named_child(index))
11972            .next()
11973            .map_or_else(
11974                || {
11975                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11976                        expression, source,
11977                    )))
11978                },
11979                |child| boolean_preprocessor_expression(child, source),
11980            ),
11981        "binary_expression" => {
11982            let operands = || {
11983                Some((
11984                    boolean_preprocessor_expression(
11985                        expression.child_by_field_name("left")?,
11986                        source,
11987                    ),
11988                    boolean_preprocessor_expression(
11989                        expression.child_by_field_name("right")?,
11990                        source,
11991                    ),
11992                ))
11993            };
11994            match expression
11995                .child_by_field_name("operator")
11996                .map(|operator| operator.kind())
11997            {
11998                Some("&&") => operands().map_or_else(
11999                    || {
12000                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12001                            expression, source,
12002                        )))
12003                    },
12004                    |(left, right)| BooleanGuardExpression::all([left, right]),
12005                ),
12006                Some("||") => operands().map_or_else(
12007                    || {
12008                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12009                            expression, source,
12010                        )))
12011                    },
12012                    |(left, right)| BooleanGuardExpression::any([left, right]),
12013                ),
12014                _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12015                    expression, source,
12016                ))),
12017            }
12018        }
12019        _ => {
12020            BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
12021        }
12022    }
12023}
12024
12025fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
12026    if targets.len() == 1 {
12027        targets.pop()
12028    } else {
12029        None
12030    }
12031}
12032
12033fn callable_declaration_nodes<'tree>(
12034    analyzer: &CppGraphSource<'_>,
12035    prepared: &'tree PreparedSyntaxTree,
12036    candidate: &CodeUnit,
12037) -> Vec<Node<'tree>> {
12038    let root = prepared.tree().root_node();
12039    analyzer
12040        .ranges(candidate)
12041        .into_iter()
12042        .filter_map(|range| {
12043            let mut declaration =
12044                root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
12045            // A declaration an attribute-like macro invocation swallowed lives
12046            // inside the `ERROR` the parser left, not inside a `declaration`
12047            // node, so that envelope is where the climb stops (#2552).
12048            //
12049            // The climb can also run out of ancestors. A collapse that takes a
12050            // whole file with it -- whisper.cpp's `include/whisper.h` from its
12051            // first `WHISPER_DEPRECATED(` to the end (#3094) -- leaves no
12052            // envelope around most of what a region reparse recovers there, and
12053            // the recovered declaration's own `;` can fall outside the envelope
12054            // it does leave, so the range spans the container rather than one
12055            // node. Either way the declaration stands at the scope the climb
12056            // ended in, which is what the callers ask these nodes about.
12057            while !matches!(
12058                declaration.kind(),
12059                "declaration" | "field_declaration" | "function_definition"
12060            ) && !crate::declarations::is_macro_wrapped_declaration_envelope(
12061                declaration,
12062                prepared.source(),
12063            ) {
12064                let Some(parent) = declaration.parent() else {
12065                    break;
12066                };
12067                declaration = parent;
12068            }
12069            Some(declaration)
12070        })
12071        .collect()
12072}
12073
12074fn real_function_definition_ancestor<'tree>(
12075    node: Node<'tree>,
12076    source: &str,
12077) -> Option<Node<'tree>> {
12078    let mut ancestor = node.parent();
12079    while let Some(node) = ancestor {
12080        if node.kind() == "function_definition"
12081            && !is_recovered_declaration_scope_container(node, source)
12082        {
12083            return Some(node);
12084        }
12085        ancestor = node.parent();
12086    }
12087    None
12088}
12089
12090fn callable_declaration_activation_in_file(
12091    analyzer: &CppGraphSource<'_>,
12092    candidate: &CodeUnit,
12093    reference: &CallableReferenceContext<'_>,
12094) -> Option<usize> {
12095    analyzer
12096        .declaration_source_occurrences(candidate)?
12097        .into_iter()
12098        .filter_map(|occurrence| {
12099            let activation = occurrence.fact.callable_activation?;
12100            let guards = cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
12101            callable_guard_requirements_are_visible(
12102                &guards,
12103                occurrence.fact.callable_guard_completion_byte,
12104                reference,
12105            )
12106            .then_some(activation)
12107        })
12108        .min()
12109}
12110
12111fn callable_guard_requirements_are_visible(
12112    required: &HashSet<PreprocessorGuard>,
12113    completion_byte: Option<usize>,
12114    reference: &CallableReferenceContext<'_>,
12115) -> bool {
12116    if required.iter().all(|guard| match guard {
12117        PreprocessorGuard::Defined(name) if name == "__cplusplus" => !reference.is_c(),
12118        PreprocessorGuard::Undefined(name) if name == "__cplusplus" => reference.is_c(),
12119        guard => reference
12120            .guards()
12121            .is_some_and(|active| preprocessor_guard_holds_at_reference(guard, active)),
12122    }) {
12123        return true;
12124    }
12125    if required.iter().any(|guard| match guard {
12126        PreprocessorGuard::Defined(name) if name == "__cplusplus" => reference.is_c(),
12127        PreprocessorGuard::Undefined(name) if name == "__cplusplus" => !reference.is_c(),
12128        _ => false,
12129    }) {
12130        return false;
12131    }
12132    let undecided: HashSet<_> = required
12133        .iter()
12134        .filter(|guard| {
12135            !matches!(guard, PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
12136            if name == "__cplusplus")
12137        })
12138        .cloned()
12139        .collect();
12140    reference.position.as_ref().is_some_and(|position| {
12141        completion_byte.is_some_and(|end| end > 0 && end <= position.byte)
12142            && guards_compatible_at_reference(&undecided, reference.guards())
12143    })
12144}
12145
12146/// The reference side of a callable visibility question.
12147///
12148/// An include-graph walk and a whole-file arity activation ask the question
12149/// without one reference position, so they carry no `position` and therefore no
12150/// guard environment.
12151struct CallableReferenceContext<'a> {
12152    file: &'a ProjectFile,
12153    position: Option<CallableReferencePosition<'a>>,
12154}
12155
12156/// One reference position plus its preprocessor guard environment. The
12157/// environment is computed on demand because most declarations carry no
12158/// non-trivial guard.
12159struct CallableReferencePosition<'a> {
12160    prepared: &'a PreparedSyntaxTree,
12161    byte: usize,
12162    guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
12163}
12164
12165impl CallableReferenceContext<'_> {
12166    fn is_c(&self) -> bool {
12167        self.file
12168            .rel_path()
12169            .extension()
12170            .and_then(|extension| extension.to_str())
12171            == Some("c")
12172    }
12173
12174    fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
12175        let position = self.position.as_ref()?;
12176        position
12177            .guards
12178            .get_or_init(|| {
12179                position
12180                    .prepared
12181                    .tree()
12182                    .root_node()
12183                    .descendant_for_byte_range(position.byte, position.byte.saturating_add(1))
12184                    .and_then(|node| {
12185                        preprocessor_guard_environment(node, position.prepared.source())
12186                    })
12187            })
12188            .as_ref()
12189    }
12190}
12191
12192/// The undecidable preprocessor guards `node` stands under, or `None` when its
12193/// conditional context excludes it from `reference`'s translation unit outright:
12194/// a false constant, a condition this analyzer cannot read, or a `__cplusplus`
12195/// branch the reference's language never takes.
12196///
12197/// Collecting one guard per enclosing conditional makes the whole walk a
12198/// conjunction of the declaration's requirements. Which comparison those
12199/// requirements then face depends on where the declaration lives: a same-file
12200/// declaration shares the reference's configuration, so the reference's active
12201/// guards must imply them, while a foreign file resolves its own conditionals
12202/// and only has to stay compatible with the reference.
12203fn callable_declaration_guard_requirements(
12204    node: Node<'_>,
12205    source: &str,
12206    reference: &CallableReferenceContext<'_>,
12207) -> Option<HashSet<PreprocessorGuard>> {
12208    let reference_is_c = reference.is_c();
12209    let mut required = HashSet::default();
12210    for guard in callable_preprocessor_ancestor_guards(node, source)? {
12211        match guard {
12212            PreprocessorGuard::Constant(true) => {}
12213            PreprocessorGuard::Constant(false) => return None,
12214            guard if preprocessor_guard_excludes_language(&guard, reference_is_c) => {
12215                return None;
12216            }
12217            guard => {
12218                required.insert(guard);
12219            }
12220        }
12221    }
12222    Some(required)
12223}
12224
12225/// Whether a declaration in the reference's own file is co-active with the
12226/// reference: one translation unit resolves every conditional the same way, so
12227/// the reference's active guards must imply the declaration's requirements.
12228///
12229/// The exception is a declaration in one branch of a completed `#if`/`#else`
12230/// family the reference follows: the reference's own position settles nothing
12231/// about that family, so a compatible branch is one resolution the source
12232/// presents at the reference (#3297), exactly as
12233/// [`VisibilityIndex::external_type_candidate_visible_in_context`] reads a type
12234/// declaration there.
12235fn callable_preprocessor_context_is_visible_for_reference(
12236    node: Node<'_>,
12237    source: &str,
12238    reference: &CallableReferenceContext<'_>,
12239) -> bool {
12240    let Some(required) = callable_declaration_guard_requirements(node, source, reference) else {
12241        return false;
12242    };
12243    if required.is_empty() || guard_requirements_hold_at_reference(&required, reference.guards()) {
12244        return true;
12245    }
12246    reference.position.as_ref().is_some_and(|position| {
12247        guards_compatible_at_reference(&required, reference.guards())
12248            && declaration_follows_completed_family_before_reference(node, source, position.byte)
12249    })
12250}
12251
12252fn flattened_macro_namespace_declaration_matches(
12253    analyzer: &CppGraphSource<'_>,
12254    _cpp: &dyn CppSource,
12255    reference_file: &ProjectFile,
12256    visible_declaration: &CodeUnit,
12257    qualified_candidate: &CodeUnit,
12258    reference_byte: usize,
12259) -> bool {
12260    // Namespace-opening macros can leave tree-sitter unable to retain the
12261    // namespace owner after a later recovery point. In that shape the forward
12262    // declaration is indexed at translation-unit scope, while the definition
12263    // still has its qualified owner. Require all surviving structural evidence
12264    // before treating the declaration as activation for that definition.
12265    if visible_declaration.kind() != qualified_candidate.kind()
12266        || visible_declaration.identifier() != qualified_candidate.identifier()
12267        || visible_declaration.signature() != qualified_candidate.signature()
12268        || !visible_declaration.package_name().is_empty()
12269        || qualified_candidate.package_name().is_empty()
12270    {
12271        return false;
12272    }
12273
12274    let Some(occurrences) = analyzer.declaration_source_occurrences(visible_declaration) else {
12275        return false;
12276    };
12277    occurrences.into_iter().any(|occurrence| {
12278        occurrence
12279            .displaced_namespace_closing_brace
12280            .is_some_and(|closing_brace| {
12281                visible_declaration.source() != reference_file || closing_brace < reference_byte
12282            })
12283    })
12284}
12285
12286pub(crate) fn flattened_macro_namespace_components(
12287    declaration: Node<'_>,
12288    source: &str,
12289) -> Option<Vec<String>> {
12290    flattened_macro_function_namespace_components(declaration, source)
12291        .or_else(|| flattened_macro_error_namespace_components(declaration, source))
12292}
12293
12294fn flattened_macro_function_namespace_components(
12295    declaration: Node<'_>,
12296    source: &str,
12297) -> Option<Vec<String>> {
12298    let body = declaration
12299        .parent()
12300        .filter(|parent| parent.kind() == "compound_statement")?;
12301    let function = body.parent()?;
12302    if function.child_by_field_name("body") != Some(body) {
12303        return None;
12304    }
12305    let namespace_name = recovered_macro_namespace_name(function, source)?;
12306    let mut components = enclosing_namespace_components(declaration, source)?;
12307    components.push(namespace_name);
12308    Some(components)
12309}
12310
12311/// The namespace name a namespace-opening macro token displaced into a
12312/// synthetic `function_definition`, or `None` when `function` is not that
12313/// recovery shape.
12314///
12315/// `ABSL_NAMESPACE_BEGIN` (or `FMT_BEGIN_NAMESPACE`, ...) immediately before
12316/// `namespace x {` leaves tree-sitter with a `function_definition` whose type is
12317/// the macro token, whose declarator is the namespace name behind an `ERROR`
12318/// holding the `namespace` keyword, and whose body spans the whole namespace
12319/// region. The matching `*_NAMESPACE_END` sibling is what separates the recovery
12320/// artifact from a real function definition.
12321fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
12322    if function.kind() != "function_definition" || !function.has_error() {
12323        return None;
12324    }
12325    let body = function
12326        .child_by_field_name("body")
12327        .filter(|body| body.kind() == "compound_statement")?;
12328    let mut cursor = function.walk();
12329    let prefix = function
12330        .named_children(&mut cursor)
12331        .take_while(|child| child.start_byte() < body.start_byte())
12332        .filter(|child| child.kind() != "comment")
12333        .collect::<Vec<_>>();
12334    let begin_index = prefix.iter().rposition(|child| {
12335        flattened_macro_sentinel_name(*child, source)
12336            .is_some_and(|name| is_namespace_begin_sentinel(&name))
12337    })?;
12338    let mut identifiers = Vec::new();
12339    let mut stack = prefix[begin_index + 1..]
12340        .iter()
12341        .rev()
12342        .copied()
12343        .collect::<Vec<_>>();
12344    while let Some(current) = stack.pop() {
12345        if let Some(identifier) = direct_cpp_identifier_name(current, source) {
12346            identifiers.push(identifier);
12347            continue;
12348        }
12349        let mut cursor = current.walk();
12350        let children = current.named_children(&mut cursor).collect::<Vec<_>>();
12351        stack.extend(children.into_iter().rev());
12352    }
12353    let [keyword, namespace_name] = identifiers.as_slice() else {
12354        return None;
12355    };
12356    if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
12357    {
12358        return None;
12359    }
12360    let mut next = function.next_named_sibling();
12361    let next = loop {
12362        let candidate = next?;
12363        next = candidate.next_named_sibling();
12364        if candidate.kind() != "comment" {
12365            break candidate;
12366        }
12367    };
12368    flattened_macro_sentinel_name(next, source)
12369        .is_some_and(|name| is_namespace_end_sentinel(&name))
12370        .then(|| namespace_name.clone())
12371}
12372
12373/// A `function_definition` that exists only because tree-sitter recovered a
12374/// macro-decorated class head or a namespace-opening macro token. A declaration
12375/// in such a body keeps class or namespace scope, so a scope walk must step over
12376/// the wrapper instead of treating the declaration as block local.
12377pub(crate) fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
12378    crate::declarations::is_recovered_exported_class_container(node, source)
12379        || crate::declarations::is_recovered_fragmented_partial_specialization_container(
12380            node, source,
12381        )
12382        || recovered_macro_namespace_name(node, source).is_some()
12383}
12384
12385fn flattened_macro_error_namespace_components(
12386    declaration: Node<'_>,
12387    source: &str,
12388) -> Option<Vec<String>> {
12389    let parent = declaration
12390        .parent()
12391        .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
12392    let mut cursor = parent.walk();
12393    let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
12394    let declaration_index = siblings
12395        .iter()
12396        .position(|candidate| same_node(*candidate, declaration))?;
12397    let begin_index = (0..declaration_index).rev().find(|index| {
12398        flattened_macro_sentinel_name(siblings[*index], source)
12399            .is_some_and(|name| is_namespace_begin_sentinel(&name))
12400    })?;
12401
12402    let significant = siblings[begin_index + 1..declaration_index]
12403        .iter()
12404        .copied()
12405        .filter(|node| node.kind() != "comment")
12406        .collect::<Vec<_>>();
12407    let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
12408        return None;
12409    };
12410    if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
12411        return None;
12412    }
12413    let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
12414    if significant[2..].iter().any(|node| {
12415        flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
12416            is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
12417        })
12418    }) {
12419        return None;
12420    }
12421
12422    let mut saw_namespace_close = false;
12423    for sibling in siblings.iter().skip(declaration_index + 1).copied() {
12424        if sibling.kind() == "comment" {
12425            continue;
12426        }
12427        if !saw_namespace_close {
12428            if direct_unmatched_closing_brace(sibling) {
12429                saw_namespace_close = true;
12430                continue;
12431            }
12432            if flattened_macro_sentinel_name(sibling, source).is_some() {
12433                return None;
12434            }
12435            continue;
12436        }
12437        if !flattened_macro_sentinel_name(sibling, source)
12438            .is_some_and(|name| is_namespace_end_sentinel(&name))
12439        {
12440            return None;
12441        }
12442        let mut components = enclosing_namespace_components(declaration, source)?;
12443        components.push(namespace_name);
12444        return Some(components);
12445    }
12446    None
12447}
12448
12449fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
12450    // At translation-unit scope the trailing `X_NAMESPACE_END` token parses as
12451    // an `expression_statement` with a missing semicolon; inside a namespace
12452    // body the same token stays a bare `type_identifier`.
12453    let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
12454        node.named_child(0)?
12455    } else {
12456        node
12457    };
12458    let candidate = direct_cpp_identifier_name(node, source).or_else(|| {
12459        node.child_by_field_name("type")
12460            .and_then(|type_node| direct_cpp_identifier_name(type_node, source))
12461    })?;
12462    (cpp_export_macro_token(&candidate)
12463        && (is_namespace_begin_sentinel(&candidate) || is_namespace_end_sentinel(&candidate)))
12464    .then_some(candidate)
12465}
12466
12467/// Namespace-opening macros are spelled both ways in the wild:
12468/// `ABSL_NAMESPACE_BEGIN` (abseil, nlohmann) and `FMT_BEGIN_NAMESPACE` (fmt).
12469#[derive(Clone, Copy, PartialEq, Eq)]
12470enum NamespaceSentinel {
12471    Begin,
12472    End,
12473}
12474
12475fn namespace_sentinel(name: &str) -> Option<NamespaceSentinel> {
12476    if name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE") {
12477        Some(NamespaceSentinel::Begin)
12478    } else if name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE") {
12479        Some(NamespaceSentinel::End)
12480    } else {
12481        None
12482    }
12483}
12484
12485fn is_namespace_begin_sentinel(name: &str) -> bool {
12486    namespace_sentinel(name) == Some(NamespaceSentinel::Begin)
12487}
12488
12489fn is_namespace_end_sentinel(name: &str) -> bool {
12490    namespace_sentinel(name) == Some(NamespaceSentinel::End)
12491}
12492
12493fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
12494    if node.kind() != "ERROR" || node.named_child_count() != 1 {
12495        return None;
12496    }
12497    let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
12498    (!cpp_export_macro_token(&name)).then_some(name)
12499}
12500
12501fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
12502    if !matches!(
12503        node.kind(),
12504        "identifier" | "namespace_identifier" | "type_identifier"
12505    ) {
12506        return None;
12507    }
12508    let name = normalize_cpp_whitespace(node_text(node, source));
12509    (!name.is_empty()).then_some(name)
12510}
12511
12512fn guard_requirement_sets_match(
12513    left: &[(usize, HashSet<PreprocessorGuard>)],
12514    right: &[(usize, HashSet<PreprocessorGuard>)],
12515) -> bool {
12516    left.len() == right.len()
12517        && left.iter().all(|(_, left_guards)| {
12518            right
12519                .iter()
12520                .any(|(_, right_guards)| left_guards == right_guards)
12521        })
12522        && right.iter().all(|(_, right_guards)| {
12523            left.iter()
12524                .any(|(_, left_guards)| right_guards == left_guards)
12525        })
12526}
12527
12528pub(crate) fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
12529    node.kind() == "ERROR"
12530        && (0..node.child_count())
12531            .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
12532}
12533
12534pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
12535    let mut ancestor = node.parent();
12536    while let Some(parent) = ancestor {
12537        if is_cpp_preprocessor_conditional(parent)
12538            && !is_file_covering_include_guard(parent, source)
12539            && !is_split_cpp_language_linkage_wrapper(parent, node, source)
12540        {
12541            return false;
12542        }
12543        ancestor = parent.parent();
12544    }
12545    true
12546}
12547
12548fn is_split_cpp_language_linkage_wrapper(
12549    conditional: Node<'_>,
12550    descendant: Node<'_>,
12551    source: &str,
12552) -> bool {
12553    if conditional.child_by_field_name("alternative").is_some()
12554        || !matches!(
12555            simple_preprocessor_guard(conditional, source),
12556            Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12557        )
12558    {
12559        return false;
12560    }
12561    let mut current = descendant.parent();
12562    let linkage = loop {
12563        let Some(node) = current else {
12564            return false;
12565        };
12566        if node == conditional {
12567            return false;
12568        }
12569        if node.kind() == "linkage_specification" {
12570            break node;
12571        }
12572        current = node.parent();
12573    };
12574    if linkage
12575        .child_by_field_name("value")
12576        .is_none_or(|value| node_text(value, source) != "\"C\"")
12577    {
12578        return false;
12579    }
12580    let Some(body) = linkage.child_by_field_name("body") else {
12581        return false;
12582    };
12583    // Walk the body with cursors: an indexed `named_child(i)` restarts from
12584    // the first child, so indexed loops over a header-sized linkage body made
12585    // each call quadratic, and this runs once per declaration inside it
12586    // (#3397).
12587    let mut cursor = body.walk();
12588    let closes_opening_branch = body
12589        .named_children(&mut cursor)
12590        .take_while(|child| child.end_byte() <= descendant.start_byte())
12591        .any(|child| {
12592            child.kind() == "preproc_call"
12593                && child
12594                    .child_by_field_name("directive")
12595                    .is_some_and(|directive| node_text(directive, source) == "#endif")
12596        });
12597    let mut cursor = body.walk();
12598    let reopens_for_closing_brace = body
12599        .named_children(&mut cursor)
12600        .skip_while(|child| child.start_byte() < descendant.end_byte())
12601        .any(|child| {
12602            matches!(
12603                simple_preprocessor_guard(child, source),
12604                Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12605            ) && {
12606                let mut tokens = child.walk();
12607                child
12608                    .children(&mut tokens)
12609                    .any(|token| token.kind() == "#endif" && token.is_missing())
12610            }
12611        });
12612    closes_opening_branch && reopens_for_closing_brace
12613}
12614
12615/// The argument list a call-shaped node supplies: `f(args)`, `new T(args)`,
12616/// `T{args}` and the member initializer `: field(args)`, whose grammar gives its
12617/// argument list no field name.
12618pub fn call_arguments_node(node: Node<'_>) -> Option<Node<'_>> {
12619    node.child_by_field_name("arguments")
12620        .or_else(|| node.child_by_field_name("parameters"))
12621        .or_else(|| node.child_by_field_name("value"))
12622        .or_else(|| first_named_child_of_kind(node, "argument_list"))
12623        .or_else(|| first_named_child_of_kind(node, "initializer_list"))
12624}
12625
12626pub fn call_arity(node: Node<'_>) -> usize {
12627    call_arguments_node(node)
12628        .map(|args| argument_children(args).count())
12629        .unwrap_or(0)
12630}
12631
12632pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
12633    let recovered_block_arguments = recovered_block_literal_arguments(node);
12634    (0..node.child_count())
12635        .filter_map(move |index| node.child(index))
12636        .filter(|child| child.is_named() && !child.is_extra())
12637        .flat_map(move |child| {
12638            if let Some((raw, left, right)) = recovered_block_arguments
12639                && child == raw
12640            {
12641                [Some(left), Some(right)]
12642            } else {
12643                [Some(child), None]
12644            }
12645        })
12646        .flatten()
12647}
12648
12649/// Recover the two ordinary C values that the C++ grammar folds into one
12650/// `new_expression` for `callee(new, trailing)`.
12651///
12652/// The malformed node has an exact grammar-owned shape: the anonymous `new`
12653/// token, an extra `ERROR` containing only the comma token, and the trailing
12654/// value in the `type` field.  The caller supplies the compilation-dialect
12655/// proof from [`reference_uses_c_semantics`]; a C++ source therefore never
12656/// reinterprets a real new-expression through this path.
12657pub fn recovered_c_new_expression_arguments(
12658    node: Node<'_>,
12659    uses_c_semantics: bool,
12660) -> Option<[Node<'_>; 2]> {
12661    if !uses_c_semantics || node.kind() != "new_expression" {
12662        return None;
12663    }
12664    let parent = node.parent()?;
12665    if parent.kind() != "argument_list" {
12666        return None;
12667    }
12668    let keyword = node.child(0)?;
12669    let error = node.child(1)?;
12670    let trailing = node.child(2)?;
12671    if node.child(3).is_some()
12672        || keyword.kind() != "new"
12673        || keyword.is_named()
12674        || keyword.child_count() != 0
12675        || error.kind() != "ERROR"
12676        || !error.is_extra()
12677        || error.child_count() != 1
12678        || error.child(0).is_none_or(|comma| comma.kind() != ",")
12679        || node.child_by_field_name("type") != Some(trailing)
12680        || trailing.kind() != "type_identifier"
12681    {
12682        return None;
12683    }
12684    Some([keyword, trailing])
12685}
12686
12687/// The recovered C value covering one focused source range, starting from any
12688/// node within the malformed new-expression.
12689pub fn recovered_c_new_expression_argument_at(
12690    mut node: Node<'_>,
12691    start_byte: usize,
12692    end_byte: usize,
12693    uses_c_semantics: bool,
12694) -> Option<Node<'_>> {
12695    // The recovery only exists for C sources, and the climb below reaches the
12696    // root before it can answer no. `Node::parent` re-descends from the root on
12697    // every step, so leaving the check inside the loop made every scanned node
12698    // of a C++ file pay a full quadratic ancestor walk for an answer settled by
12699    // the file's language (#3097).
12700    if !uses_c_semantics {
12701        return None;
12702    }
12703    loop {
12704        if let Some(arguments) = recovered_c_new_expression_arguments(node, uses_c_semantics) {
12705            return arguments.into_iter().find(|argument| {
12706                argument.start_byte() <= start_byte && end_byte <= argument.end_byte()
12707            });
12708        }
12709        node = node.parent()?;
12710    }
12711}
12712
12713fn recovered_c_keyword_argument_count(
12714    file: &ProjectFile,
12715    call: Node<'_>,
12716    arguments: Node<'_>,
12717    source: &str,
12718) -> usize {
12719    // A C identifier that is a C++ keyword can be displaced twice by the C++
12720    // grammar: first into a direct parameter-list `ERROR(keyword)`, then into
12721    // a direct argument-list `ERROR(',', keyword)`. Match those CST tokens in
12722    // the enclosing C function before restoring the otherwise dropped slot;
12723    // ABI15's collapsed function-head shape is handled by the fallback below.
12724    if !is_c_source_file(file) || arguments.kind() != "argument_list" {
12725        return 0;
12726    }
12727    let mut ancestor = Some(call);
12728    let function = loop {
12729        let Some(current) = ancestor else {
12730            return recovered_c_keyword_argument_count_from_collapsed_function(call, arguments)
12731                .unwrap_or(0);
12732        };
12733        if current.kind() == "function_definition" {
12734            break current;
12735        }
12736        ancestor = current.parent();
12737    };
12738    let Some(parameters) = function
12739        .child_by_field_name("declarator")
12740        .and_then(|declarator| declarator.child_by_field_name("parameters"))
12741    else {
12742        return 0;
12743    };
12744    let displaced_parameter_keywords = (0..parameters.child_count())
12745        .filter_map(|index| parameters.child(index))
12746        .filter(|error| error.kind() == "ERROR")
12747        .filter_map(|error| {
12748            let parameter = error.prev_named_sibling()?;
12749            if parameter.kind() != "parameter_declaration"
12750                || parameter.end_byte() != error.start_byte()
12751                || extract_variable_name(parameter, source).is_some()
12752            {
12753                return None;
12754            }
12755            let mut children = (0..error.child_count())
12756                .filter_map(|index| error.child(index))
12757                .filter(|child| !child.is_extra() && !child.is_missing());
12758            let keyword = children.next()?;
12759            (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
12760                .then_some(keyword)
12761        })
12762        .collect::<Vec<_>>();
12763    if displaced_parameter_keywords.is_empty() {
12764        return 0;
12765    }
12766
12767    (0..arguments.child_count())
12768        .filter_map(|index| arguments.child(index))
12769        .filter(|error| error.kind() == "ERROR" && error.is_extra())
12770        .filter(|error| {
12771            let mut children = (0..error.child_count())
12772                .filter_map(|index| error.child(index))
12773                .filter(|child| !child.is_extra() && !child.is_missing());
12774            let Some(comma) = children.next() else {
12775                return false;
12776            };
12777            let Some(keyword) = children.next() else {
12778                return false;
12779            };
12780            children.next().is_none()
12781                && comma.kind() == ","
12782                && !keyword.is_named()
12783                && keyword.child_count() == 0
12784                && displaced_parameter_keywords
12785                    .iter()
12786                    .any(|parameter| parameter.kind_id() == keyword.kind_id())
12787        })
12788        .count()
12789}
12790
12791/// ABI15 can collapse a C function head into nested `ERROR` nodes when its
12792/// parameter name is a C++ keyword. The call still has a structured body, and
12793/// the outer error retains the function type, name, and opening parenthesis;
12794/// recover the displaced parameter only from those CST relationships. Text
12795/// matching would also accept an unbound call after the collapsed body.
12796fn recovered_c_keyword_argument_count_from_collapsed_function(
12797    call: Node<'_>,
12798    arguments: Node<'_>,
12799) -> Option<usize> {
12800    let argument_keywords = children_iter(arguments)
12801        .filter(|error| error.kind() == "ERROR" && error.is_extra())
12802        .filter_map(|error| {
12803            let mut children =
12804                children_iter(error).filter(|child| !child.is_extra() && !child.is_missing());
12805            let comma = children.next()?;
12806            let keyword = children.next()?;
12807            (children.next().is_none()
12808                && comma.kind() == ","
12809                && !keyword.is_named()
12810                && keyword.child_count() == 0)
12811                .then_some(keyword)
12812        })
12813        .collect::<Vec<_>>();
12814    if argument_keywords.is_empty() {
12815        return Some(0);
12816    }
12817
12818    // The collapsed function keeps its body under the parameter ERROR, while
12819    // ordinary control-flow blocks add compound_statement layers below it.
12820    // Stop before crossing a real callable body, whose parameters are a
12821    // separate binding scope.
12822    let mut ancestor = call.parent();
12823    let body = loop {
12824        let current = ancestor?;
12825        if matches!(current.kind(), "function_definition" | "lambda_expression") {
12826            return None;
12827        }
12828        if current.kind() == "compound_statement"
12829            && current
12830                .parent()
12831                .is_some_and(|parent| parent.kind() == "ERROR")
12832        {
12833            break current;
12834        }
12835        ancestor = current.parent();
12836    };
12837    let parameter_error = body.parent().filter(|parent| parent.kind() == "ERROR")?;
12838    let function_error = parameter_error
12839        .parent()
12840        .filter(|parent| parent.kind() == "ERROR")?;
12841    let function_children = children_iter(function_error).collect::<Vec<_>>();
12842    let parameter_error_index = function_children
12843        .iter()
12844        .position(|child| same_node(*child, parameter_error))?;
12845    // ERROR nodes expose their retained field labels by child index, while
12846    // child_by_field_name does not return their type child in runtime 0.25.
12847    let has_return_type = function_children[..parameter_error_index.saturating_sub(2)]
12848        .iter()
12849        .enumerate()
12850        .any(|(index, child)| {
12851            function_error.field_name_for_child(index as u32) == Some("type")
12852                && !child.is_extra()
12853                && !child.is_missing()
12854                && matches!(
12855                    child.kind(),
12856                    "primitive_type" | "sized_type_specifier" | "type_identifier"
12857                )
12858        });
12859    if parameter_error_index < 2
12860        || !has_return_type
12861        || function_children[parameter_error_index - 1].kind() != "("
12862        || function_children[parameter_error_index - 2].kind() != "identifier"
12863    {
12864        return Some(0);
12865    }
12866
12867    let parameter_children = children_iter(parameter_error).collect::<Vec<_>>();
12868    let body_index = parameter_children
12869        .iter()
12870        .position(|child| same_node(*child, body))?;
12871    let close_parenthesis_index = parameter_children[..body_index]
12872        .iter()
12873        .position(|child| child.kind() == ")")?;
12874    let parameter_keywords = parameter_children[..close_parenthesis_index]
12875        .iter()
12876        .enumerate()
12877        .filter(|(index, child)| {
12878            !child.is_named()
12879                && !child.is_extra()
12880                && !child.is_missing()
12881                && child.child_count() == 0
12882                && argument_keywords
12883                    .iter()
12884                    .any(|keyword| keyword.kind_id() == child.kind_id())
12885                && collapsed_c_parameter_name_position(&parameter_children, *index)
12886        })
12887        .map(|(_, child)| *child)
12888        .collect::<Vec<_>>();
12889    Some(
12890        argument_keywords
12891            .iter()
12892            .filter(|keyword| {
12893                parameter_keywords
12894                    .iter()
12895                    .any(|parameter| parameter.kind_id() == keyword.kind_id())
12896            })
12897            .count(),
12898    )
12899}
12900
12901/// The ABI15 collapsed head retains a parameter's type and pointer declarator
12902/// as direct children. Require that exact segment before accepting an
12903/// anonymous keyword as its name; a keyword after an `=` or attribute must not
12904/// borrow the same recovery.
12905fn collapsed_c_parameter_name_position(children: &[Node<'_>], keyword_index: usize) -> bool {
12906    let segment_start = children[..keyword_index]
12907        .iter()
12908        .rposition(|child| matches!(child.kind(), "(" | ","))
12909        .map_or(0, |index| index + 1);
12910    let mut saw_type = false;
12911    let mut saw_pointer = false;
12912    for child in &children[segment_start..keyword_index] {
12913        if child.kind() == "*" && !child.is_extra() && !child.is_missing() {
12914            saw_pointer = true;
12915            continue;
12916        }
12917        if saw_pointer {
12918            return false;
12919        }
12920        if !child.is_named() || child.is_extra() || child.is_missing() {
12921            return false;
12922        }
12923        if child.kind() == "type_qualifier" {
12924            continue;
12925        }
12926        if matches!(
12927            child.kind(),
12928            "class_specifier"
12929                | "dependent_type"
12930                | "enum_specifier"
12931                | "identifier"
12932                | "primitive_type"
12933                | "qualified_identifier"
12934                | "scoped_identifier"
12935                | "struct_specifier"
12936                | "template_type"
12937                | "type_identifier"
12938                | "union_specifier"
12939        ) {
12940            saw_type = true;
12941            continue;
12942        }
12943        return false;
12944    }
12945    saw_type && saw_pointer
12946}
12947
12948fn recovered_block_literal_arguments<'tree>(
12949    arguments: Node<'tree>,
12950) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
12951    if arguments.kind() != "argument_list" {
12952        return None;
12953    }
12954    let mut raw_arguments = (0..arguments.child_count())
12955        .filter_map(|index| arguments.child(index))
12956        .filter(|child| child.is_named() && !child.is_extra());
12957    let raw = raw_arguments.next()?;
12958    if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
12959        return None;
12960    }
12961
12962    let left = raw.child_by_field_name("left")?;
12963    if left.is_missing() || left.start_byte() == left.end_byte() {
12964        return None;
12965    }
12966    let right = raw.child_by_field_name("right")?;
12967    if right.kind() != "compound_literal_expression"
12968        || right.is_missing()
12969        || right
12970            .child_by_field_name("type")
12971            .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
12972        || right
12973            .child_by_field_name("value")
12974            .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
12975    {
12976        return None;
12977    }
12978    let has_intervening_error = (0..raw.child_count())
12979        .filter_map(|index| raw.child(index))
12980        .any(|child| {
12981            child.kind() == "ERROR"
12982                && !child.is_missing()
12983                && child.start_byte() >= left.end_byte()
12984                && child.end_byte() <= right.start_byte()
12985        });
12986    has_intervening_error.then_some((raw, left, right))
12987}
12988
12989pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
12990    match node.kind() {
12991        "new_expression" => node
12992            .child_by_field_name("type")
12993            .or_else(|| node.named_child(0)),
12994        "compound_literal_expression" => node.child_by_field_name("type"),
12995        "call_expression" => node.child_by_field_name("function"),
12996        _ => None,
12997    }
12998}
12999
13000/// The structured type named by a C-style cast expression.
13001///
13002/// Tree-sitter wraps the actual type syntax in a `type_descriptor`.  Return its
13003/// structured `type` field so resolution retains qualified and nested syntax
13004/// without reparsing source text.
13005pub fn cast_expression_type_node(node: Node<'_>) -> Option<Node<'_>> {
13006    if node.kind() != "cast_expression" {
13007        return None;
13008    }
13009    let descriptor = node.child_by_field_name("type")?;
13010    if descriptor.kind() == "type_descriptor" {
13011        descriptor.child_by_field_name("type")
13012    } else {
13013        Some(descriptor)
13014    }
13015}
13016
13017/// The expression a field access reads its member from.
13018///
13019/// That is normally the node in the `argument` field. A preprocessor
13020/// conditional inside a call's argument list defeats the grammar: tree-sitter
13021/// ends the argument list at the `#if` line, then reads the text that follows
13022/// `#endif` as a field access on the whole call expression and leaves the real
13023/// receiver in an `ERROR` node between the `argument` child and the operator.
13024/// mbedtls writes exactly that at `library/ssl_tls.c:4999`, where
13025/// `ssl->session->encrypt_then_mac` is one conditionally compiled call argument
13026/// (#2982).
13027///
13028/// `.` and `->` read whatever ends immediately before them in either tree, so
13029/// answer from the operator's position instead of trusting the `argument`
13030/// field, and unwrap the single expression a displaced `ERROR` holds.
13031pub fn cpp_field_expression_receiver(field: Node<'_>) -> Option<Node<'_>> {
13032    debug_assert_eq!(field.kind(), "field_expression");
13033    let operator = field.child_by_field_name("operator")?;
13034    let mut cursor = field.walk();
13035    let receiver = field
13036        .named_children(&mut cursor)
13037        .filter(|child| child.end_byte() <= operator.start_byte())
13038        .last()?;
13039    if receiver.kind() != "ERROR" {
13040        return Some(receiver);
13041    }
13042    (receiver.named_child_count() == 1)
13043        .then(|| receiver.named_child(0))
13044        .flatten()
13045}
13046
13047pub fn field_initializer_constructs_target(
13048    node: Node<'_>,
13049    ctx: &ScanCtx<'_>,
13050    owner: &CodeUnit,
13051) -> bool {
13052    // A qualified name in a constructor initializer denotes a base
13053    // subobject constructor (`namespace::Base(args)`), not a member field.  The
13054    // field-initializer grammar exposes the qualified name as one structured
13055    // `qualified_identifier`; resolve its owner through the same lexical type
13056    // machinery used for ordinary C++ type references before considering the
13057    // initializer a hit.  This keeps an unrelated `namespace::Other(...)`, a
13058    // qualified non-constructor member, and an unresolved owner out of the
13059    // target constructor's inverse usage set.
13060    if first_named_child_of_kind(node, "qualified_identifier").is_some() {
13061        return qualified_base_initializer_constructs_target(node, ctx, owner);
13062    }
13063    let Some(name) = node
13064        .child_by_field_name("name")
13065        .or_else(|| first_named_child_of_kind(node, "field_identifier"))
13066        .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
13067    else {
13068        return false;
13069    };
13070    let field_name = node_text(name, ctx.source);
13071    ctx.visibility
13072        .visible_identifier_candidates(ctx.file, field_name)
13073        .filter(|unit| unit.is_field() && unit.identifier() == field_name)
13074        .any(|unit| field_declares_type(unit, ctx, owner) && field_holds_subobject(unit, ctx))
13075}
13076
13077/// Whether a member initializer for `unit` runs a constructor of the type the
13078/// member declares.
13079///
13080/// Only a member that holds the type by value does. `Owner* p` initialized as
13081/// `p(nullptr)` binds a pointer and `Owner& r` as `r(other)` binds a
13082/// reference; neither runs an `Owner` constructor, even though both declare
13083/// `Owner` as the receiver type a member access through them resolves (#3286).
13084/// A member whose declaration the index cannot decode keeps the by-value
13085/// reading, which is what the declared-type match already assumed.
13086fn field_holds_subobject(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
13087    field_initializer_holds_subobject(&ctx.analyzer, ctx.visibility, unit)
13088}
13089
13090fn qualified_base_initializer_constructs_target(
13091    node: Node<'_>,
13092    ctx: &ScanCtx<'_>,
13093    owner: &CodeUnit,
13094) -> bool {
13095    let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
13096        return false;
13097    };
13098    let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
13099        return false;
13100    };
13101    let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
13102        return false;
13103    };
13104    let resolves_target = |components: &[String]| {
13105        matches!(
13106            ctx.visibility.resolve_type_components_lexically_for_target(
13107                &ctx.analyzer,
13108                ctx.file,
13109                components,
13110                is_globally_qualified_cpp_name(qualified),
13111                &lexical_scope,
13112                owner,
13113            ),
13114            LexicalTypeResolution::Resolved { unit, .. }
13115                if same_visible_symbol(&unit, owner)
13116        )
13117    };
13118    if resolves_target(&components) {
13119        return true;
13120    }
13121
13122    // Some real-world code spells a base mem-initializer as
13123    // `Base::Base(args)`. In that structured path the final component repeats
13124    // the constructor name; resolve the preceding type path. The terminal
13125    // identity check prevents an arbitrary qualified member from taking this
13126    // route.
13127    components
13128        .last()
13129        .is_some_and(|terminal| terminal == owner.identifier())
13130        && resolves_target(&components[..components.len() - 1])
13131}
13132
13133fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
13134    ctx.visibility
13135        .field_declared_type_fact(&ctx.analyzer, unit)
13136        .is_some_and(|fact| {
13137            ctx.visibility
13138                .resolves_to_type(&ctx.analyzer, ctx.file, &fact.type_text, owner)
13139                || ctx.visibility.resolves_to_type(
13140                    &ctx.analyzer,
13141                    ctx.file,
13142                    &normalize_field_type_text(&fact.type_text),
13143                    owner,
13144                )
13145        })
13146}
13147
13148pub fn field_declared_binding(
13149    analyzer: &CppGraphSource<'_>,
13150    visibility: &VisibilityIndex<'_>,
13151    visible_from: &ProjectFile,
13152    field: &CodeUnit,
13153) -> Option<CppScanBinding> {
13154    let (normalized, resolved, indirection) =
13155        field_declared_type_binding(analyzer, visibility, visible_from, field)?;
13156    let resolved = resolved
13157        .or_else(|| anonymous_aggregate_field_owner(analyzer, visibility, visible_from, field));
13158    Some(CppScanBinding::from_type_name(
13159        normalized,
13160        resolved,
13161        indirection,
13162    ))
13163}
13164
13165/// Whether a member-initializer `field(args)` runs the declared type's
13166/// constructor: only a member that holds the type by value does. `Owner* p`
13167/// initialized as `p(nullptr)` binds a pointer and `Owner& r` as `r(other)`
13168/// binds a reference; neither runs an `Owner` constructor (#3286). A member
13169/// whose declaration the index cannot decode keeps the by-value reading, the
13170/// same assumption the usage scan's declared-type match makes.
13171pub fn field_initializer_holds_subobject(
13172    analyzer: &CppGraphSource<'_>,
13173    visibility: &VisibilityIndex<'_>,
13174    field: &CodeUnit,
13175) -> bool {
13176    visibility
13177        .field_declared_type_fact(analyzer, field)
13178        .is_none_or(|fact| !fact.binds_indirectly)
13179}
13180
13181/// Resolve the receiver type minted for a named declarator on an anonymous C
13182/// aggregate, such as `struct { int r; } c`. The declaration index preserves
13183/// the aggregate as the nested class `Owner$c`, but the field's type fact has
13184/// no spelling that can name that class. Confirm the anonymous aggregate from
13185/// its parsed declaration, then use the owner's structured child relationship
13186/// to recover the one corresponding receiver type.
13187fn anonymous_aggregate_field_owner(
13188    analyzer: &CppGraphSource<'_>,
13189    visibility: &VisibilityIndex<'_>,
13190    visible_from: &ProjectFile,
13191    field: &CodeUnit,
13192) -> Option<CodeUnit> {
13193    let owner = type_owner_of(analyzer, field)?;
13194    if !owner.is_class() {
13195        return None;
13196    }
13197    let declaration = analyzer.get_source(field, false)?;
13198    let mut parser = Parser::new();
13199    parser
13200        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13201        .ok()?;
13202    let tree = parser.parse(&declaration, None)?;
13203    let mut stack = vec![tree.root_node()];
13204    while let Some(node) = stack.pop() {
13205        if matches!(node.kind(), "declaration" | "field_declaration")
13206            && let Some(type_node) = node
13207                .child_by_field_name("type")
13208                .or_else(|| first_type_child(node))
13209            && matches!(type_node.kind(), "struct_specifier" | "union_specifier")
13210            && type_node.child_by_field_name("name").is_none()
13211            && declared_name_binding(node, type_node, field.identifier(), &declaration).is_some()
13212        {
13213            let matches = visibility
13214                .visible_members_for_owner_name(visible_from, &owner, field.identifier())
13215                .into_iter()
13216                .filter(|child| child.is_class() && child.identifier() == field.identifier())
13217                .collect::<Vec<_>>();
13218            return match matches.as_slice() {
13219                [child] => Some((*child).clone()),
13220                _ => None,
13221            };
13222        }
13223        let mut cursor = node.walk();
13224        stack.extend(node.named_children(&mut cursor));
13225    }
13226    None
13227}
13228
13229/// Resolve an anonymous C aggregate's generated owner from its declaration
13230/// range. Anonymous local structs and unions have no type name to enter into
13231/// the visibility index; declaration extraction gives them a structured class
13232/// identity keyed by the aggregate node's exact CST range instead. Matching
13233/// that range keeps nested aggregates and unrelated generated owners out of
13234/// the result without inspecting source text.
13235pub fn anonymous_aggregate_owner(
13236    analyzer: &CppGraphSource<'_>,
13237    file: &ProjectFile,
13238    node: Node<'_>,
13239) -> Option<CodeUnit> {
13240    if !matches!(node.kind(), "struct_specifier" | "union_specifier")
13241        || node.child_by_field_name("name").is_some()
13242    {
13243        return None;
13244    }
13245    let mut candidates = analyzer
13246        .declarations(file)
13247        .into_iter()
13248        .filter(|candidate| {
13249            candidate.is_class()
13250                && analyzer.ranges(candidate).into_iter().any(|range| {
13251                    range.start_byte == node.start_byte() && range.end_byte == node.end_byte()
13252                })
13253        })
13254        .collect::<Vec<_>>();
13255    candidates.sort_by_key(|candidate| candidate.fq_name());
13256    candidates.dedup();
13257    match candidates.as_slice() {
13258        [candidate] => Some(candidate.clone()),
13259        _ => None,
13260    }
13261}
13262
13263/// The one logical type the candidates name, or why they do not name one.
13264fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
13265    let Some(first) = candidates.first() else {
13266        return Err(TypeCandidateFailure::Unresolvable);
13267    };
13268    if candidates
13269        .iter()
13270        .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
13271    {
13272        Ok((*first).clone())
13273    } else {
13274        Err(TypeCandidateFailure::Ambiguous)
13275    }
13276}
13277
13278fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
13279    logical_type_candidate(candidates).ok()
13280}
13281
13282fn unique_type_candidate_preserving_alias(
13283    analyzer: &CppGraphSource<'_>,
13284    file: &ProjectFile,
13285    candidates: &[&CodeUnit],
13286) -> Option<CodeUnit> {
13287    let first = *candidates.first()?;
13288    if declared_type_alias(analyzer, first) {
13289        return candidates
13290            .iter()
13291            .all(|candidate| {
13292                declared_type_alias(analyzer, candidate)
13293                    && candidate.kind() == first.kind()
13294                    && candidate.fq_name() == first.fq_name()
13295                    && candidate.source() == first.source()
13296            })
13297            .then(|| first.clone());
13298    }
13299    if analyzer.reference_uses_c_semantics(file)
13300        && first.is_class()
13301        && indexed_c_tag_kind(analyzer, first).is_some()
13302    {
13303        let mut full_source = None;
13304        let mut tag_kind = None;
13305        for candidate in candidates.iter().copied() {
13306            let candidate_tag_kind = indexed_c_tag_kind(analyzer, candidate)?;
13307            if tag_kind
13308                .replace(candidate_tag_kind)
13309                .is_some_and(|existing| existing != candidate_tag_kind)
13310            {
13311                return None;
13312            }
13313            if cpp_class_declaration_strength(analyzer, candidate)
13314                == CppClassDeclarationStrength::Full
13315                && full_source
13316                    .replace(candidate.source())
13317                    .is_some_and(|existing| existing != candidate.source())
13318            {
13319                return None;
13320            }
13321        }
13322    }
13323    candidates
13324        .iter()
13325        .all(|candidate| {
13326            !declared_type_alias(analyzer, candidate)
13327                && candidate.kind() == first.kind()
13328                && candidate.fq_name() == first.fq_name()
13329        })
13330        .then(|| first.clone())
13331}
13332
13333pub(crate) fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
13334    brokk_bifrost_core::analyzer::capabilities::provider_declares_type_alias(
13335        analyzer.type_alias_provider(),
13336        unit,
13337    )
13338}
13339
13340pub fn field_declared_type_binding(
13341    analyzer: &CppGraphSource<'_>,
13342    visibility: &VisibilityIndex<'_>,
13343    visible_from: &ProjectFile,
13344    field: &CodeUnit,
13345) -> Option<(String, Option<CodeUnit>, i32)> {
13346    let fact = visibility.field_declared_type_fact(analyzer, field)?;
13347    let normalized = normalize_field_type_text(&fact.type_text);
13348    let primary = visibility.resolve_unique_canonical_type_for_declaration(
13349        analyzer,
13350        visible_from,
13351        field,
13352        &normalized,
13353    );
13354    let resolved = match (primary, fact.template_arguments.as_deref()) {
13355        (Some(primary), Some(arguments)) => visibility
13356            .resolve_template_arguments(visible_from, primary, arguments)
13357            .ok(),
13358        (resolved, None) => resolved,
13359        (None, Some(_)) => None,
13360    };
13361    Some((normalized, resolved, fact.indirection))
13362}
13363
13364fn decode_field_declared_type_fact(
13365    analyzer: &CppGraphSource<'_>,
13366    field: &CodeUnit,
13367) -> Option<DeclaredFieldTypeFact> {
13368    if let Some(facts) = analyzer.declaration_source_properties(field)
13369        && let Some(first) = facts.first().and_then(|fact| fact.field_type.as_ref())
13370        && facts
13371            .iter()
13372            .all(|fact| fact.field_type.as_ref() == Some(first))
13373    {
13374        return Some(first.clone());
13375    }
13376    let Some(declaration) = analyzer.get_source(field, false) else {
13377        return decode_indexed_field_declared_type_fact(analyzer, field);
13378    };
13379    let mut parser = Parser::new();
13380    parser
13381        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13382        .ok()?;
13383    // A field's indexed source is stored without its surrounding class body.
13384    // Give tree-sitter that grammatical context before checking recovery-only
13385    // field shapes such as `PyObject_HEAD Imaging image;`.
13386    let contextual_declaration = format!("struct __bifrost_field_context {{ {declaration} }};");
13387    let contextual_tree = parser.parse(&contextual_declaration, None)?;
13388    let mut stack = vec![contextual_tree.root_node()];
13389    while let Some(node) = stack.pop() {
13390        if let Some(recovered) = recovered_pyobject_head_field(node, &contextual_declaration)
13391            && node_text(recovered.name, &contextual_declaration) == field.identifier()
13392        {
13393            return Some(DeclaredFieldTypeFact {
13394                type_text: node_text(recovered.type_node, &contextual_declaration).to_string(),
13395                indirection: recovered.pointer_depth(),
13396                binds_indirectly: recovered.pointer_depth() > 0,
13397                template_arguments: None,
13398            });
13399        }
13400        if let Some(recovered) =
13401            recovered_function_like_field_declarator(node, &contextual_declaration)
13402            && node_text(recovered.name, &contextual_declaration) == field.identifier()
13403        {
13404            let type_node = node
13405                .child_by_field_name("type")
13406                .or_else(|| first_type_child(node))?;
13407            return Some(DeclaredFieldTypeFact {
13408                type_text: node_text(type_node, &contextual_declaration).to_string(),
13409                indirection: recovered.pointer_depth(),
13410                // A function-like declarator names a callable member, never a
13411                // subobject of the declared return type.
13412                binds_indirectly: true,
13413                template_arguments: cpp_template_reference_arguments(
13414                    type_node,
13415                    &contextual_declaration,
13416                ),
13417            });
13418        }
13419        if let Some(fact) =
13420            decode_declared_field_type_node(node, field.identifier(), &contextual_declaration)
13421        {
13422            return Some(fact);
13423        }
13424        let mut cursor = node.walk();
13425        stack.extend(node.named_children(&mut cursor));
13426    }
13427    let tree = parser.parse(&declaration, None)?;
13428    let mut stack = vec![tree.root_node()];
13429    while let Some(node) = stack.pop() {
13430        if let Some(fact) = decode_declared_field_type_node(node, field.identifier(), &declaration)
13431        {
13432            return Some(fact);
13433        }
13434        let mut cursor = node.walk();
13435        stack.extend(node.named_children(&mut cursor));
13436    }
13437    None
13438}
13439
13440/// Decode a field whose generated owner prevents the ordinary source lookup
13441/// from selecting a standalone declaration. The indexed ranges still point
13442/// into the physical syntax tree, so recover the enclosing declaration from
13443/// that structure and apply the same declarator decoder to it.
13444fn decode_indexed_field_declared_type_fact(
13445    analyzer: &CppGraphSource<'_>,
13446    field: &CodeUnit,
13447) -> Option<DeclaredFieldTypeFact> {
13448    let cpp = analyzer.cpp?;
13449    let prepared = cpp.prepared_syntax(analyzer.token, field.source())?;
13450    let source = prepared.source();
13451    let root = prepared.tree().root_node();
13452    for range in analyzer.ranges(field) {
13453        let end = range.start_byte.saturating_add(1).min(source.len());
13454        let mut current = root.descendant_for_byte_range(range.start_byte, end);
13455        while let Some(node) = current {
13456            if matches!(node.kind(), "declaration" | "field_declaration")
13457                && let Some(fact) =
13458                    decode_declared_field_type_node(node, field.identifier(), source)
13459            {
13460                return Some(fact);
13461            }
13462            current = node.parent();
13463        }
13464    }
13465    None
13466}
13467
13468fn decode_declared_field_type_node(
13469    node: Node<'_>,
13470    field_name: &str,
13471    source: &str,
13472) -> Option<DeclaredFieldTypeFact> {
13473    if !matches!(node.kind(), "declaration" | "field_declaration") {
13474        return None;
13475    }
13476    let type_node = node
13477        .child_by_field_name("type")
13478        .or_else(|| first_type_child(node))?;
13479    cpp_declared_field_type_fact(node, type_node, field_name, source)
13480}
13481
13482pub(crate) fn cpp_declared_field_type_fact(
13483    node: Node<'_>,
13484    type_node: Node<'_>,
13485    field_name: &str,
13486    source: &str,
13487) -> Option<DeclaredFieldTypeFact> {
13488    let binding = declared_name_binding(node, type_node, field_name, source)?;
13489    let declared_type = if matches!(
13490        type_node.kind(),
13491        "class_specifier" | "struct_specifier" | "union_specifier"
13492    ) {
13493        type_node.child_by_field_name("name")
13494    } else {
13495        Some(type_node)
13496    };
13497    Some(DeclaredFieldTypeFact {
13498        type_text: declared_type.map_or_else(
13499            || field_name.to_string(),
13500            |declared_type| node_text(declared_type, source).to_string(),
13501        ),
13502        indirection: binding.pointer_depth,
13503        binds_indirectly: binding.indirect,
13504        template_arguments: declared_type
13505            .and_then(|declared_type| cpp_template_reference_arguments(declared_type, source)),
13506    })
13507}
13508
13509/// Text of the type that a C or C++ alias declaration names, read from the
13510/// `type_definition` or `alias_declaration` node's `type` field.
13511///
13512/// The declaration text is never scanned. A function-pointer typedef
13513/// interleaves its aliased type with its declarator (`typedef R (*F)(int)`),
13514/// so no prefix or suffix of the spelling isolates the target.
13515///
13516/// An alias whose declarator is a function declarator names a function type:
13517/// `typedef R F(int)`, `typedef R (*F)(int)`, `typedef R *F(int)`, and
13518/// `using F = R (*)(int)`. The analyzer's type model names declared types only,
13519/// so such an alias has no canonical target. Its `type` field holds the return
13520/// type `R`, which is a different type from the alias, so this returns `None`
13521/// rather than that return type.
13522pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
13523    let mut parser = Parser::new();
13524    parser
13525        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13526        .ok()?;
13527    let tree = parser.parse(declaration, None)?;
13528    let mut stack = vec![tree.root_node()];
13529    while let Some(node) = stack.pop() {
13530        let type_node = match node.kind() {
13531            "type_definition" => {
13532                let mut cursor = node.walk();
13533                if node
13534                    .children_by_field_name("declarator", &mut cursor)
13535                    .any(declarator_names_function_type)
13536                {
13537                    return None;
13538                }
13539                node.child_by_field_name("type")?
13540            }
13541            "alias_declaration" => {
13542                let type_node = node.child_by_field_name("type")?;
13543                if type_node
13544                    .child_by_field_name("declarator")
13545                    .is_some_and(declarator_names_function_type)
13546                {
13547                    return None;
13548                }
13549                type_node
13550            }
13551            _ => {
13552                let mut cursor = node.walk();
13553                let children = node.named_children(&mut cursor).collect::<Vec<_>>();
13554                stack.extend(children.into_iter().rev());
13555                continue;
13556            }
13557        };
13558        return Some(node_text(type_node, declaration).to_string());
13559    }
13560    None
13561}
13562
13563/// True when an alias declarator names a function type.
13564///
13565/// The declarator chain is walked through the `declarator` field, so the
13566/// parameter list -- a sibling field -- is never entered and a parameter's own
13567/// function declarator cannot be mistaken for the alias's.
13568pub(crate) fn declarator_names_function_type(declarator: Node<'_>) -> bool {
13569    let mut current = Some(declarator);
13570    while let Some(node) = current {
13571        match node.kind() {
13572            "function_declarator" | "abstract_function_declarator" => return true,
13573            "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
13574                current = node.named_child(0);
13575            }
13576            _ => current = node.child_by_field_name("declarator"),
13577        }
13578    }
13579    false
13580}
13581
13582fn decode_structured_alias_target(
13583    analyzer: &CppGraphSource<'_>,
13584    unit: &CodeUnit,
13585) -> Option<StructuredAliasTarget> {
13586    let facts = analyzer.declaration_source_properties(unit)?;
13587    let first = facts.first()?.alias_target.as_ref()?;
13588    facts
13589        .iter()
13590        .all(|fact| fact.alias_target.as_ref() == Some(first))
13591        .then(|| first.clone())
13592}
13593
13594pub(crate) fn structured_alias_type_target(
13595    mut type_node: Node<'_>,
13596    source: &str,
13597) -> Option<StructuredAliasTarget> {
13598    while type_node.kind() == "type_descriptor" {
13599        type_node = type_node.child_by_field_name("type")?;
13600    }
13601    if type_node.kind() == "primitive_type" {
13602        return Some(StructuredAliasTarget::Builtin);
13603    }
13604    if matches!(
13605        type_node.kind(),
13606        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
13607    ) {
13608        type_node = type_node.child_by_field_name("name")?;
13609    }
13610    let global = type_node.child_by_field_name("scope").is_none()
13611        && type_node.child(0).is_some_and(|child| child.kind() == "::");
13612    let mut components = Vec::new();
13613    append_structured_type_components(type_node, source, &mut components)?;
13614    let arguments = cpp_template_reference_arguments(type_node, source);
13615    (!components.is_empty()).then_some(StructuredAliasTarget::Named {
13616        components,
13617        global,
13618        arguments,
13619    })
13620}
13621
13622fn append_structured_type_components(
13623    node: Node<'_>,
13624    source: &str,
13625    out: &mut Vec<String>,
13626) -> Option<()> {
13627    match node.kind() {
13628        "identifier" | "namespace_identifier" | "type_identifier" => {
13629            out.push(node_text(node, source).to_string());
13630            Some(())
13631        }
13632        "template_type" | "template_function" => {
13633            append_structured_type_components(node.child_by_field_name("name")?, source, out)
13634        }
13635        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
13636            let (scope, name) = qualified_name_parts(node)?;
13637            if let Some(scope) = scope {
13638                append_structured_type_components(scope, source, out)?;
13639            }
13640            append_structured_type_components(name, source, out)
13641        }
13642        _ => None,
13643    }
13644}
13645
13646/// How the declarator that names a declared entity binds the declaration's
13647/// type.
13648pub(crate) struct DeclaredNameBinding {
13649    /// Pointer declarators between the type and the name.
13650    pub pointer_depth: i32,
13651    /// The name binds the declared type through a pointer or a reference, so
13652    /// the declared entity holds no subobject of that type. An array of the
13653    /// type does hold subobjects, so it is not indirect.
13654    pub indirect: bool,
13655}
13656
13657pub(crate) fn declared_name_binding(
13658    declaration: Node<'_>,
13659    type_node: Node<'_>,
13660    field_name: &str,
13661    source: &str,
13662) -> Option<DeclaredNameBinding> {
13663    let mut stack = Vec::new();
13664    let mut cursor = declaration.walk();
13665    stack.extend(
13666        declaration
13667            .named_children(&mut cursor)
13668            .filter(|child| !same_node(*child, type_node)),
13669    );
13670    while let Some(node) = stack.pop() {
13671        if matches!(node.kind(), "identifier" | "field_identifier")
13672            && node_text(node, source) == field_name
13673        {
13674            let mut binding = DeclaredNameBinding {
13675                pointer_depth: 0,
13676                indirect: false,
13677            };
13678            let mut current = node.parent();
13679            while let Some(parent) = current {
13680                if same_node(parent, declaration) {
13681                    return Some(binding);
13682                }
13683                match parent.kind() {
13684                    "pointer_declarator" => {
13685                        binding.pointer_depth += 1;
13686                        binding.indirect = true;
13687                    }
13688                    "reference_declarator" => binding.indirect = true,
13689                    _ => {}
13690                }
13691                current = parent.parent();
13692            }
13693            return None;
13694        }
13695        let mut cursor = node.walk();
13696        stack.extend(node.named_children(&mut cursor));
13697    }
13698    None
13699}
13700
13701fn normalize_field_type_text(type_text: &str) -> String {
13702    const FIELD_SPECIFIERS: [&str; 8] = [
13703        "extern ",
13704        "static ",
13705        "mutable ",
13706        "constexpr ",
13707        "constinit ",
13708        "inline ",
13709        "volatile ",
13710        "const ",
13711    ];
13712
13713    let mut normalized = normalize_type_text(type_text);
13714    loop {
13715        let Some(stripped) = FIELD_SPECIFIERS
13716            .iter()
13717            .find_map(|specifier| normalized.strip_prefix(specifier))
13718        else {
13719            return normalized;
13720        };
13721        normalized = normalize_type_text(stripped);
13722    }
13723}
13724
13725pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
13726    let Some(type_node) = node.child_by_field_name("type") else {
13727        return false;
13728    };
13729    ctx.visibility.resolves_to_type(
13730        &ctx.analyzer,
13731        ctx.file,
13732        node_text(type_node, ctx.source),
13733        owner,
13734    )
13735}
13736
13737pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13738    !ctx.analyzer
13739        .declarations(ctx.file)
13740        .into_iter()
13741        .filter(|unit| unit.is_function())
13742        .any(|unit| {
13743            ctx.analyzer.ranges(&unit).iter().any(|range| {
13744                node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
13745            })
13746        })
13747}
13748
13749/// How a `T var ...;` declaration initializes its object.
13750pub enum DeclarationConstructorInitializer<'tree> {
13751    /// Direct initialization, `T var(args)` or `T var{args}`: the argument list
13752    /// the declaration hands the constructor.
13753    Arguments(Node<'tree>),
13754    /// Copy initialization from one expression, `T var = expr`, which supplies a
13755    /// single constructor argument without spelling an argument list.
13756    Expression(Node<'tree>),
13757    /// `T var;`, which names no constructor argument at all.
13758    Empty,
13759}
13760
13761pub fn declaration_constructor_initializer(
13762    node: Node<'_>,
13763) -> DeclarationConstructorInitializer<'_> {
13764    let mut cursor = node.walk();
13765    for child in node.named_children(&mut cursor) {
13766        if child.kind() == "init_declarator" {
13767            let Some(value) = child
13768                .child_by_field_name("value")
13769                .or_else(|| first_named_child_of_kind(child, "initializer_list"))
13770                .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
13771            else {
13772                return DeclarationConstructorInitializer::Empty;
13773            };
13774            return match value.kind() {
13775                "argument_list" | "initializer_list" => {
13776                    DeclarationConstructorInitializer::Arguments(value)
13777                }
13778                "compound_literal_expression" => call_arguments_node(value)
13779                    .map_or(DeclarationConstructorInitializer::Empty, |arguments| {
13780                        DeclarationConstructorInitializer::Arguments(arguments)
13781                    }),
13782                _ => DeclarationConstructorInitializer::Expression(value),
13783            };
13784        }
13785        if let Some(declarator) = declaration_declarator(node, child) {
13786            return declarator_parameters(declarator)
13787                .map_or(DeclarationConstructorInitializer::Empty, |parameters| {
13788                    DeclarationConstructorInitializer::Arguments(parameters)
13789                });
13790        }
13791    }
13792    DeclarationConstructorInitializer::Empty
13793}
13794
13795pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
13796    match declaration_constructor_initializer(node) {
13797        DeclarationConstructorInitializer::Arguments(arguments) => {
13798            argument_children(arguments).count()
13799        }
13800        DeclarationConstructorInitializer::Expression(_) => 1,
13801        DeclarationConstructorInitializer::Empty => 0,
13802    }
13803}
13804
13805/// The parameter list of the innermost declarator, which is where a
13806/// `T var(args)` declaration parsed as a function declarator keeps the
13807/// constructor arguments.
13808fn declarator_parameters(node: Node<'_>) -> Option<Node<'_>> {
13809    let mut current = node;
13810    loop {
13811        if let Some(parameters) = current.child_by_field_name("parameters") {
13812            return Some(parameters);
13813        }
13814        current = current.child_by_field_name("declarator")?;
13815    }
13816}
13817
13818fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
13819    brokk_bifrost_core::analyzer::tree_walk::first_named_descendant_of_kind_inclusive(root, kind)
13820}
13821
13822fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
13823    if node.kind() == "identifier" {
13824        return true;
13825    }
13826    if node.kind() == "parenthesized_expression" {
13827        return false;
13828    }
13829    if node.kind() == "call_expression" {
13830        return node
13831            .child_by_field_name("function")
13832            .is_some_and(|function| function.kind() == "identifier");
13833    }
13834    let mut stack = vec![node];
13835    while let Some(descendant) = stack.pop() {
13836        if descendant != node && descendant.kind() == "parenthesized_expression" {
13837            continue;
13838        }
13839        if descendant.kind() == "identifier" {
13840            return true;
13841        }
13842        if descendant.kind() == "call_expression" {
13843            if descendant
13844                .child_by_field_name("function")
13845                .is_some_and(|function| function.kind() == "identifier")
13846            {
13847                return true;
13848            }
13849            continue;
13850        }
13851        push_named_children_reversed(descendant, &mut stack);
13852    }
13853    false
13854}
13855
13856fn macro_expansion_shape_is_safe(
13857    node: Node<'_>,
13858    source: &str,
13859    parameters: &[String],
13860    environment: &MacroEnvironment,
13861) -> bool {
13862    if matches!(node.kind(), "identifier" | "parenthesized_expression") {
13863        return true;
13864    }
13865    if node.kind() == "call_expression" {
13866        let Some(function) = node.child_by_field_name("function") else {
13867            return true;
13868        };
13869        if function.kind() != "identifier" {
13870            return true;
13871        }
13872        let function_name = node_text(function, source);
13873        if parameters
13874            .iter()
13875            .any(|parameter| parameter == function_name)
13876        {
13877            return false;
13878        }
13879        if !environment.may_bind(function_name) {
13880            return true;
13881        }
13882        let Some(arguments) = node.child_by_field_name("arguments") else {
13883            return false;
13884        };
13885        return argument_children(arguments).all(|argument| {
13886            if argument.kind() == "identifier"
13887                && parameters
13888                    .iter()
13889                    .any(|parameter| parameter == node_text(argument, source))
13890            {
13891                return false;
13892            }
13893            macro_expansion_shape_is_safe(argument, source, parameters, environment)
13894        });
13895    }
13896    let mut stack = vec![node];
13897    while let Some(descendant) = stack.pop() {
13898        if descendant != node {
13899            if descendant.kind() == "parenthesized_expression" {
13900                continue;
13901            }
13902            if descendant.kind() == "call_expression" {
13903                let expands = descendant
13904                    .child_by_field_name("function")
13905                    .filter(|function| function.kind() == "identifier")
13906                    .is_some_and(|function| environment.may_bind(node_text(function, source)));
13907                if expands {
13908                    return false;
13909                }
13910                continue;
13911            }
13912        }
13913        if descendant.kind() == "identifier" {
13914            let identifier = node_text(descendant, source);
13915            if parameters.iter().any(|parameter| parameter == identifier)
13916                || environment.may_bind(identifier)
13917            {
13918                return false;
13919            }
13920        }
13921        push_named_children_reversed(descendant, &mut stack);
13922    }
13923    true
13924}
13925
13926fn collect_structured_include_facts(prepared: &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]> {
13927    let source = prepared.source();
13928    let mut facts = Vec::new();
13929    let mut nodes = vec![prepared.tree().root_node()];
13930    while let Some(node) = nodes.pop() {
13931        if node.kind() == "preproc_include" {
13932            let Some(path) = node
13933                .child_by_field_name("path")
13934                .and_then(|path| crate::imports::include_path_literal_value(path, source))
13935                .map(str::to_owned)
13936            else {
13937                continue;
13938            };
13939            facts.push(StructuredIncludeFact {
13940                start_byte: node.start_byte(),
13941                end_byte: node.end_byte(),
13942                path,
13943            });
13944            continue;
13945        }
13946        push_named_children_reversed(node, &mut nodes);
13947    }
13948    Arc::from(facts.into_boxed_slice())
13949}
13950
13951fn has_unresolved_include_visible_before_in_prepared(
13952    file: &ProjectFile,
13953    prepared: &PreparedSyntaxTree,
13954    include_targets: &IncludeTargetIndex,
13955    facts: &[StructuredIncludeFact],
13956    before_byte: usize,
13957) -> bool {
13958    let guards = OnceCell::new();
13959    let reference = CallableReferenceContext {
13960        file,
13961        position: Some(CallableReferencePosition {
13962            prepared,
13963            byte: before_byte,
13964            guards: &guards,
13965        }),
13966    };
13967    let root = prepared.tree().root_node();
13968    facts
13969        .iter()
13970        .filter(|fact| fact.end_byte <= before_byte)
13971        .any(|fact| {
13972            let node = root
13973                .descendant_for_byte_range(fact.start_byte, fact.end_byte)
13974                .expect("structured include fact range must be in prepared tree");
13975            assert_eq!(
13976                node.kind(),
13977                "preproc_include",
13978                "structured include fact range must identify its include node"
13979            );
13980            callable_preprocessor_context_is_visible_for_reference(
13981                node,
13982                prepared.source(),
13983                &reference,
13984            ) && resolve_include_targets_with_index(file, &fact.path, include_targets).is_empty()
13985        })
13986}
13987
13988fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
13989    let descendant = node;
13990    while let Some(parent) = node.parent() {
13991        if is_cpp_preprocessor_conditional(parent)
13992            && !is_file_covering_include_guard(parent, source)
13993            && preprocessor_conditional_contains_descendant(parent, descendant)
13994        {
13995            return true;
13996        }
13997        node = parent;
13998    }
13999    false
14000}
14001
14002/// The [`OwningPreprocessorConditionals`] of a macro event at `event`.
14003///
14004/// The descendant this walks up from is the one
14005/// [`VisibilityIndex::macro_event_condition_value`] starts from -- the
14006/// innermost node at the event's first byte, not the event node itself --
14007/// because containment compares that descendant's end against the recovered
14008/// conditional boundary, and the two nodes end in different places. An event
14009/// that [`has_preprocessor_conditional_ancestor`] rejects owns nothing: that
14010/// predicate is what has always decided whether an event is conditional at
14011/// all, and answering it first also skips the walk for the ordinary
14012/// unconditional event.
14013fn owning_preprocessor_conditionals(
14014    root: Node<'_>,
14015    event: Node<'_>,
14016    source: &str,
14017) -> OwningPreprocessorConditionals {
14018    if !has_preprocessor_conditional_ancestor(event, source) {
14019        return OwningPreprocessorConditionals::default();
14020    }
14021    let start = event.start_byte();
14022    let descendant = root
14023        .descendant_for_byte_range(start, start.saturating_add(1).min(source.len()))
14024        .expect("a byte inside the parsed tree names a descendant");
14025    let mut owners = Vec::new();
14026    let mut current = descendant.parent();
14027    while let Some(conditional) = current {
14028        if is_cpp_preprocessor_conditional(conditional)
14029            && !is_file_covering_include_guard(conditional, source)
14030            && preprocessor_conditional_contains_descendant(conditional, descendant)
14031        {
14032            owners.push(conditional.start_byte());
14033        }
14034        current = conditional.parent();
14035    }
14036    owners.into_boxed_slice()
14037}
14038
14039fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
14040    node.parent()
14041        .filter(|parent| parent.kind() == "translation_unit")
14042        .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
14043        && is_canonical_include_guard(node, source)
14044}
14045
14046fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
14047    if node.kind() != "preproc_ifdef"
14048        || node
14049            .child(0)
14050            .is_none_or(|directive| directive.kind() != "#ifndef")
14051        || node.child_by_field_name("alternative").is_some()
14052    {
14053        return false;
14054    }
14055    let Some(guard_name) = node.child_by_field_name("name") else {
14056        return false;
14057    };
14058    let mut cursor = node.walk();
14059    node.named_children(&mut cursor)
14060        .find(|child| *child != guard_name && child.kind() != "comment")
14061        .filter(|child| child.kind() == "preproc_def")
14062        .and_then(|definition| definition.child_by_field_name("name"))
14063        .is_some_and(|defined_name| {
14064            node_text(defined_name, source) == node_text(guard_name, source)
14065        })
14066}
14067
14068fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
14069    let mut guard = None;
14070    for child in named_children_iter(root) {
14071        if child.kind() == "comment" || is_pragma_once(child, source) {
14072            continue;
14073        }
14074        if guard.is_none() && is_canonical_include_guard(child, source) {
14075            guard = Some(child);
14076        } else {
14077            return None;
14078        }
14079    }
14080    guard
14081        .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
14082        .map(|name| node_text(name, source).to_string())
14083}
14084
14085fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
14086    if (0..root.named_child_count())
14087        .filter_map(|index| root.named_child(index))
14088        .any(|child| is_pragma_once(child, source))
14089    {
14090        return MacroIncludeProtection::PragmaOnce;
14091    }
14092    top_level_canonical_include_guard_name(root, source)
14093        .map(MacroIncludeProtection::MacroGuard)
14094        .unwrap_or(MacroIncludeProtection::None)
14095}
14096
14097fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
14098    node.kind() == "preproc_call"
14099        && node
14100            .child_by_field_name("directive")
14101            .is_some_and(|directive| node_text(directive, source) == "#pragma")
14102        && node
14103            .child_by_field_name("argument")
14104            .is_some_and(|argument| node_text(argument, source).trim() == "once")
14105}
14106
14107fn parse_preproc_identifier(argument: &str) -> Option<String> {
14108    let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
14109    let mut parser = Parser::new();
14110    parser
14111        .set_language(&tree_sitter_cpp::LANGUAGE.into())
14112        .ok()?;
14113    let tree = parser.parse(&sentinel, None)?;
14114    if tree.root_node().has_error() {
14115        return None;
14116    }
14117    let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
14118    let identifier = statement.named_child(0)?;
14119    (identifier.kind() == "identifier" && statement.named_child_count() == 1)
14120        .then(|| node_text(identifier, &sentinel).to_string())
14121}
14122
14123pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
14124    match node.kind() {
14125        "identifier" | "field_identifier" => {
14126            let name = node_text(node, source).trim();
14127            (!name.is_empty()).then(|| name.to_string())
14128        }
14129        "abstract_array_declarator"
14130        | "abstract_function_declarator"
14131        | "abstract_parenthesized_declarator"
14132        | "abstract_pointer_declarator"
14133        | "abstract_reference_declarator" => None,
14134        "function_declarator" => node
14135            .child_by_field_name("declarator")
14136            .or_else(|| node.child_by_field_name("name"))
14137            .and_then(|child| extract_variable_name(child, source)),
14138        _ => node
14139            .child_by_field_name("declarator")
14140            .or_else(|| node.child_by_field_name("name"))
14141            .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
14142            .and_then(|child| extract_variable_name(child, source)),
14143    }
14144}
14145
14146/// Whether `file` is proven to use plain-C source semantics.
14147///
14148/// `Language::Cpp` intentionally serves both C and C++. Headers do not carry a
14149/// compilation dialect on their own, so only an exact `.c` source extension is
14150/// sufficient to reinterpret C++-grammar keyword nodes such as `this` as C
14151/// identifiers.
14152///
14153/// The exact-lowercase-`.c` rule itself lives in [`LanguageDialect::for_path`],
14154/// which extraction reads too (a `.c` file is extracted with C tag scope), so
14155/// the doctrine has exactly one definition.
14156pub fn is_c_source_file(file: &ProjectFile) -> bool {
14157    LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
14158}
14159
14160/// Whether tree-sitter parsed the operand of C `sizeof(T)` as an expression
14161/// identifier even though `T` may denote a typedef.
14162///
14163/// The grammar cannot distinguish `sizeof(value)` from `sizeof(Type)` without
14164/// semantic information. Keep this helper structural and narrow; callers must
14165/// still prove a visible type and reject an active ordinary-namespace shadow.
14166pub fn is_c_sizeof_expression_type_candidate(file: &ProjectFile, node: Node<'_>) -> bool {
14167    if !is_c_source_file(file) || node.kind() != "identifier" {
14168        return false;
14169    }
14170    let mut operand = node;
14171    while let Some(parent) = operand.parent().filter(|parent| {
14172        parent.kind() == "parenthesized_expression"
14173            && parent.named_child_count() == 1
14174            && parent.named_child(0) == Some(operand)
14175    }) {
14176        operand = parent;
14177    }
14178    operand.parent().is_some_and(|parent| {
14179        parent.kind() == "sizeof_expression" && parent.child_by_field_name("value") == Some(operand)
14180    })
14181}
14182
14183/// Return the type and member leaves of a C `offsetof` member designator.
14184///
14185/// `offsetof_expression` is a dedicated tree-sitter node, so its two operands
14186/// must be interpreted through their named fields.  In particular, do not
14187/// infer the aggregate from the enclosing lexical scope: an `offsetof` can
14188/// name a member of an unrelated aggregate, including a field promoted from
14189/// an anonymous union.  A missing or unsupported operand is deliberately
14190/// rejected so callers can keep the reference unresolved.
14191pub fn c_offsetof_member_parts(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
14192    if !is_c_offsetof_member_node(node) {
14193        return None;
14194    }
14195    let expression = node.parent()?;
14196    if expression.has_error() {
14197        return None;
14198    }
14199    let closing = expression.child(expression.child_count().saturating_sub(1))?;
14200    if closing.kind() != ")" || closing.is_missing() {
14201        return None;
14202    }
14203    let type_descriptor = expression.child_by_field_name("type")?;
14204    if type_descriptor.kind() != "type_descriptor"
14205        || type_descriptor.is_missing()
14206        || type_descriptor.has_error()
14207    {
14208        return None;
14209    }
14210    let type_specifier = type_descriptor.child_by_field_name("type")?;
14211    if type_specifier.is_missing() || type_specifier.has_error() {
14212        return None;
14213    }
14214    let type_reference = match type_specifier.kind() {
14215        "class_specifier" | "struct_specifier" | "union_specifier" => {
14216            type_specifier.child_by_field_name("name")?
14217        }
14218        _ => type_specifier,
14219    };
14220    (!type_reference.is_missing() && !type_reference.has_error()).then_some((type_reference, node))
14221}
14222
14223/// Whether `node` is the member leaf of an `offsetof_expression`, including a
14224/// malformed type operand.  Callers use this guard to prevent the ordinary
14225/// field-name heuristics from guessing an owner after structured resolution
14226/// has failed.
14227pub fn is_c_offsetof_member_node(node: Node<'_>) -> bool {
14228    node.kind() == "field_identifier"
14229        && node.parent().is_some_and(|parent| {
14230            parent.kind() == "offsetof_expression"
14231                && parent.child_by_field_name("member") == Some(node)
14232        })
14233}
14234
14235/// Admit an ABI 15 template-function shape to type resolution only through
14236/// the structured argument list of a type descriptor. This is syntax evidence;
14237/// callers must still resolve a visible type binding before proving identity.
14238pub fn cpp_template_function_has_type_owner(node: Node<'_>) -> bool {
14239    if node.kind() != "template_function" {
14240        return false;
14241    }
14242    let Some(arguments) = node
14243        .parent()
14244        .filter(|parent| parent.kind() == "template_argument_list")
14245    else {
14246        return false;
14247    };
14248    let Some(owner) = arguments.parent().filter(|parent| {
14249        parent.kind() == "template_type"
14250            && parent.child_by_field_name("arguments") == Some(arguments)
14251    }) else {
14252        return false;
14253    };
14254
14255    let mut current = owner;
14256    while let Some(parent) = current.parent() {
14257        if parent.kind() == "type_descriptor" && parent.child_by_field_name("type") == Some(current)
14258        {
14259            return true;
14260        }
14261        if parent.kind() == "qualified_identifier"
14262            && parent.child_by_field_name("name") == Some(current)
14263        {
14264            current = parent;
14265            continue;
14266        }
14267        return false;
14268    }
14269    false
14270}
14271
14272/// Whether `node` is a template argument name that tree-sitter spelled with
14273/// type syntax.
14274///
14275/// The grammar cannot tell a type argument from a non-type (value) argument, so
14276/// it gives both the same shape:
14277/// `template_argument_list -> type_descriptor -> type_identifier`. In
14278/// `std::array<W, N>` the type `W` and the constant `N` parse identically, and
14279/// so do `std::span<const uint8_t, ED448_LEN>`'s length and a nested type
14280/// member used as a real type argument.
14281///
14282/// This helper reports only the syntactic position. A caller must still prove
14283/// which namespace explains the spelling: forward navigation asks the type
14284/// namespace first and reads the leaf as a value only when no type explains it,
14285/// and the inverse field scan admits the leaf only when no visible type does
14286/// (#2556).
14287pub fn is_type_shaped_template_argument_name(node: Node<'_>) -> bool {
14288    if node.kind() != "type_identifier" {
14289        return false;
14290    }
14291    let Some(descriptor) = node
14292        .parent()
14293        .filter(|parent| parent.kind() == "type_descriptor")
14294    else {
14295        return false;
14296    };
14297    if descriptor.child_by_field_name("type") != Some(node) {
14298        return false;
14299    }
14300    let Some(arguments) = descriptor
14301        .parent()
14302        .filter(|parent| parent.kind() == "template_argument_list")
14303    else {
14304        return false;
14305    };
14306    arguments.parent().is_some_and(|owner| {
14307        matches!(
14308            owner.kind(),
14309            "template_type" | "template_function" | "template_method"
14310        ) && owner.child_by_field_name("arguments") == Some(arguments)
14311    })
14312}
14313
14314/// Whether a reference written in `file` reads C++ source with C semantics.
14315///
14316/// [`is_c_source_file`] answers the half a path settles on its own. The other
14317/// half is a header, which has no dialect of its own: it is read as C exactly
14318/// when every workspace translation unit that provably compiles it compiles it
14319/// as C ([`CppSource::header_uses_c_semantics`], issue #1970).
14320///
14321/// This is the gate for anything that is really about the compilation
14322/// language of the code being read -- which reading of an included header's
14323/// declarations is in scope, whether `this` is an ordinary identifier. It is
14324/// NOT the gate for a question that is genuinely about a `.c` file on disk;
14325/// those keep calling [`is_c_source_file`].
14326pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
14327    is_c_source_file(file) || cpp.header_uses_c_semantics(file)
14328}
14329
14330pub fn is_declarator_node(node: Node<'_>) -> bool {
14331    matches!(
14332        node.kind(),
14333        "identifier"
14334            | "field_identifier"
14335            | "qualified_identifier"
14336            | "scoped_identifier"
14337            | "pointer_declarator"
14338            | "reference_declarator"
14339            | "array_declarator"
14340            | "parenthesized_declarator"
14341            | "function_declarator"
14342    )
14343}
14344
14345/// Returns the declarator represented by one direct child of a declaration.
14346///
14347/// Tree-sitter exposes the first declarator through the `declarator` field,
14348/// but subsequent comma-separated declarators are unfielded direct children:
14349/// `int first, second;` therefore has an `identifier` child for `first` and a
14350/// second, otherwise identical, `identifier` child for `second`. Consumers
14351/// must inspect both shapes or a later declarator can be mistaken for a use of
14352/// an unrelated indexed symbol.
14353pub fn declaration_declarator<'tree>(
14354    declaration: Node<'tree>,
14355    child: Node<'tree>,
14356) -> Option<Node<'tree>> {
14357    if !matches!(
14358        declaration.kind(),
14359        "declaration"
14360            | "field_declaration"
14361            | "parameter_declaration"
14362            | "optional_parameter_declaration"
14363            | "function_definition"
14364            | "type_definition"
14365            | "alias_declaration"
14366            | "template_instantiation"
14367    ) {
14368        return None;
14369    }
14370    if declaration
14371        .child_by_field_name("type")
14372        .is_some_and(|type_node| same_node(type_node, child))
14373    {
14374        return None;
14375    }
14376    if child.kind() == "init_declarator" {
14377        return child.child_by_field_name("declarator");
14378    }
14379    let field = field_name_in_parent(declaration, child);
14380    if (is_declarator_node(child) && matches!(field, Some("declarator") | None))
14381        || (declaration.kind() == "type_definition"
14382            && child.kind() == "type_identifier"
14383            && matches!(field, Some("declarator") | None))
14384    {
14385        Some(child)
14386    } else {
14387        None
14388    }
14389}
14390
14391/// Read the name, declarator, and optional type of a typed local binding.
14392pub fn cpp_binding_parts<'tree>(
14393    node: Node<'tree>,
14394    source: &str,
14395) -> Option<(String, Node<'tree>, Option<Node<'tree>>)> {
14396    let declarator = node.child_by_field_name("declarator")?;
14397    let name = extract_variable_name(declarator, source)?;
14398    let type_node = node
14399        .child_by_field_name("type")
14400        .or_else(|| first_type_child(node));
14401    Some((name, declarator, type_node))
14402}
14403
14404/// One run of a container's children whose parsed namespaces differ from
14405/// their lexical namespaces, with the complete lexical namespace path.
14406#[derive(Clone, Debug, PartialEq, Eq)]
14407pub struct RecoveredNamespaceRegion {
14408    /// Start byte of the first child in the run.
14409    pub start: usize,
14410    /// End byte of the last child in the run.
14411    pub end: usize,
14412    /// The complete enclosing namespace path of the run, outermost first.
14413    /// This can be empty when a parsed namespace extends past its real close.
14414    pub components: Vec<String>,
14415    /// The `namespace Name { ... }` construct that opens each of `components`,
14416    /// parallel to it, from the `namespace` keyword through the close the index
14417    /// proved for its `{`. A proven surviving namespace definition uses its
14418    /// body's direct close; a recovered head, or a namespace body exposed by
14419    /// malformed member recovery, uses the file-global brace stack. A level
14420    /// whose close the index could not prove -- the file never closes it, or
14421    /// the file's brace token stream lost an opening token (#3328) -- carries
14422    /// the head alone. This is the range the declaration walk records for the
14423    /// Module, so a namespace whose head collapsed into an `ERROR` reports
14424    /// where it is written rather than where its first member is (#3309).
14425    pub component_ranges: Vec<Range>,
14426    /// The name token each of `components` is written as, parallel to it. The
14427    /// declaration walk interns this subspan as the Module's name occurrence:
14428    /// the node the walk is visiting when it declares the level belongs to an
14429    /// unrelated construct inside the region, and deriving the name from it
14430    /// paired the level's range with that construct's name token (#4292).
14431    pub component_name_ranges: Vec<Range>,
14432    /// The close the index proved for each level's `{`, parallel to
14433    /// `components`. A node after that close is outside the level (#3633).
14434    /// `None` when the file never closes the level or the walk proved no
14435    /// pairing in the file (#3328); the level then applies to the whole region
14436    /// as a best-effort scope.
14437    pub component_closes: Vec<Option<Range>>,
14438}
14439
14440/// The namespaces C++ parse recovery drops from a file's tree.
14441///
14442/// When tree-sitter cannot parse a construct inside a namespace body it closes
14443/// an inner scope with a MISSING brace, or skips an opening brace into an
14444/// ERROR node. Every real `}` after that then closes one scope too early: a
14445/// class body's `}` closes the namespace, the namespace's own `}` closes its
14446/// parent, and the outermost real closes land in a trailing ERROR node. The
14447/// declarations between a stolen close and the real one keep their byte
14448/// positions but lose their `namespace_definition` ancestors (Catch2's
14449/// `catch_matchers_templated.hpp`, issue #1537).
14450///
14451/// A file-global stack over real AST brace tokens restores both lost and
14452/// overextended namespaces. Missing braces do nothing; error-free subtrees are
14453/// balanced and can be skipped. For each child of a damaged container, compare
14454/// the namespaces still open on that stack with the parsed ancestor path. A
14455/// difference yields a region carrying the complete lexical path, including
14456/// an empty path when recovery swallowed file-scope declarations. Consecutive
14457/// children sharing that path form one [`RecoveredNamespaceRegion`]. The same
14458/// pass retains matching brace positions for declaration partitioning (#3087).
14459/// A file without parse errors needs neither correction nor a brace index.
14460///
14461/// The stack restores the source's nesting only while the tree's brace tokens
14462/// are the source's braces. Severe recovery can break that even when the real
14463/// brace counts balance: in simdjson's `compile_time_json-inl.h`, ABI 15 keeps
14464/// the reflection case labels as `character` nodes but loses the outer
14465/// namespace's closing token. The global stack then cascades the function and
14466/// inner namespace closes into the wrong owners. A surviving
14467/// `namespace_definition` can supply stronger provenance when its body has a
14468/// genuine declaration boundary: its direct real `}` belongs to that
14469/// namespace, while a missing direct close proves that the namespace has no
14470/// close to claim. A body whose direct boundary is itself recovered from a
14471/// class/member error stays with the global fallback. The proven boundaries
14472/// override the global stack only when the file has no unpaired real close. An
14473/// unpaired close proves the stream lost an open at a position the stack cannot
14474/// recover, so all pairing in that file remains invalid:
14475/// [`Self::matching_close_brace`] then answers nothing and each restored level
14476/// keeps the head that writes it. The namespace paths remain, as the
14477/// best-effort structure recovery they are.
14478///
14479/// Every consumer reads a node's namespace path through one authority rule,
14480/// shared by [`Self::enclosing_namespace_levels`] and
14481/// [`Self::restore_enclosing_namespaces`], so declaration collection and both
14482/// lookup directions agree:
14483///
14484/// - Outside every region, the node's parsed `namespace_definition` chain is
14485///   the whole answer.
14486/// - Inside a region, the region's restored levels come first and only the
14487///   parsed ancestors that start inside the region follow them. The region
14488///   overrides a parsed ancestor that starts before it, which is how a
14489///   namespace the tree kept open past its real close gives up the
14490///   declarations after that close (#3087).
14491/// - A restored level stops applying at the close the stack paired with its
14492///   `{`, and so does every level inside it. The walk compares a child with
14493///   the stack before it descends into that child, so a run can reach past a
14494///   close inside its last child. In simdjson's `tests/dom/basictests.cpp` the
14495///   collapsed `namespace type_tests {` on line 1467 closes on line 1811, and
14496///   recovery put that `}` inside a `preproc_else` that runs to line 2505: the
14497///   region that restores `type_tests` covers the whole `preproc_else`, and
14498///   without this check every namespace written after the close inherited
14499///   `type_tests` (#3633). A level whose close the stack did not prove applies
14500///   to the whole region, the best-effort answer #3328 leaves.
14501/// - A region whose restored path is empty keeps exactly the parsed ancestors
14502///   whose own `{` and `}` are both real tokens, wherever they start, and drops
14503///   the others. This holds whether or not the file's pairing is proven.
14504#[derive(Clone, Debug, Default)]
14505pub struct OrphanedNamespaceScopeIndex {
14506    regions: Vec<RecoveredNamespaceRegion>,
14507    /// Open-brace start byte to the close the file-global stack paired with it.
14508    /// Empty when the walk proved the brace token stream incomplete.
14509    brace_closes: HashMap<usize, Range>,
14510}
14511
14512impl OrphanedNamespaceScopeIndex {
14513    pub fn build(root: Node<'_>, source: &str) -> Self {
14514        if !root.has_error() {
14515            return Self::default();
14516        }
14517        /// One namespace level open on the brace stack: the `namespace Name {`
14518        /// head it is written as, and the `{`'s start byte, which is the
14519        /// `brace_closes` key its close lands under. The close is not known
14520        /// while the level is open, so the final ranges are assembled once the
14521        /// walk has paired every brace.
14522        #[derive(Clone, Copy)]
14523        struct OpenHead {
14524            head: Range,
14525            open: usize,
14526        }
14527        /// A [`RecoveredNamespaceRegion`] whose levels still name their open
14528        /// braces rather than their closes.
14529        struct PendingRegion {
14530            start: usize,
14531            end: usize,
14532            components: Vec<String>,
14533            heads: Vec<OpenHead>,
14534            name_ranges: Vec<Range>,
14535        }
14536        struct Frame<'tree> {
14537            node: Node<'tree>,
14538            children: Vec<Node<'tree>>,
14539            next: usize,
14540            parsed_scope: Vec<String>,
14541            run: Option<PendingRegion>,
14542        }
14543        fn frame<'tree>(
14544            node: Node<'tree>,
14545            mut parsed_scope: Vec<String>,
14546            source: &str,
14547        ) -> Frame<'tree> {
14548            if node.kind() == "namespace_definition"
14549                && let Some(name) = node.child_by_field_name("name")
14550            {
14551                let mut components = Vec::new();
14552                if append_cpp_name_components(name, source, &mut components).is_some() {
14553                    parsed_scope.extend(components);
14554                }
14555            }
14556            let mut cursor = node.walk();
14557            Frame {
14558                node,
14559                children: node.children(&mut cursor).collect(),
14560                next: 0,
14561                parsed_scope,
14562                run: None,
14563            }
14564        }
14565        let mut regions = Vec::new();
14566        let mut brace_closes = HashMap::default();
14567        // A surviving namespace_definition may own a direct brace boundary of
14568        // its declaration_list. Keep that evidence separate from the file-
14569        // global stack: recovery can lose an outer namespace close while
14570        // retaining balanced braces from nested constructs, so token order
14571        // alone can assign a real close to the wrong namespace. The helper
14572        // rejects body boundaries exposed by malformed class/member recovery.
14573        let mut namespace_brace_proofs = Vec::new();
14574        // Real closes that arrived with nothing open. Each one proves the tree
14575        // is missing a scope-opening token the source writes, which is what
14576        // makes every pairing in the file unusable as an extent (#3328). The
14577        // complete set is kept, not a count, so a walk that has to be diagnosed
14578        // shows every position the stream went out of sync.
14579        let mut unpaired_closes: Vec<Range> = Vec::new();
14580        // The brace stack belongs to the file, not to a parser frame. A real
14581        // close inside a damaged child can close its parent's namespace. The
14582        // following children must see that removal immediately (#3087).
14583        let mut open = Vec::new();
14584        let mut lexical_scope = Vec::new();
14585        // One head per entry of `lexical_scope`: `namespace a::b {` opens two
14586        // levels and both are written by that one head, exactly as the
14587        // ordinary namespace visit records one range for every level of the
14588        // C++17 shorthand (#1878).
14589        let mut lexical_heads: Vec<OpenHead> = Vec::new();
14590        // One name token per entry of `lexical_scope`: unlike the shared head,
14591        // each level of a `namespace a::b {` shorthand has its own token.
14592        let mut lexical_name_ranges: Vec<Range> = Vec::new();
14593        let mut frames = vec![frame(root, Vec::new(), source)];
14594        // A frame's node is the previous frame's direct child, so the frame
14595        // below answers what Node::parent would without re-descending from the
14596        // root; per-node parent or sibling climbs are quadratic over a wide
14597        // translation unit (#3141).
14598        while !frames.is_empty() {
14599            let parent_node = frames.len().checked_sub(2).map(|index| frames[index].node);
14600            let current = frames.last_mut().expect("frames is non-empty");
14601            if current.next == current.children.len() {
14602                regions.extend(frames.pop().expect("the frame just borrowed").run);
14603                continue;
14604            }
14605            let child = current.children[current.next];
14606            current.next += 1;
14607            match child.kind() {
14608                "{" if !child.is_missing() => {
14609                    regions.extend(current.run.take());
14610                    if let Some(namespace) = parent_node.filter(|parent| {
14611                        parent.kind() == "namespace_definition"
14612                            && parent.child_by_field_name("body") == Some(current.node)
14613                            && child.start_byte() == current.node.start_byte()
14614                    }) && let Some(proof) = namespace_body_brace_proof(current.node)
14615                    {
14616                        namespace_brace_proofs.push((child.start_byte(), namespace, proof));
14617                    }
14618                    // The head is the construct that writes the namespace: the
14619                    // `namespace_definition` node when one survived, and the
14620                    // bare `namespace` keyword when recovery collapsed the head
14621                    // into flat `ERROR` children (#3084).
14622                    let mut named = parent_node.and_then(|parent| {
14623                        namespace_body_name_components(parent, current.node, source)
14624                    });
14625                    if named.is_none() {
14626                        named = recovered_namespace_open_components(
14627                            &current.children[..current.next - 1],
14628                            source,
14629                        );
14630                    }
14631                    open.push((child.start_byte(), lexical_scope.len()));
14632                    if let Some((written_at, components)) = named {
14633                        let head = OpenHead {
14634                            head: Range {
14635                                start_byte: written_at.start_byte(),
14636                                end_byte: child.end_byte(),
14637                                start_line: written_at.start_position().row + 1,
14638                                end_line: child.end_position().row + 1,
14639                            },
14640                            open: child.start_byte(),
14641                        };
14642                        lexical_heads.extend(std::iter::repeat_n(head, components.len()));
14643                        lexical_name_ranges
14644                            .extend(components.iter().map(|(_, name_range)| *name_range));
14645                        lexical_scope
14646                            .extend(components.into_iter().map(|(component, _)| component));
14647                    }
14648                    continue;
14649                }
14650                "}" if !child.is_missing() => {
14651                    regions.extend(current.run.take());
14652                    let close = Range {
14653                        start_byte: child.start_byte(),
14654                        end_byte: child.end_byte(),
14655                        start_line: child.start_position().row + 1,
14656                        end_line: child.end_position().row + 1,
14657                    };
14658                    match open.pop() {
14659                        Some((start, namespace_len)) => {
14660                            debug_assert!(
14661                                start < close.start_byte,
14662                                "a close pairs with an open before it: {start} then {close:?}"
14663                            );
14664                            lexical_scope.truncate(namespace_len);
14665                            lexical_heads.truncate(namespace_len);
14666                            lexical_name_ranges.truncate(namespace_len);
14667                            brace_closes.insert(start, close);
14668                        }
14669                        None => unpaired_closes.push(close),
14670                    }
14671                    continue;
14672                }
14673                _ => {}
14674            }
14675            // The namespace head and body precede their opening brace; their
14676            // own parsed namespace is not yet on the lexical stack. Compare
14677            // the items inside the body, not this header bookkeeping.
14678            if current.node.kind() != "namespace_definition"
14679                && lexical_scope != current.parsed_scope
14680            {
14681                match &mut current.run {
14682                    Some(run) if run.components == lexical_scope => run.end = child.end_byte(),
14683                    run => {
14684                        regions.extend(run.take());
14685                        *run = Some(PendingRegion {
14686                            start: child.start_byte(),
14687                            end: child.end_byte(),
14688                            components: lexical_scope.clone(),
14689                            heads: lexical_heads.clone(),
14690                            name_ranges: lexical_name_ranges.clone(),
14691                        });
14692                    }
14693                }
14694            } else {
14695                regions.extend(current.run.take());
14696            }
14697            // Error-free subtrees are balanced. Keeping them intact also
14698            // leaves their valid nested namespaces to the ordinary ancestry
14699            // walk in restore_enclosing_namespaces.
14700            if child.has_error() {
14701                let parsed_scope = current.parsed_scope.clone();
14702                frames.push(frame(child, parsed_scope, source));
14703            }
14704        }
14705        // A close with nothing open is only possible when a `{` the source
14706        // writes is not a `{` token of the tree. The stack then paired some
14707        // earlier open with a close that belongs to an inner scope, and
14708        // nothing in the token stream says which pairs absorbed the loss: the
14709        // lost token sits at an unknown position before the close that
14710        // exposed it. Keep no pairing rather than an unprovable extent. An
14711        // open the file never closes is the opposite case and stays: it makes
14712        // no claim, so the levels it writes keep their heads either way.
14713        if !unpaired_closes.is_empty() {
14714            brace_closes.clear();
14715        } else {
14716            // Use AST ownership when the global walk has no unpaired close. A missing
14717            // direct close removes a misleading stack pairing; a real direct
14718            // close replaces any cascade from a damaged descendant. Apply this
14719            // only to the namespace chain containing a proven missing boundary;
14720            // unrelated or ambiguous namespaces retain the global fallback.
14721            let missing_namespaces = namespace_brace_proofs
14722                .iter()
14723                .filter_map(|(_, namespace, proof)| {
14724                    matches!(proof, NamespaceBodyBraceProof::Missing).then_some(namespace.id())
14725                })
14726                .collect::<HashSet<_>>();
14727            for (open, namespace, proof) in namespace_brace_proofs {
14728                if !std::iter::successors(Some(namespace), |node| node.parent())
14729                    .any(|ancestor| missing_namespaces.contains(&ancestor.id()))
14730                {
14731                    continue;
14732                }
14733                match proof {
14734                    NamespaceBodyBraceProof::Real(close) => {
14735                        brace_closes.insert(open, close);
14736                    }
14737                    NamespaceBodyBraceProof::Missing => {
14738                        brace_closes.remove(&open);
14739                    }
14740                }
14741            }
14742        }
14743        // Every surviving brace is paired now, so each level's head can grow to
14744        // the construct it writes. A level whose `{` never closes keeps its
14745        // head: the file ends inside the namespace and there is no real close
14746        // to reach for.
14747        let regions = regions
14748            .into_iter()
14749            .map(|region| {
14750                debug_assert_eq!(
14751                    region.components.len(),
14752                    region.heads.len(),
14753                    "every recovered namespace level carries the head that writes it: {:?}",
14754                    region.components
14755                );
14756                debug_assert_eq!(
14757                    region.components.len(),
14758                    region.name_ranges.len(),
14759                    "every recovered namespace level carries the name token that writes it: {:?}",
14760                    region.components
14761                );
14762                let component_closes: Vec<_> = region
14763                    .heads
14764                    .iter()
14765                    .map(|level| brace_closes.get(&level.open).copied())
14766                    .collect();
14767                let component_ranges = region
14768                    .heads
14769                    .iter()
14770                    .zip(&component_closes)
14771                    .map(|(level, close)| match close {
14772                        Some(close) => Range {
14773                            start_byte: level.head.start_byte,
14774                            end_byte: close.end_byte,
14775                            start_line: level.head.start_line,
14776                            end_line: close.end_line,
14777                        },
14778                        None => level.head,
14779                    })
14780                    .collect();
14781                RecoveredNamespaceRegion {
14782                    start: region.start,
14783                    end: region.end,
14784                    components: region.components,
14785                    component_ranges,
14786                    component_name_ranges: region.name_ranges,
14787                    component_closes,
14788                }
14789            })
14790            .collect();
14791        Self {
14792            regions,
14793            brace_closes,
14794        }
14795    }
14796
14797    /// The real AST brace matching an opening brace in a damaged subtree.
14798    /// Missing tokens and braces inside comments or literals do not participate.
14799    ///
14800    /// `None` when this open has no close in the file, and for every open in a
14801    /// file whose brace token stream lost an opening token: a caller cannot
14802    /// treat a pairing the walk could not prove as an enclosing extent (#3328).
14803    pub fn matching_close_brace(&self, open: usize) -> Option<Range> {
14804        self.brace_closes.get(&open).copied()
14805    }
14806
14807    pub fn is_empty(&self) -> bool {
14808        self.regions.is_empty()
14809    }
14810
14811    /// The bytes this index holds, for the analyzer cache's weight.
14812    pub fn approximate_size(&self) -> usize {
14813        self.regions.iter().fold(
14814            self.brace_closes.len() * std::mem::size_of::<(usize, Range)>(),
14815            |total, region| {
14816                total
14817                    .saturating_add(std::mem::size_of::<RecoveredNamespaceRegion>())
14818                    .saturating_add(region.components.iter().map(String::len).sum::<usize>())
14819                    .saturating_add(region.component_ranges.len() * std::mem::size_of::<Range>())
14820                    .saturating_add(
14821                        region.component_name_ranges.len() * std::mem::size_of::<Range>(),
14822                    )
14823                    .saturating_add(
14824                        region.component_closes.len() * std::mem::size_of::<Option<Range>>(),
14825                    )
14826            },
14827        )
14828    }
14829
14830    /// The innermost recovered region containing `byte`.
14831    pub fn region_at(&self, byte: usize) -> Option<&RecoveredNamespaceRegion> {
14832        self.regions
14833            .iter()
14834            .filter(|region| region.start <= byte && byte < region.end)
14835            .min_by_key(|region| region.end - region.start)
14836    }
14837
14838    /// The enclosing namespaces of `node`, outermost first, restoring the ones
14839    /// parse recovery dropped from its ancestor chain. The one answer both
14840    /// lookup directions and declaration collection use (issue #1537).
14841    pub fn enclosing_namespace_components(&self, node: Node<'_>, source: &str) -> Vec<String> {
14842        self.enclosing_namespace_levels(node, source)
14843            .into_iter()
14844            .map(|(component, ..)| component)
14845            .collect()
14846    }
14847
14848    /// [`Self::enclosing_namespace_components`] with the construct that writes
14849    /// each level and the level's own name token, for the declaration walk
14850    /// that has to record both ranges.
14851    ///
14852    /// A level the brace stack restored carries the ranges this index recorded
14853    /// for its head and name; a surviving `namespace_definition` ancestor
14854    /// carries its own node range and name token, which is what the ordinary
14855    /// namespace visit records.
14856    pub fn enclosing_namespace_levels(
14857        &self,
14858        node: Node<'_>,
14859        source: &str,
14860    ) -> Vec<(String, Range, Range)> {
14861        let mut parsed = Vec::new();
14862        let mut current = node.parent();
14863        while let Some(parent) = current {
14864            if parent.kind() == "namespace_definition"
14865                && let Some(name) = parent.child_by_field_name("name")
14866                && let Some(components) = cpp_name_component_ranges(name, source)
14867            {
14868                let range = node_range(parent);
14869                parsed.push((
14870                    parent.start_byte(),
14871                    components
14872                        .into_iter()
14873                        .map(|(component, name_range)| (component, range, name_range))
14874                        .collect(),
14875                    namespace_definition_has_real_braces(parent),
14876                ));
14877            }
14878            current = parent.parent();
14879        }
14880        parsed.reverse();
14881        self.splice_recovered_path(parsed, node.start_byte(), |region, active_components| {
14882            region
14883                .components
14884                .iter()
14885                .take(active_components)
14886                .cloned()
14887                .zip(
14888                    region
14889                        .component_ranges
14890                        .iter()
14891                        .take(active_components)
14892                        .copied(),
14893                )
14894                .zip(
14895                    region
14896                        .component_name_ranges
14897                        .iter()
14898                        .take(active_components)
14899                        .copied(),
14900                )
14901                .map(|((component, range), name_range)| (component, range, name_range))
14902                .collect()
14903        })
14904    }
14905
14906    /// [`Self::enclosing_namespace_components`] for a caller that has already
14907    /// climbed the ancestor chain: `parsed` lists the node's named
14908    /// `namespace_definition` ancestors outermost first, each with its start
14909    /// byte and whether its own opening and closing braces survived parsing.
14910    /// The answer follows the authority rule on [`OrphanedNamespaceScopeIndex`].
14911    pub fn restore_enclosing_namespaces(
14912        &self,
14913        parsed: Vec<(usize, Vec<String>, bool)>,
14914        node_start: usize,
14915    ) -> Vec<String> {
14916        self.splice_recovered_path(parsed, node_start, |region, active_components| {
14917            region
14918                .components
14919                .iter()
14920                .take(active_components)
14921                .cloned()
14922                .collect()
14923        })
14924    }
14925
14926    /// Splice a node's parsed namespace ancestors onto the path a recovered
14927    /// region restores, by the authority rule on [`OrphanedNamespaceScopeIndex`].
14928    /// `recovered` receives the region and the number of its outermost levels
14929    /// still open at the node, and reads those levels in whatever shape the
14930    /// caller needs.
14931    fn splice_recovered_path<T>(
14932        &self,
14933        parsed: Vec<(usize, Vec<T>, bool)>,
14934        node_start: usize,
14935        recovered: impl FnOnce(&RecoveredNamespaceRegion, usize) -> Vec<T>,
14936    ) -> Vec<T> {
14937        let Some(region) = self.region_at(node_start) else {
14938            return parsed
14939                .into_iter()
14940                .flat_map(|(_, levels, _)| levels)
14941                .collect();
14942        };
14943        debug_assert_eq!(
14944            region.components.len(),
14945            region.component_closes.len(),
14946            "every recovered namespace component carries its close proof: {region:?}"
14947        );
14948        debug_assert_eq!(
14949            region.components.len(),
14950            region.component_name_ranges.len(),
14951            "every recovered namespace component carries its name token: {region:?}"
14952        );
14953        if region.components.is_empty() {
14954            return parsed
14955                .into_iter()
14956                .filter(|(_, _, complete)| *complete)
14957                .flat_map(|(_, levels, _)| levels)
14958                .collect();
14959        }
14960        let active_components = region
14961            .component_closes
14962            .iter()
14963            .take_while(|close| {
14964                close
14965                    .as_ref()
14966                    .is_none_or(|close| node_start < close.end_byte)
14967            })
14968            .count();
14969        recovered(region, active_components)
14970            .into_iter()
14971            .chain(
14972                parsed
14973                    .into_iter()
14974                    .filter(|(start, _, _)| *start >= region.start)
14975                    .flat_map(|(_, levels, _)| levels),
14976            )
14977            .collect()
14978    }
14979}
14980
14981/// Whether a parsed namespace owns real AST tokens for both of its boundary
14982/// braces. Recovery may damage its body while leaving this boundary usable as
14983/// lexical ownership evidence.
14984pub(super) fn namespace_definition_has_real_braces(namespace: Node<'_>) -> bool {
14985    debug_assert_eq!(namespace.kind(), "namespace_definition");
14986    let Some(body) = namespace.child_by_field_name("body") else {
14987        return false;
14988    };
14989    let mut opening = false;
14990    let mut closing = false;
14991    let mut cursor = body.walk();
14992    for child in body.children(&mut cursor) {
14993        match child.kind() {
14994            "{" if !child.is_missing() => opening = true,
14995            "}" if !child.is_missing() => closing = true,
14996            _ => {}
14997        }
14998    }
14999    opening && closing
15000}
15001
15002/// The `namespace` keyword and name components of the namespace a stray `{`
15003/// opens, or `None` when the brace does not follow a namespace head.
15004///
15005/// When a namespace body holds a construct tree-sitter cannot parse, recovery
15006/// can collapse the whole `namespace Name { ... }` into one `ERROR` instead of
15007/// a `namespace_definition`: the head survives as the node's own `namespace`
15008/// keyword, name and `{` tokens, in that order, and everything the namespace
15009/// declares becomes a flat sibling of them (Catch2's `catch_decomposer.hpp`,
15010/// issue #3084). Read the head from those siblings so the brace stack names
15011/// the scope the brace opens. An anonymous namespace has no representable
15012/// name and keeps an opaque scope.
15013///
15014/// `preceding` is the open brace's preceding siblings, nearest last. The
15015/// caller already holds the container's child list; `Node::prev_sibling`
15016/// would re-descend from the root for each step (#3141).
15017fn recovered_namespace_open_components<'tree>(
15018    preceding: &[Node<'tree>],
15019    source: &str,
15020) -> Option<(Node<'tree>, Vec<(String, Range)>)> {
15021    let mut head = Vec::new();
15022    for &sibling in preceding.iter().rev() {
15023        if sibling.kind() != "comment" {
15024            head.push(sibling);
15025            if head.len() == 2 {
15026                break;
15027            }
15028        }
15029    }
15030    let [name, keyword] = head[..] else {
15031        return None;
15032    };
15033    if keyword.kind() != "namespace" {
15034        return None;
15035    }
15036    let components = cpp_name_component_ranges(name, source)?;
15037    (!components.is_empty()).then_some((keyword, components))
15038}
15039
15040/// The written head and name-token ranges of the namespace opened by `body`.
15041/// Ordinary namespace bodies and one strict damaged literal head share the
15042/// existing brace-stack lifetime; a recovered compound body's end is NOT a
15043/// namespace close proof (#5868).
15044fn namespace_body_name_components<'tree>(
15045    parent: Node<'tree>,
15046    body: Node<'tree>,
15047    source: &str,
15048) -> Option<(Node<'tree>, Vec<(String, Range)>)> {
15049    if parent.child_by_field_name("body") != Some(body) {
15050        return None;
15051    }
15052    let (written_at, name) = match (parent.kind(), body.kind()) {
15053        ("namespace_definition", "declaration_list") => {
15054            (parent, parent.child_by_field_name("name")?)
15055        }
15056        ("function_definition", "compound_statement") if parent.has_error() => {
15057            // An opaque prefix can make the parser read the literal namespace
15058            // keyword as a function declarator. Account for every direct token:
15059            // no parameters, attributes, extra names or punctuation may compete.
15060            let mut cursor = parent.walk();
15061            let children = parent
15062                .children(&mut cursor)
15063                .filter(|child| child.kind() != "comment")
15064                .collect::<Vec<_>>();
15065            let [prefix, keyword, error, actual_body] = children.as_slice() else {
15066                return None;
15067            };
15068            if *actual_body != body
15069                || parent.child_by_field_name("type") != Some(*prefix)
15070                || parent.child_by_field_name("declarator") != Some(*keyword)
15071                || prefix.kind() != "type_identifier"
15072                || prefix.is_missing()
15073                || prefix.has_error()
15074                || prefix.child_count() != 0
15075                || keyword.kind() != "identifier"
15076                || keyword.is_missing()
15077                || keyword.has_error()
15078                || keyword.child_count() != 0
15079                || node_text(*keyword, source) != "namespace"
15080                || error.kind() != "ERROR"
15081                || error.is_missing()
15082                || body.is_missing()
15083            {
15084                return None;
15085            }
15086            let mut cursor = error.walk();
15087            let names = error
15088                .children(&mut cursor)
15089                .filter(|child| child.kind() != "comment")
15090                .collect::<Vec<_>>();
15091            let [name] = names.as_slice() else {
15092                return None;
15093            };
15094            if !matches!(name.kind(), "identifier" | "namespace_identifier")
15095                || name.is_missing()
15096                || name.has_error()
15097                || name.child_count() != 0
15098            {
15099                return None;
15100            }
15101            let open = body.child(0)?;
15102            if open.kind() != "{"
15103                || open.is_missing()
15104                || open.has_error()
15105                || open.start_byte() != body.start_byte()
15106            {
15107                return None;
15108            }
15109            // Keep the literal keyword's provenance, never the unrelated prefix
15110            // or the prematurely closed function envelope (#3309, #4292).
15111            (*keyword, *name)
15112        }
15113        _ => return None,
15114    };
15115    let components = cpp_name_component_ranges(name, source)?;
15116    (!components.is_empty()).then_some((written_at, components))
15117}
15118
15119#[derive(Clone, Copy)]
15120enum NamespaceBodyBraceProof {
15121    Real(Range),
15122    Missing,
15123}
15124
15125/// Prove a namespace body's boundary from its direct AST children. Require the
15126/// body's boundary as well as direct ownership, so a stray brace inside a
15127/// recovered body cannot become namespace ownership evidence. Unknown is
15128/// distinct from a proven missing token: unknown leaves the global pairing in
15129/// place, while a direct MISSING `}` removes a misleading pairing.
15130fn namespace_body_brace_proof(body: Node<'_>) -> Option<NamespaceBodyBraceProof> {
15131    if body.kind() != "declaration_list" {
15132        return None;
15133    }
15134    let mut cursor = body.walk();
15135    let children = body.children(&mut cursor).collect::<Vec<_>>();
15136    let close_index = children
15137        .iter()
15138        .rposition(|child| child.kind() == "}" && child.end_byte() == body.end_byte())?;
15139    let close = children[close_index];
15140    if close.is_missing() {
15141        return Some(NamespaceBodyBraceProof::Missing);
15142    }
15143    let namespace_terminator_sibling = body
15144        .parent()
15145        .filter(|namespace| namespace.kind() == "namespace_definition")
15146        .and_then(|namespace| namespace.next_sibling())
15147        .is_some_and(|sibling| {
15148            sibling.kind() == ";"
15149                || (sibling.kind() == "expression_statement"
15150                    && sibling.child_count() == 1
15151                    && sibling.child(0).is_some_and(|child| child.kind() == ";"))
15152        });
15153    // A recovered class close can become the namespace body boundary while
15154    // its class terminator remains as a sibling semicolon. A malformed class
15155    // can also leak its final expression or label directly into the namespace
15156    // body. Neither shape proves that this `}` belongs to the namespace.
15157    if namespace_terminator_sibling
15158        || children
15159            .get(close_index + 1)
15160            .is_some_and(|sibling| sibling.kind() == ";")
15161        || children[..close_index]
15162            .iter()
15163            .rev()
15164            .find(|child| !child.is_extra() && child.kind() != "{")
15165            .is_some_and(|previous| {
15166                matches!(
15167                    previous.kind(),
15168                    "expression_statement" | "labeled_statement"
15169                )
15170            })
15171    {
15172        return None;
15173    }
15174    Some(NamespaceBodyBraceProof::Real(node_range(close)))
15175}
15176
15177#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15178pub enum RecoveredDeclaratorTypeContext {
15179    Declaration,
15180    FunctionDefinition,
15181    Parameter,
15182}
15183
15184/// Recognize a real type displaced into a qualified declarator by parser
15185/// recovery.
15186///
15187/// Tree-sitter parses `API Result *make(Arg);` as if `API` were the declared
15188/// type and `Result` were the scope of a qualified declarator with a missing
15189/// `::`. A template return such as `API Result<T> make()` uses a
15190/// `template_type` for the same recovered scope. The same recovery occurs for
15191/// macro-prefixed definitions, extern variables, and macro-decorated
15192/// parameters (`f(MACRO T* p)`, where the parameter's own `type` field takes
15193/// the macro). Keep this intentionally structural: the recovered scope must
15194/// have the grammar's missing separator, the qualified node must occupy the
15195/// declaration's declarator chain, a separate nonempty type must occupy the
15196/// normal type field, and the recovered name must unwrap to a real declarator
15197/// name.
15198pub fn recovered_macro_decorated_declarator_type(
15199    node: Node<'_>,
15200) -> Option<RecoveredDeclaratorTypeContext> {
15201    recovered_macro_decorated_type_node(node).map(|(_, context)| context)
15202}
15203
15204/// Return the declaration/function `type` displaced by a macro-shaped
15205/// qualified declarator, together with the enclosing declaration context.
15206/// Callers use the macro scope only as structural admission evidence; the
15207/// returned node is the real type reference to resolve and record.
15208pub fn recovered_macro_decorated_type_node(
15209    node: Node<'_>,
15210) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
15211    if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
15212        return None;
15213    }
15214    let qualified = node.parent()?;
15215    if qualified.kind() != "qualified_identifier"
15216        || qualified.child_by_field_name("scope") != Some(node)
15217        || !(0..qualified.child_count())
15218            .filter_map(|index| qualified.child(index))
15219            .any(|child| child.kind() == "::" && child.is_missing())
15220    {
15221        return None;
15222    }
15223    if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
15224        return None;
15225    }
15226
15227    let (declaration, context) = recovered_declarator_container(qualified)?;
15228    let type_node = declaration
15229        .child_by_field_name("type")
15230        .filter(|type_node| {
15231            *type_node != qualified
15232                && !type_node.is_missing()
15233                && type_node.start_byte() != type_node.end_byte()
15234        })?;
15235    Some((type_node, context))
15236}
15237
15238fn recovered_declarator_container(
15239    mut declarator: Node<'_>,
15240) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
15241    loop {
15242        let parent = declarator.parent()?;
15243        if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
15244            return Some((
15245                parent
15246                    .parent()
15247                    .filter(|declaration| declaration.kind() == "declaration")?,
15248                RecoveredDeclaratorTypeContext::Declaration,
15249            ));
15250        }
15251        if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
15252            return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
15253        }
15254        if parent.kind() == "function_definition"
15255            && has_field_child(parent, "declarator", declarator)
15256        {
15257            return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
15258        }
15259        // `f(MACRO T* p)` recovers exactly like `MACRO T *make(...)` does, one
15260        // level down: the parameter's `type` field takes the macro token and
15261        // the real type `T` becomes the recovered scope of the declarator.
15262        // Declining here left every xxhash `XXH_NOESCAPE` parameter with no
15263        // candidate at all (#1830).
15264        if matches!(
15265            parent.kind(),
15266            "parameter_declaration" | "optional_parameter_declaration"
15267        ) && has_field_child(parent, "declarator", declarator)
15268        {
15269            return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
15270        }
15271        if !matches!(
15272            parent.kind(),
15273            "array_declarator"
15274                | "function_declarator"
15275                | "parenthesized_declarator"
15276                | "pointer_declarator"
15277                | "pointer_type_declarator"
15278                | "reference_declarator"
15279        ) || !has_field_child(parent, "declarator", declarator)
15280        {
15281            return None;
15282        }
15283        declarator = parent;
15284    }
15285}
15286
15287fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
15288    brokk_bifrost_core::analyzer::structural::adapter_helpers::is_field_of(parent, target, field)
15289}
15290
15291fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
15292    loop {
15293        if node.is_missing() || node.start_byte() == node.end_byte() {
15294            return false;
15295        }
15296        match node.kind() {
15297            "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
15298                return true;
15299            }
15300            "array_declarator"
15301            | "function_declarator"
15302            | "parenthesized_declarator"
15303            | "pointer_declarator"
15304            | "pointer_type_declarator"
15305            | "reference_declarator" => {
15306                let Some(declarator) = node.child_by_field_name("declarator") else {
15307                    return false;
15308                };
15309                node = declarator;
15310            }
15311            _ => return false,
15312        }
15313    }
15314}
15315
15316/// Aggregate-owner proof for a structurally recognized designated initializer.
15317pub enum DesignatedInitializerOwner {
15318    Resolved(CodeUnit),
15319    Unresolved,
15320}
15321
15322enum InitializerOwnerStep {
15323    Field(String),
15324    AggregateWrapper,
15325}
15326
15327/// Recognize a designated-initializer field and, when possible, resolve its
15328/// aggregate owner.
15329///
15330/// Covers both the grammar's ordinary `field_designator` shape and the exact
15331/// recovery used for `.field = value` after a preprocessor-split array
15332/// initializer. Ordered multi-component designators follow each preceding
15333/// field's declared aggregate type. `None` means the node is not a designator
15334/// at all; an unresolved designator remains classified so callers cannot fall
15335/// through to unrelated global/member heuristics.
15336pub fn designated_initializer_owner(
15337    analyzer: &CppGraphSource<'_>,
15338    visibility: &VisibilityIndex<'_>,
15339    file: &ProjectFile,
15340    source: &str,
15341    node: Node<'_>,
15342) -> Option<DesignatedInitializerOwner> {
15343    if let Some(designator) = node
15344        .parent()
15345        .filter(|parent| parent.kind() == "field_designator")
15346    {
15347        let pair = designator.parent()?;
15348        if pair.kind() != "initializer_pair" {
15349            return None;
15350        }
15351        let mut cursor = pair.walk();
15352        let designators = pair
15353            .children_by_field_name("designator", &mut cursor)
15354            .collect::<Vec<_>>();
15355        let position = designators
15356            .iter()
15357            .position(|candidate| same_node(*candidate, designator))?;
15358        let initializer = pair.parent()?;
15359        if initializer.kind() != "initializer_list" {
15360            return None;
15361        }
15362        let mut owner = initializer_list_owner(analyzer, visibility, file, source, initializer);
15363        for prior in &designators[..position] {
15364            let field = prior
15365                .child_by_field_name("field")
15366                .or_else(|| first_named_child_of_kind(*prior, "field_identifier"))?;
15367            owner = owner.and_then(|owner| {
15368                initializer_field_owner(analyzer, visibility, file, owner, node_text(field, source))
15369            });
15370        }
15371        return Some(classified_designated_owner(owner));
15372    }
15373
15374    let init_declarator = node.parent()?;
15375    if init_declarator.child_by_field_name("declarator") != Some(node)
15376        || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
15377    {
15378        return None;
15379    }
15380    Some(classified_designated_owner(declaration_owner(
15381        analyzer,
15382        visibility,
15383        file,
15384        source,
15385        init_declarator.parent()?,
15386    )))
15387}
15388
15389fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
15390    owner.map_or(
15391        DesignatedInitializerOwner::Unresolved,
15392        DesignatedInitializerOwner::Resolved,
15393    )
15394}
15395
15396fn initializer_list_owner(
15397    analyzer: &CppGraphSource<'_>,
15398    visibility: &VisibilityIndex<'_>,
15399    file: &ProjectFile,
15400    source: &str,
15401    initializer: Node<'_>,
15402) -> Option<CodeUnit> {
15403    let mut current = initializer;
15404    let mut steps = Vec::new();
15405    loop {
15406        let parent = current.parent()?;
15407        match parent.kind() {
15408            "initializer_pair" if parent.child_by_field_name("value") == Some(current) => {
15409                let designator = parent.child_by_field_name("designator")?;
15410                let step = designator
15411                    .child_by_field_name("field")
15412                    .or_else(|| first_named_child_of_kind(designator, "field_identifier"))
15413                    .map(|field| InitializerOwnerStep::Field(node_text(field, source).to_string()))
15414                    .unwrap_or(InitializerOwnerStep::AggregateWrapper);
15415                steps.push(step);
15416                current = parent.parent()?;
15417            }
15418            "initializer_list" => {
15419                current = parent;
15420            }
15421            "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
15422                let declaration = parent.parent()?;
15423                let owner = declaration_owner(analyzer, visibility, file, source, declaration)?;
15424                return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
15425            }
15426            "compound_literal_expression"
15427                if parent.child_by_field_name("value") == Some(current) =>
15428            {
15429                let type_node = parent.child_by_field_name("type")?;
15430                let owner =
15431                    resolve_designated_owner_type(analyzer, visibility, file, source, type_node)?;
15432                return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
15433            }
15434            "ERROR" => current = parent,
15435            _ => return None,
15436        }
15437    }
15438}
15439
15440fn apply_initializer_owner_steps(
15441    analyzer: &CppGraphSource<'_>,
15442    visibility: &VisibilityIndex<'_>,
15443    file: &ProjectFile,
15444    mut owner: CodeUnit,
15445    steps: Vec<InitializerOwnerStep>,
15446) -> Option<CodeUnit> {
15447    for step in steps.into_iter().rev() {
15448        if let InitializerOwnerStep::Field(field_name) = step {
15449            owner = initializer_field_owner(analyzer, visibility, file, owner, &field_name)?;
15450        }
15451    }
15452    Some(owner)
15453}
15454
15455fn initializer_field_owner(
15456    analyzer: &CppGraphSource<'_>,
15457    visibility: &VisibilityIndex<'_>,
15458    file: &ProjectFile,
15459    owner: CodeUnit,
15460    field_name: &str,
15461) -> Option<CodeUnit> {
15462    let fields = visibility
15463        .visible_members_for_owner_name(file, &owner, field_name)
15464        .into_iter()
15465        .filter(|field| field.is_field())
15466        .collect::<Vec<_>>();
15467    let field = match fields.as_slice() {
15468        [field] => *field,
15469        _ => return None,
15470    };
15471    field_declared_binding(analyzer, visibility, file, field)?.unit
15472}
15473
15474fn declaration_owner(
15475    analyzer: &CppGraphSource<'_>,
15476    visibility: &VisibilityIndex<'_>,
15477    file: &ProjectFile,
15478    source: &str,
15479    declaration: Node<'_>,
15480) -> Option<CodeUnit> {
15481    if !matches!(declaration.kind(), "declaration" | "field_declaration") {
15482        return None;
15483    }
15484    let type_node = declaration
15485        .child_by_field_name("type")
15486        .or_else(|| first_type_child(declaration))?;
15487    resolve_designated_owner_type(analyzer, visibility, file, source, type_node)
15488}
15489
15490fn resolve_designated_owner_type(
15491    analyzer: &CppGraphSource<'_>,
15492    visibility: &VisibilityIndex<'_>,
15493    file: &ProjectFile,
15494    source: &str,
15495    type_node: Node<'_>,
15496) -> Option<CodeUnit> {
15497    if let Some(owner) = anonymous_aggregate_owner(analyzer, file, type_node) {
15498        return Some(owner);
15499    }
15500    let type_name = normalize_type_text(node_text(type_node, source));
15501    visibility
15502        .resolve_type(file, &type_name)
15503        .filter(CodeUnit::is_class)
15504}
15505
15506pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
15507    let mut cursor = node.walk();
15508    node.named_children(&mut cursor).find(|child| {
15509        matches!(
15510            child.kind(),
15511            "type_identifier"
15512                | "primitive_type"
15513                | "qualified_identifier"
15514                | "scoped_type_identifier"
15515                | "struct_specifier"
15516                | "union_specifier"
15517                | "enum_specifier"
15518        )
15519    })
15520}
15521
15522pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
15523    visibility: &VisibilityIndex<'_>,
15524    file: &ProjectFile,
15525    source: &str,
15526    declarator: Node<'_>,
15527    type_text: Option<&str>,
15528    bindings: &LocalInferenceEngine<T>,
15529) -> bool {
15530    if !has_ancestor_kind(declarator, "compound_statement") {
15531        return false;
15532    }
15533    if declarator
15534        .child_by_field_name("declarator")
15535        .is_none_or(|declarator| declarator.kind() != "identifier")
15536    {
15537        return false;
15538    }
15539    if !type_text
15540        .and_then(|text| visibility.resolve_type(file, text))
15541        .is_some_and(|unit| unit.is_class())
15542    {
15543        return false;
15544    }
15545    declarator
15546        .child_by_field_name("parameters")
15547        .is_some_and(|parameters| {
15548            constructor_parameters_look_like_expressions(parameters, source, bindings)
15549        })
15550}
15551
15552fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
15553    parameters: Node<'_>,
15554    source: &str,
15555    bindings: &LocalInferenceEngine<T>,
15556) -> bool {
15557    let mut cursor = parameters.walk();
15558    parameters.named_children(&mut cursor).any(|parameter| {
15559        !matches!(
15560            parameter.kind(),
15561            "parameter_declaration" | "optional_parameter_declaration"
15562        ) || parameter_declaration_is_local_expression(parameter, source, bindings)
15563    })
15564}
15565
15566fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
15567    parameter: Node<'_>,
15568    source: &str,
15569    bindings: &LocalInferenceEngine<T>,
15570) -> bool {
15571    let text = node_text(parameter, source).trim();
15572    if text
15573        .chars()
15574        .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
15575        && bindings.is_shadowed(text)
15576    {
15577        return true;
15578    }
15579
15580    let Some(base) = parameter
15581        .child_by_field_name("type")
15582        .filter(|base| base.kind() == "type_identifier")
15583    else {
15584        return false;
15585    };
15586    let Some(subscript) = parameter
15587        .child_by_field_name("declarator")
15588        .filter(|declarator| declarator.kind() == "abstract_array_declarator")
15589    else {
15590        return false;
15591    };
15592    subscript.child_by_field_name("size").is_some()
15593        && bindings.is_shadowed(node_text(base, source).trim())
15594}
15595
15596pub fn is_declaration_name(node: Node<'_>) -> bool {
15597    let Some(parent) = node.parent() else {
15598        return false;
15599    };
15600    if parent
15601        .child_by_field_name("name")
15602        .is_some_and(|name| same_node(name, node))
15603    {
15604        if matches!(
15605            parent.kind(),
15606            "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
15607        ) {
15608            return cpp_tag_specifier_declares_name(parent);
15609        }
15610        if matches!(
15611            parent.kind(),
15612            "namespace_definition"
15613                | "namespace_alias_definition"
15614                | "alias_declaration"
15615                | "enumerator"
15616        ) {
15617            return true;
15618        }
15619    }
15620
15621    let mut current = Some(parent);
15622    while let Some(ancestor) = current {
15623        let type_definition = ancestor.kind() == "type_definition";
15624        let mut child_cursor = ancestor.walk();
15625        if ancestor.named_children(&mut child_cursor).any(|child| {
15626            declaration_declarator(ancestor, child).is_some_and(|declarator| {
15627                declarator_name_path_contains(declarator, node, type_definition)
15628            })
15629        }) {
15630            return true;
15631        }
15632        if matches!(
15633            ancestor.kind(),
15634            "declaration"
15635                | "field_declaration"
15636                | "parameter_declaration"
15637                | "optional_parameter_declaration"
15638                | "function_definition"
15639                | "type_definition"
15640                | "alias_declaration"
15641                | "template_instantiation"
15642                | "class_specifier"
15643                | "struct_specifier"
15644                | "union_specifier"
15645                | "enum_specifier"
15646        ) {
15647            return false;
15648        }
15649        current = ancestor.parent();
15650    }
15651    false
15652}
15653
15654/// Whether tree-sitter recovered a qualified friend-class type as an ordinary
15655/// declaration's declarator inside a malformed class body.
15656///
15657/// An export macro between `class` and the class name can make the containing
15658/// body parse as a function body. A source declaration such as
15659/// `friend class internal::Friend;` then retains this exact structure:
15660/// `declaration(type: friend, ERROR(class), declarator: internal::Friend)`.
15661/// The declarator is a type reference despite its field role.
15662pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
15663    if !matches!(
15664        node.kind(),
15665        "qualified_identifier" | "scoped_type_identifier"
15666    ) {
15667        return false;
15668    }
15669    let Some(declaration) = node
15670        .parent()
15671        .filter(|parent| parent.kind() == "declaration")
15672    else {
15673        return false;
15674    };
15675    if declaration.child_by_field_name("declarator") != Some(node)
15676        || !declaration
15677            .child_by_field_name("type")
15678            .is_some_and(|friend| {
15679                friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
15680            })
15681    {
15682        return false;
15683    }
15684    let mut cursor = declaration.walk();
15685    let mut errors = declaration
15686        .named_children(&mut cursor)
15687        .filter(|child| child.kind() == "ERROR");
15688    let Some(error) = errors.next() else {
15689        return false;
15690    };
15691    errors.next().is_none()
15692        && error.named_child_count() == 1
15693        && error.named_child(0).is_some_and(|class| {
15694            class.kind() == "identifier" && node_text(class, source) == "class"
15695        })
15696}
15697
15698pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
15699    if !matches!(node.kind(), "identifier" | "field_identifier") {
15700        return false;
15701    }
15702    if let Some(parent) = node.parent() {
15703        if parent.kind() == "call_expression"
15704            && parent.child_by_field_name("function") == Some(node)
15705        {
15706            return false;
15707        }
15708        if matches!(parent.kind(), "labeled_statement" | "goto_statement")
15709            && parent.child_by_field_name("label") == Some(node)
15710        {
15711            return false;
15712        }
15713    }
15714    if is_declaration_name(node) {
15715        return false;
15716    }
15717    let mut current = node.parent();
15718    while let Some(ancestor) = current {
15719        match ancestor.kind() {
15720            "preproc_ifdef" | "preproc_ifndef" => {
15721                if ancestor
15722                    .child_by_field_name("name")
15723                    .is_some_and(|name| node_range_contains(name, node))
15724                {
15725                    return false;
15726                }
15727            }
15728            "preproc_if" | "preproc_elif" => {
15729                if ancestor
15730                    .child_by_field_name("condition")
15731                    .is_some_and(|condition| node_range_contains(condition, node))
15732                {
15733                    return false;
15734                }
15735            }
15736            "preproc_else" => {}
15737            kind if kind.starts_with("preproc_") => return false,
15738            _ => {}
15739        }
15740        if matches!(
15741            ancestor.kind(),
15742            "translation_unit" | "function_definition" | "compound_statement"
15743        ) {
15744            break;
15745        }
15746        current = ancestor.parent();
15747    }
15748    true
15749}
15750
15751fn recovered_c_reference_node(
15752    visibility: &VisibilityIndex<'_>,
15753    file: &ProjectFile,
15754    node: Node<'_>,
15755    source: &str,
15756) -> bool {
15757    if node.start_byte() >= node.end_byte()
15758        || node.is_error()
15759        || node.is_missing()
15760        || !matches!(
15761            node.kind(),
15762            "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
15763        )
15764        || recovered_c_macro_binding_role(node)
15765        || recovered_c_label_role(node)
15766    {
15767        return false;
15768    }
15769    // A declaration name can be an identifier child of a recovered ERROR
15770    // (for example `EFI_STATUS Encode()` in C files parsed as C++). Do not
15771    // let a same-named macro turn that binder into a reference. An
15772    // assignment whose C callee follows the recovered `explicit` token is
15773    // the one expression-shaped exception: the generic declarator walk sees
15774    // its function_declarator as a declaration path, but the ERROR sibling
15775    // proves it is a call.
15776    let name = node_text(node, source);
15777    let recovered_function_call = recovered_c_function_call(visibility, file, node, name);
15778    let recovered_macro_call = recovered_c_function_declarator_invocation(node)
15779        && visibility.macro_name_may_be_bound_at(file, name, node.start_byte());
15780    let recovered_parenthesized_reference = recovered_c_parenthesized_declarator_reference(node);
15781    if is_declaration_name(node)
15782        && !recovered_c_explicit_assignment_callee(visibility, file, node, name)
15783        && !recovered_function_call
15784        && !recovered_macro_call
15785        && !recovered_parenthesized_reference
15786    {
15787        return false;
15788    }
15789
15790    if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
15791        return true;
15792    }
15793    if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
15794        return true;
15795    }
15796    if recovered_parenthesized_reference {
15797        return true;
15798    }
15799    if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
15800        if recovered_function_call {
15801            return true;
15802        }
15803        return visibility
15804            .visible_identifier_candidates(file, name)
15805            .any(|candidate| candidate.is_class() || candidate.is_module());
15806    }
15807    let visible = visibility
15808        .visible_identifier_candidates(file, name)
15809        .next()
15810        .is_some();
15811    visible
15812        && (recovered_c_reference_anchor(node)
15813            || recovered_c_error_expression_leaf(node)
15814            || recovered_function_call)
15815}
15816
15817fn push_recovered_c_range(
15818    ranges: &mut Vec<Range>,
15819    seen: &mut HashSet<(usize, usize)>,
15820    start_byte: usize,
15821    end_byte: usize,
15822    node: Node<'_>,
15823    limit: usize,
15824) -> bool {
15825    if start_byte >= end_byte || !seen.insert((start_byte, end_byte)) {
15826        return true;
15827    }
15828    if ranges.len() >= limit {
15829        return false;
15830    }
15831    ranges.push(Range {
15832        start_byte,
15833        end_byte,
15834        start_line: node.start_position().row,
15835        end_line: node.end_position().row,
15836    });
15837    true
15838}
15839
15840/// A reference leaf can sit directly beneath an ERROR while its ERROR parent
15841/// is still attached to a real expression (most often a recovered macro call
15842/// argument). The expression parent is the structured proof; an unindexed
15843/// identifier beneath a bare recovery envelope has no such proof.
15844fn recovered_c_error_expression_leaf(node: Node<'_>) -> bool {
15845    let mut current = node.parent();
15846    while let Some(parent) = current {
15847        if parent.is_error() {
15848            let Some(anchor) = parent.parent() else {
15849                return false;
15850            };
15851            return anchor.kind().ends_with("_expression")
15852                || matches!(
15853                    anchor.kind(),
15854                    "argument_list"
15855                        | "return_statement"
15856                        | "expression_statement"
15857                        | "case_statement"
15858                        | "initializer_list"
15859                        | "field_designator"
15860                        | "enumerator"
15861                );
15862        }
15863        if matches!(
15864            parent.kind(),
15865            "translation_unit" | "function_definition" | "compound_statement"
15866        ) {
15867            return false;
15868        }
15869        current = parent.parent();
15870    }
15871    false
15872}
15873
15874/// C recovery may represent a call as `identifier > function_declarator >
15875/// function_declarator > ERROR > compound_statement`. This shape is only a
15876/// call when the malformed declarator is attached to a real function body and
15877/// the name is an indexed visible callable. A declaration's
15878/// `ERROR > function_declarator > declaration` shape deliberately fails this
15879/// test.
15880fn recovered_c_function_call(
15881    visibility: &VisibilityIndex<'_>,
15882    file: &ProjectFile,
15883    node: Node<'_>,
15884    name: &str,
15885) -> bool {
15886    if !matches!(
15887        node.kind(),
15888        "identifier" | "field_identifier" | "type_identifier"
15889    ) {
15890        return false;
15891    }
15892    // C++ keywords used as C arguments can leave only the call prefix under
15893    // ERROR, with the remaining arguments in a following expression statement.
15894    // Require a statement boundary in a real block, an opening parenthesis,
15895    // and a recovered argument. A bare ERROR identifier supplies no call role.
15896    let error_call_prefix = node.parent().is_some_and(|error| {
15897        error.is_error()
15898            && error
15899                .parent()
15900                .is_some_and(|parent| parent.kind() == "compound_statement")
15901    }) && node
15902        .prev_sibling()
15903        .is_none_or(|previous| previous.kind() == ";")
15904        && node.next_sibling().is_some_and(|open| {
15905            open.kind() == "("
15906                && open.next_named_sibling().is_some_and(|argument| {
15907                    argument.kind() == "parameter_declaration" && !argument.has_error()
15908                })
15909        });
15910    (error_call_prefix || recovered_c_function_declarator_invocation(node))
15911        && visibility
15912            .visible_identifier_candidates(file, name)
15913            .any(CodeUnit::is_function)
15914}
15915
15916/// Return whether a C identifier belongs to a call-shaped declarator that the
15917/// C++ grammar put under an `ERROR` node.
15918///
15919/// The malformed call can be direct (`f(arg)`) or nested in a parameter
15920/// declaration when one of its arguments looks like a type (`f(TYPE, value)`).
15921/// In both cases the CST retains the function-declarator and its enclosing
15922/// recovery envelope. We walk only those declarator/parameter nodes and stop
15923/// at a real expression-bearing boundary; declarations therefore cannot pass
15924/// this predicate merely because they have a parameter list.
15925fn recovered_c_function_declarator_invocation(node: Node<'_>) -> bool {
15926    let mut function_declarator = if node.parent().is_some_and(|parent| {
15927        parent.kind() == "function_declarator"
15928            && parent.child_by_field_name("declarator") == Some(node)
15929    }) {
15930        node.parent().expect("checked function declarator parent")
15931    } else {
15932        let Some(parameter) = node.parent().filter(|parent| {
15933            parent.kind() == "parameter_declaration"
15934                && parent.child_by_field_name("type") == Some(node)
15935        }) else {
15936            return false;
15937        };
15938        if !parameter
15939            .child_by_field_name("declarator")
15940            .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
15941        {
15942            return false;
15943        }
15944        let Some(parameters) = parameter
15945            .parent()
15946            .filter(|parent| parent.kind() == "parameter_list")
15947        else {
15948            return false;
15949        };
15950        let Some(function_declarator) = parameters
15951            .parent()
15952            .filter(|parent| parent.kind() == "function_declarator")
15953        else {
15954            return false;
15955        };
15956        function_declarator
15957    };
15958
15959    // Recovery may absorb the next statement's parenthesized cast into a
15960    // second function declarator. Follow only the original declarator chain.
15961    while let Some(parent) = function_declarator.parent().filter(|parent| {
15962        parent.kind() == "function_declarator"
15963            && parent.child_by_field_name("declarator") == Some(function_declarator)
15964    }) {
15965        function_declarator = parent;
15966    }
15967    let Some(mut current) = function_declarator
15968        .parent()
15969        .filter(|parent| parent.is_error())
15970    else {
15971        return false;
15972    };
15973    loop {
15974        let Some(parent) = current.parent() else {
15975            return false;
15976        };
15977        if matches!(
15978            parent.kind(),
15979            "translation_unit"
15980                | "compound_statement"
15981                | "preproc_if"
15982                | "preproc_ifdef"
15983                | "preproc_ifndef"
15984                | "preproc_else"
15985                | "preproc_elif"
15986        ) {
15987            return true;
15988        }
15989        if parent.kind() == "function_definition"
15990            && parent.child_by_field_name("declarator") == Some(current)
15991            && parent.named_child(0) == Some(current)
15992            && parent.child_by_field_name("body").is_some()
15993        {
15994            return true;
15995        }
15996        if parent.is_error()
15997            || matches!(
15998                parent.kind(),
15999                "parameter_declaration"
16000                    | "parameter_list"
16001                    | "function_declarator"
16002                    | "abstract_function_declarator"
16003                    | "parenthesized_declarator"
16004            )
16005        {
16006            current = parent;
16007            continue;
16008        }
16009        return false;
16010    }
16011}
16012
16013/// C permits an identifier named `typename`. The C++ grammar can recover an
16014/// assignment using that identifier as a declaration whose declarator is a
16015/// parenthesized argument list, for example `typename = f(ctx, value)`. Only
16016/// the argument retained beneath the nested `ERROR` is a reference; sibling
16017/// declarator identifiers remain binders/grammar artifacts.
16018fn recovered_c_parenthesized_declarator_reference(node: Node<'_>) -> bool {
16019    let Some(error) = node.parent().filter(|parent| parent.is_error()) else {
16020        return false;
16021    };
16022    if error.named_child_count() != 1 || error.named_child(0) != Some(node) {
16023        return false;
16024    }
16025    let Some(declarator) = error
16026        .parent()
16027        .filter(|parent| parent.kind() == "parenthesized_declarator")
16028    else {
16029        return false;
16030    };
16031    let Some(declaration) = declarator
16032        .parent()
16033        .filter(|parent| parent.kind() == "declaration")
16034    else {
16035        return false;
16036    };
16037    if declaration.child_by_field_name("declarator") != Some(declarator) {
16038        return false;
16039    }
16040    let Some(type_node) = declaration.child_by_field_name("type") else {
16041        return false;
16042    };
16043    type_node.kind() == "dependent_type"
16044        && type_node
16045            .child(0)
16046            .is_some_and(|keyword| keyword.kind() == "typename")
16047}
16048
16049fn recovered_c_explicit_assignment_callee(
16050    visibility: &VisibilityIndex<'_>,
16051    file: &ProjectFile,
16052    node: Node<'_>,
16053    name: &str,
16054) -> bool {
16055    let mut current = node;
16056    let error = loop {
16057        let Some(parent) = current.parent() else {
16058            return false;
16059        };
16060        if parent.is_error() {
16061            break parent;
16062        }
16063        current = parent;
16064    };
16065    let mut cursor = error.walk();
16066    let explicit_recovery_precedes_callee = error
16067        .named_children(&mut cursor)
16068        .take_while(|child| child.start_byte() < node.start_byte())
16069        .any(|child| child.kind() == "explicit_function_specifier");
16070    if !explicit_recovery_precedes_callee {
16071        return false;
16072    }
16073    visibility
16074        .visible_identifier_candidates(file, name)
16075        .any(CodeUnit::is_function)
16076}
16077
16078fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
16079    while let Some(parent) = node.parent() {
16080        if matches!(
16081            parent.kind(),
16082            "preproc_def" | "preproc_function_def" | "preproc_params"
16083        ) {
16084            return true;
16085        }
16086        if parent.is_error()
16087            || matches!(
16088                parent.kind(),
16089                "translation_unit" | "function_definition" | "compound_statement"
16090            )
16091        {
16092            return false;
16093        }
16094        node = parent;
16095    }
16096    false
16097}
16098
16099fn recovered_c_label_role(node: Node<'_>) -> bool {
16100    node.parent().is_some_and(|parent| {
16101        matches!(parent.kind(), "labeled_statement" | "goto_statement")
16102            && parent.child_by_field_name("label") == Some(node)
16103    })
16104}
16105
16106fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
16107    while let Some(parent) = node.parent() {
16108        if parent.is_error() {
16109            return false;
16110        }
16111        // A C macro call recovered as a function declarator can parse an
16112        // assignment-shaped argument as an optional parameter. Its
16113        // `default_value` field remains an expression role even though the
16114        // surrounding call shape is beneath ERROR.
16115        if parent.kind() == "optional_parameter_declaration"
16116            && parent
16117                .child_by_field_name("default_value")
16118                .is_some_and(|value| node_range_contains(value, node))
16119        {
16120            return true;
16121        }
16122        if parent.kind().ends_with("_expression")
16123            || matches!(
16124                parent.kind(),
16125                "argument_list"
16126                    | "return_statement"
16127                    | "expression_statement"
16128                    | "case_statement"
16129                    | "initializer_list"
16130                    | "init_declarator"
16131                    | "array_declarator"
16132                    | "field_designator"
16133                    | "enumerator"
16134            )
16135        {
16136            return true;
16137        }
16138        if matches!(
16139            parent.kind(),
16140            "translation_unit"
16141                | "function_definition"
16142                | "compound_statement"
16143                | "declaration"
16144                | "field_declaration"
16145                | "parameter_declaration"
16146        ) {
16147            return false;
16148        }
16149        node = parent;
16150    }
16151    false
16152}
16153
16154/// Whether a parameter declaration belongs to the callable scope whose body can
16155/// contain references to it.
16156///
16157/// Error recovery can wrap a macro-decorated class body in a synthetic outer
16158/// `function_definition`. Merely finding any callable ancestor would then leak
16159/// parameters from member prototypes into later member bodies. Require the
16160/// parameter to be inside that definition's own declarator instead.
16161pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
16162    let mut current = parameter.parent();
16163    while let Some(ancestor) = current {
16164        if ancestor.kind() == "lambda_expression" {
16165            return ancestor
16166                .child_by_field_name("declarator")
16167                .is_some_and(|declarator| {
16168                    declarator.start_byte() <= parameter.start_byte()
16169                        && parameter.end_byte() <= declarator.end_byte()
16170                });
16171        }
16172        if ancestor.kind() == "function_definition" {
16173            return ancestor
16174                .child_by_field_name("declarator")
16175                .is_some_and(|declarator| {
16176                    declarator.start_byte() <= parameter.start_byte()
16177                        && parameter.end_byte() <= declarator.end_byte()
16178                });
16179        }
16180        current = ancestor.parent();
16181    }
16182    false
16183}
16184
16185pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
16186    let mut current = node.parent();
16187    while let Some(ancestor) = current {
16188        if matches!(
16189            ancestor.kind(),
16190            "parameter_declaration" | "optional_parameter_declaration"
16191        ) {
16192            return crate::declarations::cpp_type_field_contains_node(ancestor, node);
16193        }
16194        if matches!(
16195            ancestor.kind(),
16196            "function_definition" | "lambda_expression" | "compound_statement"
16197        ) {
16198            return false;
16199        }
16200        current = ancestor.parent();
16201    }
16202    false
16203}
16204
16205/// Walk tag-specifier ancestors until a type-reference context makes its name
16206/// an operand rather than a declaration. Callers retain their own terminal
16207/// declaration boundaries after this shared exclusion scan.
16208pub fn cpp_tag_specifier_ancestors_before_type_reference_context(
16209    specifier: Node<'_>,
16210) -> impl Iterator<Item = Node<'_>> {
16211    std::iter::successors(specifier.parent(), |ancestor| ancestor.parent()).take_while(|ancestor| {
16212        !matches!(
16213            ancestor.kind(),
16214            "type_descriptor"
16215                | "parameter_declaration"
16216                | "optional_parameter_declaration"
16217                | "template_argument_list"
16218                | "cast_expression"
16219        )
16220    })
16221}
16222
16223pub fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
16224    if specifier.child_by_field_name("body").is_some() {
16225        return true;
16226    }
16227    for ancestor in cpp_tag_specifier_ancestors_before_type_reference_context(specifier) {
16228        match ancestor.kind() {
16229            "declaration" | "field_declaration" => {
16230                let mut cursor = ancestor.walk();
16231                return ancestor
16232                    .children_by_field_name("declarator", &mut cursor)
16233                    .next()
16234                    .is_none();
16235            }
16236            "translation_unit" => return true,
16237            _ => {}
16238        }
16239    }
16240    false
16241}
16242
16243pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
16244    match node.kind() {
16245        "identifier"
16246        | "field_identifier"
16247        | "qualified_identifier"
16248        | "scoped_identifier"
16249        | "operator_name"
16250        | "destructor_name"
16251        | "literal_operator_name" => Some(node),
16252        "reference_declarator" | "parenthesized_declarator" => {
16253            node.named_child(0).and_then(declarator_name_node)
16254        }
16255        _ => node
16256            .child_by_field_name("declarator")
16257            .or_else(|| node.child_by_field_name("name"))
16258            .or_else(|| node.child_by_field_name("field"))
16259            .and_then(declarator_name_node),
16260    }
16261}
16262
16263/// Whether `candidate` occurs on the declarator path before its selected name.
16264pub fn declarator_path_contains_before_name(
16265    declarator: Node<'_>,
16266    name: Node<'_>,
16267    candidate: Node<'_>,
16268) -> bool {
16269    let mut current = Some(declarator);
16270    while let Some(node) = current {
16271        if same_node(node, candidate) {
16272            return true;
16273        }
16274        if same_node(node, name) {
16275            return false;
16276        }
16277        current = node
16278            .child_by_field_name("declarator")
16279            .or_else(|| node.child_by_field_name("name"))
16280            .or_else(|| node.child_by_field_name("field"));
16281    }
16282    false
16283}
16284
16285fn declarator_name_path_contains(
16286    declarator: Node<'_>,
16287    candidate: Node<'_>,
16288    allow_type_identifier: bool,
16289) -> bool {
16290    let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
16291        return false;
16292    };
16293    declarator_path_contains_before_name(declarator, name, candidate)
16294}
16295
16296fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
16297    match node.kind() {
16298        "identifier"
16299        | "field_identifier"
16300        | "operator_name"
16301        | "destructor_name"
16302        | "literal_operator_name" => Some(node),
16303        "type_identifier" if allow_type_identifier => Some(node),
16304        _ => node
16305            .child_by_field_name("declarator")
16306            .or_else(|| node.child_by_field_name("name"))
16307            .or_else(|| node.child_by_field_name("field"))
16308            .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
16309    }
16310}
16311
16312/// True when `node` is a component of a larger structured type node whose outer
16313/// range is the single reference surfaced to callers.
16314pub fn is_nested_type_node(node: Node<'_>) -> bool {
16315    node.parent().is_some_and(|parent| {
16316        matches!(
16317            parent.kind(),
16318            "qualified_identifier" | "scoped_type_identifier" | "template_type"
16319        )
16320    })
16321}
16322
16323pub struct OutOfLineMemberDefinitionOwners<'tree> {
16324    pub owners: Vec<(Node<'tree>, CodeUnit)>,
16325    innermost: Option<(Node<'tree>, CodeUnit)>,
16326}
16327
16328impl OutOfLineMemberDefinitionOwners<'_> {
16329    pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
16330        self.innermost.as_ref().map(|(node, owner)| (*node, owner))
16331    }
16332}
16333
16334pub struct QualifiedOwnerComponents<'tree> {
16335    pub nodes: Vec<Node<'tree>>,
16336    pub names: Vec<String>,
16337    pub global: bool,
16338}
16339
16340/// True when each structured qualifier on the callable-name path has a real
16341/// `::` token. A macro-prefixed return type can make tree-sitter insert a
16342/// zero-width missing separator and parse `TYPE Result<T> method()` as the
16343/// false qualified declarator `Result<T>::method`.
16344pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
16345    let mut stack = vec![node];
16346    let mut found_separator = false;
16347    while let Some(current) = stack.pop() {
16348        if !matches!(
16349            current.kind(),
16350            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
16351        ) {
16352            continue;
16353        }
16354        let mut current_has_separator = false;
16355        for child in children_iter(current) {
16356            if child.kind() == "::" {
16357                if child.is_missing() {
16358                    return false;
16359                }
16360                current_has_separator = true;
16361                found_separator = true;
16362            }
16363        }
16364        if !current_has_separator {
16365            return false;
16366        }
16367        for field in ["scope", "name"] {
16368            if let Some(child) = current.child_by_field_name(field)
16369                && matches!(
16370                    child.kind(),
16371                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
16372                )
16373            {
16374                stack.push(child);
16375            }
16376        }
16377    }
16378    found_separator
16379}
16380
16381pub fn qualified_owner_components<'tree>(
16382    node: Node<'tree>,
16383    source: &str,
16384) -> Option<QualifiedOwnerComponents<'tree>> {
16385    if !qualified_name_has_concrete_scope_separators(node) {
16386        return None;
16387    }
16388    let mut nodes = cpp_name_component_nodes(node)?;
16389    nodes.pop()?;
16390    if nodes.is_empty() {
16391        return None;
16392    }
16393    let names = nodes
16394        .iter()
16395        .map(|component| node_text(*component, source).to_string())
16396        .collect();
16397    Some(QualifiedOwnerComponents {
16398        nodes,
16399        names,
16400        global: is_globally_qualified_cpp_name(node),
16401    })
16402}
16403
16404pub fn out_of_line_member_definition_owner<'tree>(
16405    analyzer: &CppGraphSource<'_>,
16406    visibility: &VisibilityIndex<'_>,
16407    file: &ProjectFile,
16408    source: &str,
16409    node: Node<'tree>,
16410) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
16411    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
16412        || !has_ancestor_kind(node, "function_definition")
16413        || !is_function_declarator_name_root(node)
16414    {
16415        return None;
16416    }
16417    let qualified = qualified_owner_components(node, source)?;
16418    let lexical_scope = enclosing_namespace_components(node, source)?;
16419    let mut owners = Vec::new();
16420    let mut innermost = None;
16421
16422    for component_count in 1..=qualified.names.len() {
16423        if let LexicalTypeResolution::Resolved { unit, .. } = visibility
16424            .resolve_type_components_lexically(
16425                analyzer,
16426                file,
16427                &qualified.names[..component_count],
16428                qualified.global,
16429                &lexical_scope,
16430            )
16431            && !owners
16432                .iter()
16433                .any(|(_, existing)| same_visible_symbol(existing, &unit))
16434        {
16435            if component_count == qualified.names.len() {
16436                innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
16437            }
16438            owners.push((qualified.nodes[component_count - 1], unit));
16439        }
16440    }
16441
16442    // The C++ analyzer has already reconciled an indexed out-of-line callable
16443    // against the include-visible class table. Consult that canonical owner
16444    // chain only when ordinary lexical lookup could not recover the innermost
16445    // owner.  A one-segment qualifier is safe here only when the enclosing
16446    // indexed callable has an authoritative class owner and the parser's
16447    // namespace path is a (possibly sparse) subsequence of that owner path.
16448    // The latter is what lets macro-wrapped namespace sentinels recover a
16449    // missing `time_internal`/`cord_internal` component without guessing an
16450    // unrelated short name.
16451    if innermost.is_none() {
16452        let indexed_owner_components = visibility
16453            .indexed_enclosing_owner_scope(analyzer, file, node)
16454            .or_else(|| {
16455                // Retain the legacy rendered-name fallback for the existing
16456                // multi-segment path when an enclosing owner chain is not
16457                // available (for example, cache-loaded units without parent
16458                // links).  One-segment recovery must stay canonical-only.
16459                if qualified.names.len() <= 1 {
16460                    return None;
16461                }
16462                let range = Range {
16463                    start_byte: node.start_byte(),
16464                    end_byte: node.end_byte(),
16465                    start_line: node.start_position().row,
16466                    end_line: node.end_position().row,
16467                };
16468                let start = analyzer.enclosing_code_unit(file, &range)?;
16469                let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
16470                    brokk_bifrost_core::analyzer::Language::Cpp,
16471                    &cpp_name_for(&start),
16472                );
16473                components.pop();
16474                Some(components)
16475            });
16476        if let Some(indexed_owner_components) = indexed_owner_components
16477            && indexed_owner_components.len() > qualified.names.len()
16478            && indexed_owner_components.ends_with(&qualified.names)
16479            && indexed_namespace_path_is_recoverable(
16480                &lexical_scope,
16481                &indexed_owner_components,
16482                qualified.names.len(),
16483            )
16484            // A globally-qualified one-segment owner is an explicit request
16485            // for the top-level binding; do not reinterpret it as a missing
16486            // namespace component.  Existing multi-segment global lookups
16487            // retain their historical indexed recovery.
16488            && (qualified.names.len() > 1 || !qualified.global)
16489        {
16490            let namespace_count = indexed_owner_components.len() - qualified.names.len();
16491            for component_count in 1..=qualified.names.len() {
16492                let expected = &indexed_owner_components[..namespace_count + component_count];
16493                let owner_node = qualified.nodes[component_count - 1];
16494                for owner in visibility
16495                    .visible_identifier_candidates(file, &qualified.names[component_count - 1])
16496                    .filter(|candidate| candidate.is_class())
16497                    .filter(|candidate| {
16498                        canonical_cpp_scope_components(candidate) == expected
16499                            && visibility.external_type_candidate_visible_in_context(
16500                                analyzer, file, candidate, node,
16501                            )
16502                    })
16503                {
16504                    if component_count == qualified.names.len() && innermost.is_none() {
16505                        innermost = Some((owner_node, owner.clone()));
16506                    }
16507                    if !owners
16508                        .iter()
16509                        .any(|(_, existing)| same_symbol(existing, owner))
16510                    {
16511                        owners.push((owner_node, owner.clone()));
16512                    }
16513                }
16514            }
16515        }
16516    }
16517    (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
16518}
16519
16520fn is_function_declarator_name_root(node: Node<'_>) -> bool {
16521    let mut current = node;
16522    while let Some(parent) = current.parent() {
16523        if parent.kind() == "function_declarator" {
16524            return parent.child_by_field_name("declarator") == Some(current);
16525        }
16526        if matches!(
16527            parent.kind(),
16528            "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
16529        ) && parent.child_by_field_name("declarator") == Some(current)
16530        {
16531            current = parent;
16532            continue;
16533        }
16534        return false;
16535    }
16536    false
16537}
16538
16539pub fn append_cpp_name_components(
16540    node: Node<'_>,
16541    source: &str,
16542    out: &mut Vec<String>,
16543) -> Option<()> {
16544    out.extend(
16545        cpp_name_component_nodes(node)?
16546            .into_iter()
16547            .map(|component| node_text(component, source).to_string()),
16548    );
16549    Some(())
16550}
16551
16552/// The components of a C++ name with the source range of the token that
16553/// writes each one, outermost first.
16554fn cpp_name_component_ranges(node: Node<'_>, source: &str) -> Option<Vec<(String, Range)>> {
16555    cpp_name_component_nodes(node).map(|components| {
16556        components
16557            .into_iter()
16558            .map(|component| {
16559                (
16560                    node_text(component, source).to_string(),
16561                    node_range(component),
16562                )
16563            })
16564            .collect()
16565    })
16566}
16567
16568pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
16569    let mut components = Vec::new();
16570    append_cpp_name_components(node, source, &mut components)?;
16571    Some(components)
16572}
16573
16574/// Resolve a structured type spelling from an object-like macro replacement
16575/// when definition-site source order has no answer.
16576///
16577/// Macro replacement tokens are looked up where the macro is expanded, so a
16578/// type declared later in the defining header can still be their destination.
16579/// Without expanding every invocation, accept only one include-visible logical
16580/// class or alias whose structured path ends in the replacement components.
16581/// An ordinary lexical answer always takes precedence at the call site.
16582pub fn unique_macro_replacement_type_candidate(
16583    analyzer: &CppGraphSource<'_>,
16584    visibility: &VisibilityIndex<'_>,
16585    file: &ProjectFile,
16586    components: &[String],
16587) -> Option<CodeUnit> {
16588    let terminal = components.last()?;
16589    let mut candidates = Vec::new();
16590    for candidate in visibility
16591        .visible_identifier_candidates(file, terminal)
16592        .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
16593        .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
16594    {
16595        if !candidates
16596            .iter()
16597            .any(|existing| same_logical_symbol(existing, candidate))
16598        {
16599            candidates.push(candidate.clone());
16600        }
16601    }
16602    (candidates.len() == 1).then(|| candidates.remove(0))
16603}
16604
16605/// The base scopes named by member using-declarations for `member` in one
16606/// class source range.
16607///
16608/// The grammar supplies the qualified identifier and each component. Keep
16609/// this interpretation shared between forward overload lookup and inverse
16610/// owner routing rather than reparsing a rendered `Base::member` string at
16611/// either call site.
16612pub fn cpp_member_using_declaration_scopes(source: &str, member: &str) -> Vec<String> {
16613    let mut parser = Parser::new();
16614    if parser
16615        .set_language(&tree_sitter_cpp::LANGUAGE.into())
16616        .is_err()
16617    {
16618        return Vec::new();
16619    }
16620    let Some(tree) = parser.parse(source, None) else {
16621        return Vec::new();
16622    };
16623    let mut scopes = Vec::new();
16624    let mut pending = vec![tree.root_node()];
16625    while let Some(node) = pending.pop() {
16626        if node.kind() == "using_declaration" {
16627            let Some(imported) = node.named_child(0) else {
16628                continue;
16629            };
16630            let Some(mut components) = cpp_type_name_components(imported, source) else {
16631                continue;
16632            };
16633            if components.pop().as_deref() == Some(member) && !components.is_empty() {
16634                scopes.push(components.join("::"));
16635            }
16636            continue;
16637        }
16638        push_named_children_reversed(node, &mut pending);
16639    }
16640    scopes
16641}
16642
16643/// Whether a structured using-declaration scope can name `qualified` as an
16644/// ancestor class. The boundary check prevents `Base` from matching
16645/// `OtherBase` while allowing a relative `Base` spelling to match `ns::Base`.
16646pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
16647    qualified == scope
16648        || qualified
16649            .strip_suffix(scope)
16650            .is_some_and(|prefix| prefix.ends_with("::"))
16651}
16652
16653/// Whether `node` is the direct structured type payload of a template
16654/// argument. This role remains meaningful even when a surrounding expression
16655/// is below tree-sitter recovery, because both the `template_argument_list`
16656/// and the `type_descriptor` retain their named fields.
16657pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
16658    let Some(type_descriptor) = node.parent() else {
16659        return false;
16660    };
16661    if type_descriptor.kind() != "type_descriptor"
16662        || type_descriptor.child_by_field_name("type") != Some(node)
16663    {
16664        return false;
16665    }
16666    let Some(arguments) = type_descriptor.parent() else {
16667        return false;
16668    };
16669    if arguments.kind() != "template_argument_list" {
16670        return false;
16671    }
16672    arguments.parent().is_some_and(|parent| {
16673        matches!(parent.kind(), "template_type" | "template_function")
16674            && parent.child_by_field_name("arguments") == Some(arguments)
16675    })
16676}
16677
16678pub fn cpp_template_reference_arguments(
16679    mut node: Node<'_>,
16680    source: &str,
16681) -> Option<Vec<CppTemplateExpression>> {
16682    loop {
16683        match node.kind() {
16684            "template_type" | "template_function" => {
16685                let arguments = node.child_by_field_name("arguments")?;
16686                let mut cursor = arguments.walk();
16687                return Some(
16688                    arguments
16689                        .named_children(&mut cursor)
16690                        .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
16691                        .map(|argument| CppTemplateExpression {
16692                            text: normalize_cpp_whitespace(node_text(argument, source)),
16693                            // One template term from a resolver query; see `ParentIndex::unindexed`.
16694                            term: cpp_template_term(
16695                                argument,
16696                                source,
16697                                &[],
16698                                &ParentIndex::unindexed(),
16699                            ),
16700                        })
16701                        .collect(),
16702                );
16703            }
16704            "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
16705                node = node
16706                    .child_by_field_name("name")
16707                    .or_else(|| node.child_by_field_name("type"))?;
16708            }
16709            _ => return None,
16710        }
16711    }
16712}
16713
16714fn cpp_reconcile_primary_template_parameters(
16715    candidates: &[(&CodeUnit, &CppTemplateMetadata)],
16716    preferred: &CodeUnit,
16717) -> Option<Vec<CppTemplateParameterMetadata>> {
16718    let canonical = candidates
16719        .iter()
16720        .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
16721    let mut merged = canonical
16722        .parameters
16723        .iter()
16724        .map(|parameter| CppTemplateParameterMetadata {
16725            name: parameter.name.clone(),
16726            kind: parameter.kind,
16727            variadic: parameter.variadic,
16728            default: None,
16729        })
16730        .collect::<Vec<_>>();
16731
16732    for (_, metadata) in candidates {
16733        if metadata.parameters.len() != merged.len() {
16734            return None;
16735        }
16736        let rename_bindings = metadata
16737            .parameters
16738            .iter()
16739            .zip(&merged)
16740            .map(|(parameter, canonical)| {
16741                (
16742                    parameter.name.clone(),
16743                    CppTemplateTerm::Parameter(canonical.name.clone()),
16744                )
16745            })
16746            .collect::<HashMap<_, _>>();
16747        for ((parameter, canonical), merged_parameter) in metadata
16748            .parameters
16749            .iter()
16750            .zip(&canonical.parameters)
16751            .zip(&mut merged)
16752        {
16753            if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
16754                return None;
16755            }
16756            let Some(default) = &parameter.default else {
16757                continue;
16758            };
16759            let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
16760            if let Some(existing) = &merged_parameter.default {
16761                if !cpp_template_terms_equal(&existing.term, &normalized_term) {
16762                    return None;
16763                }
16764            } else {
16765                merged_parameter.default = Some(CppTemplateExpression {
16766                    text: default.text.clone(),
16767                    term: normalized_term,
16768                });
16769            }
16770        }
16771    }
16772    Some(merged)
16773}
16774
16775pub fn cpp_bind_template_arguments(
16776    parameters: &[CppTemplateParameterMetadata],
16777    explicit_arguments: &[CppTemplateExpression],
16778) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
16779    let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
16780    if variadic_index.is_some_and(|index| {
16781        index + 1 != parameters.len()
16782            || parameters[index + 1..]
16783                .iter()
16784                .any(|parameter| parameter.variadic)
16785    }) {
16786        return None;
16787    }
16788    let fixed_count = variadic_index.unwrap_or(parameters.len());
16789    if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
16790        return None;
16791    }
16792    let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
16793    let mut expanded = explicit_arguments[..explicit_fixed_count]
16794        .iter()
16795        .map(cpp_clone_template_expression_iterative)
16796        .collect::<Vec<_>>();
16797    let mut bindings = HashMap::default();
16798    for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
16799        bindings.insert(
16800            parameter.name.clone(),
16801            cpp_clone_template_term_iterative(&argument.term),
16802        );
16803    }
16804    for parameter in &parameters[explicit_fixed_count..fixed_count] {
16805        let default = parameter.default.as_ref()?;
16806        let term = cpp_substitute_template_term(&default.term, &bindings)?;
16807        bindings.insert(parameter.name.clone(), term.clone());
16808        expanded.push(CppTemplateExpression {
16809            text: default.text.clone(),
16810            term,
16811        });
16812    }
16813    if let Some(index) = variadic_index {
16814        let packed_arguments = &explicit_arguments[explicit_fixed_count..];
16815        expanded.extend(
16816            packed_arguments
16817                .iter()
16818                .map(cpp_clone_template_expression_iterative),
16819        );
16820        bindings.insert(
16821            parameters[index].name.clone(),
16822            CppTemplateTerm::Node {
16823                kind: "parameter_pack".to_string(),
16824                children: packed_arguments
16825                    .iter()
16826                    .map(|argument| cpp_clone_template_term_iterative(&argument.term))
16827                    .collect(),
16828            },
16829        );
16830    }
16831    Some((expanded, bindings))
16832}
16833
16834fn cpp_specialization_matches(
16835    metadata: &CppTemplateMetadata,
16836    arguments: &[CppTemplateExpression],
16837) -> bool {
16838    cpp_specialization_terms_match(
16839        &metadata.parameters,
16840        &metadata.specialization_arguments,
16841        arguments,
16842    )
16843}
16844
16845fn cpp_specialization_terms_match(
16846    parameters: &[CppTemplateParameterMetadata],
16847    patterns: &[CppTemplateExpression],
16848    arguments: &[CppTemplateExpression],
16849) -> bool {
16850    if patterns.len() != arguments.len() {
16851        return false;
16852    }
16853    let parameter_names = parameters
16854        .iter()
16855        .map(|parameter| parameter.name.as_str())
16856        .collect::<HashSet<_>>();
16857    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
16858    patterns.iter().zip(arguments).all(|(pattern, argument)| {
16859        cpp_unify_template_term(
16860            &pattern.term,
16861            &argument.term,
16862            &parameter_names,
16863            &mut bindings,
16864        )
16865    })
16866}
16867
16868fn cpp_specialization_more_specialized(
16869    candidate: &CppTemplateMetadata,
16870    other: &CppTemplateMetadata,
16871) -> bool {
16872    cpp_specialization_pattern_accepts(other, candidate)
16873        && !cpp_specialization_pattern_accepts(candidate, other)
16874}
16875
16876fn cpp_specialization_pattern_accepts(
16877    broader: &CppTemplateMetadata,
16878    narrower: &CppTemplateMetadata,
16879) -> bool {
16880    cpp_specialization_terms_match(
16881        &broader.parameters,
16882        &broader.specialization_arguments,
16883        &narrower.specialization_arguments,
16884    )
16885}
16886
16887pub fn cpp_substitute_template_term(
16888    term: &CppTemplateTerm,
16889    bindings: &HashMap<String, CppTemplateTerm>,
16890) -> Option<CppTemplateTerm> {
16891    enum Work<'a> {
16892        Visit(&'a CppTemplateTerm),
16893        Build { kind: String, child_count: usize },
16894    }
16895
16896    let mut work = vec![Work::Visit(term)];
16897    let mut substituted = Vec::new();
16898    while let Some(next) = work.pop() {
16899        match next {
16900            Work::Visit(CppTemplateTerm::Parameter(name)) => {
16901                substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
16902            }
16903            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
16904                substituted.push(CppTemplateTerm::Atom {
16905                    kind: kind.clone(),
16906                    text: text.clone(),
16907                });
16908            }
16909            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
16910                work.push(Work::Build {
16911                    kind: kind.clone(),
16912                    child_count: children.len(),
16913                });
16914                work.extend(children.iter().rev().map(Work::Visit));
16915            }
16916            Work::Build { kind, child_count } => {
16917                let children = substituted.split_off(substituted.len() - child_count);
16918                substituted.push(CppTemplateTerm::Node { kind, children });
16919            }
16920        }
16921    }
16922    substituted.pop()
16923}
16924
16925pub fn cpp_substitute_template_arguments(
16926    arguments: &[CppTemplateExpression],
16927    bindings: &HashMap<String, CppTemplateTerm>,
16928) -> Option<Vec<CppTemplateExpression>> {
16929    let mut substituted = Vec::new();
16930    for argument in arguments {
16931        let CppTemplateTerm::Node { kind, children } = &argument.term else {
16932            substituted.push(CppTemplateExpression {
16933                text: argument.text.clone(),
16934                term: cpp_substitute_template_term(&argument.term, bindings)?,
16935            });
16936            continue;
16937        };
16938        if kind != "parameter_pack_expansion" {
16939            substituted.push(CppTemplateExpression {
16940                text: argument.text.clone(),
16941                term: cpp_substitute_template_term(&argument.term, bindings)?,
16942            });
16943            continue;
16944        }
16945        let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
16946            return None;
16947        };
16948        if ellipsis != "..." {
16949            return None;
16950        }
16951
16952        let mut pack_names = Vec::new();
16953        let mut work = vec![pattern];
16954        while let Some(term) = work.pop() {
16955            match term {
16956                CppTemplateTerm::Parameter(name)
16957                    if matches!(
16958                        bindings.get(name),
16959                        Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
16960                    ) =>
16961                {
16962                    if !pack_names.contains(name) {
16963                        pack_names.push(name.clone());
16964                    }
16965                }
16966                CppTemplateTerm::Node { children, .. } => work.extend(children),
16967                CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
16968            }
16969        }
16970        let first_pack = pack_names.first()?;
16971        let CppTemplateTerm::Node {
16972            children: first_elements,
16973            ..
16974        } = bindings.get(first_pack)?
16975        else {
16976            return None;
16977        };
16978        let pack_len = first_elements.len();
16979        for pack_name in &pack_names {
16980            let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16981                return None;
16982            };
16983            if children.len() != pack_len {
16984                return None;
16985            }
16986        }
16987        for index in 0..pack_len {
16988            let mut element_bindings = bindings.clone();
16989            for pack_name in &pack_names {
16990                let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16991                    return None;
16992                };
16993                element_bindings.insert(
16994                    pack_name.clone(),
16995                    cpp_clone_template_term_iterative(&children[index]),
16996                );
16997            }
16998            substituted.push(CppTemplateExpression {
16999                text: argument.text.clone(),
17000                term: cpp_substitute_template_term(pattern, &element_bindings)?,
17001            });
17002        }
17003    }
17004    Some(substituted)
17005}
17006
17007fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
17008    enum Work<'a> {
17009        Visit(&'a CppTemplateTerm),
17010        Build { kind: String, child_count: usize },
17011    }
17012
17013    let mut work = vec![Work::Visit(term)];
17014    let mut cloned = Vec::new();
17015    while let Some(next) = work.pop() {
17016        match next {
17017            Work::Visit(CppTemplateTerm::Parameter(name)) => {
17018                cloned.push(CppTemplateTerm::Parameter(name.clone()));
17019            }
17020            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
17021                cloned.push(CppTemplateTerm::Atom {
17022                    kind: kind.clone(),
17023                    text: text.clone(),
17024                });
17025            }
17026            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
17027                work.push(Work::Build {
17028                    kind: kind.clone(),
17029                    child_count: children.len(),
17030                });
17031                work.extend(children.iter().rev().map(Work::Visit));
17032            }
17033            Work::Build { kind, child_count } => {
17034                let children = cloned.split_off(cloned.len() - child_count);
17035                cloned.push(CppTemplateTerm::Node { kind, children });
17036            }
17037        }
17038    }
17039    cloned
17040        .pop()
17041        .expect("template term traversal emits one root")
17042}
17043
17044fn cpp_clone_template_expression_iterative(
17045    expression: &CppTemplateExpression,
17046) -> CppTemplateExpression {
17047    CppTemplateExpression {
17048        text: expression.text.clone(),
17049        term: cpp_clone_template_term_iterative(&expression.term),
17050    }
17051}
17052
17053pub fn cpp_unify_template_term(
17054    pattern: &CppTemplateTerm,
17055    argument: &CppTemplateTerm,
17056    parameters: &HashSet<&str>,
17057    bindings: &mut HashMap<String, CppTemplateTerm>,
17058) -> bool {
17059    let mut work = vec![(pattern, argument)];
17060    while let Some((pattern, argument)) = work.pop() {
17061        match pattern {
17062            CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
17063                if let Some(bound) = bindings.get(name) {
17064                    if !cpp_template_terms_equal(bound, argument) {
17065                        return false;
17066                    }
17067                } else {
17068                    bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
17069                }
17070            }
17071            CppTemplateTerm::Atom {
17072                kind: pattern_kind,
17073                text: pattern_text,
17074            } => {
17075                if !matches!(
17076                    argument,
17077                    CppTemplateTerm::Atom { kind, text }
17078                        if kind == pattern_kind && text == pattern_text
17079                ) {
17080                    return false;
17081                }
17082            }
17083            CppTemplateTerm::Node {
17084                kind: pattern_kind,
17085                children: pattern_children,
17086            } => {
17087                let CppTemplateTerm::Node { kind, children } = argument else {
17088                    return false;
17089                };
17090                if kind != pattern_kind || children.len() != pattern_children.len() {
17091                    return false;
17092                }
17093                work.extend(pattern_children.iter().zip(children).rev());
17094            }
17095            CppTemplateTerm::Parameter(_) => return false,
17096        }
17097    }
17098    true
17099}
17100
17101fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
17102    let mut work = vec![(left, right)];
17103    while let Some((left, right)) = work.pop() {
17104        match (left, right) {
17105            (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
17106                if left != right {
17107                    return false;
17108                }
17109            }
17110            (
17111                CppTemplateTerm::Atom {
17112                    kind: left_kind,
17113                    text: left_text,
17114                },
17115                CppTemplateTerm::Atom {
17116                    kind: right_kind,
17117                    text: right_text,
17118                },
17119            ) => {
17120                if left_kind != right_kind || left_text != right_text {
17121                    return false;
17122                }
17123            }
17124            (
17125                CppTemplateTerm::Node {
17126                    kind: left_kind,
17127                    children: left_children,
17128                },
17129                CppTemplateTerm::Node {
17130                    kind: right_kind,
17131                    children: right_children,
17132                },
17133            ) => {
17134                if left_kind != right_kind || left_children.len() != right_children.len() {
17135                    return false;
17136                }
17137                work.extend(left_children.iter().zip(right_children).rev());
17138            }
17139            _ => return false,
17140        }
17141    }
17142    true
17143}
17144
17145pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
17146    let mut components = Vec::new();
17147    let mut stack = vec![node];
17148    while let Some(current) = stack.pop() {
17149        match current.kind() {
17150            "identifier"
17151            | "field_identifier"
17152            | "namespace_identifier"
17153            | "type_identifier"
17154            | "operator_name"
17155            | "destructor_name" => components.push(current),
17156            "template_type" | "template_function" => {
17157                stack.push(current.child_by_field_name("name")?);
17158            }
17159            "dependent_name" => stack.push(current.named_child(0)?),
17160            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
17161                stack.push(current.child_by_field_name("name")?);
17162                if let Some(scope) = current.child_by_field_name("scope") {
17163                    stack.push(scope);
17164                }
17165            }
17166            "nested_namespace_specifier" => {
17167                for index in (0..current.named_child_count()).rev() {
17168                    stack.push(current.named_child(index)?);
17169                }
17170            }
17171            _ => return None,
17172        }
17173    }
17174    Some(components)
17175}
17176
17177pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
17178    node.child_by_field_name("scope").is_none()
17179        && node.child(0).is_some_and(|child| child.kind() == "::")
17180}
17181
17182fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
17183    let mut namespaces = Vec::new();
17184    for parent in enclosing_cpp_namespace_nodes(node) {
17185        if let Some(name) = parent.child_by_field_name("name") {
17186            let mut components = Vec::new();
17187            append_cpp_name_components(name, source, &mut components)?;
17188            namespaces.push(components);
17189        }
17190    }
17191    Some(namespaces.into_iter().flatten().collect())
17192}
17193
17194/// Whether a parser-derived namespace path can be reconciled with an indexed
17195/// owner scope without inventing an unrelated short-name binding.
17196///
17197/// Macro namespace sentinels can make tree-sitter omit one or more namespace
17198/// definitions from the ancestor chain. Preserve the order of every namespace
17199/// that did survive parsing, but allow indexed components between them. An
17200/// empty path is accepted only when the declarator itself supplies a nested
17201/// owner suffix such as `Outer::Inner`: together with the indexed enclosing
17202/// owner chain, that suffix is structural evidence that a namespace was lost.
17203/// A one-segment owner at the translation-unit root remains insufficient.
17204fn indexed_namespace_path_is_recoverable(
17205    lexical_scope: &[String],
17206    indexed_owner_scope: &[String],
17207    explicit_owner_component_count: usize,
17208) -> bool {
17209    if lexical_scope.is_empty() {
17210        return explicit_owner_component_count > 1;
17211    }
17212    if lexical_scope.len() >= indexed_owner_scope.len() {
17213        return false;
17214    }
17215    super::cpp_namespace_path_is_ordered_subsequence(lexical_scope, indexed_owner_scope)
17216}
17217
17218pub use brokk_bifrost_core::analyzer::tree_walk::has_ancestor_kind;
17219
17220/// Whether a declaration type is initialized with a pointer cast.
17221///
17222/// This structured shape has an independent qualified occurrence in addition
17223/// to the cast descriptor below it. Other declarations must keep their normal
17224/// full-range occurrence only.
17225pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
17226    let mut current = Some(node);
17227    while let Some(candidate) = current {
17228        if candidate.kind() == "declaration" {
17229            if !crate::declarations::cpp_type_field_contains_node(candidate, node) {
17230                return false;
17231            }
17232            let mut cursor = candidate.walk();
17233            return candidate.named_children(&mut cursor).any(|child| {
17234                child.kind() == "init_declarator"
17235                    && child
17236                        .child_by_field_name("value")
17237                        .is_some_and(|value| value.kind() == "cast_expression")
17238            });
17239        }
17240        current = candidate.parent();
17241    }
17242    false
17243}
17244
17245#[derive(Clone, Copy, PartialEq, Eq)]
17246pub(crate) enum QualifiedAliasReferenceKind {
17247    Ordinary,
17248    ConstructorWithExpressionArgument,
17249    ExhaustiveTemplate,
17250}
17251
17252/// Whether a qualified alias reference preserves the requested target.
17253///
17254/// The complete qualified spelling and its terminal identifier are both valid
17255/// occurrences when the visible alias path is structurally proven to name the
17256/// target. Template aliases use their bound arguments; ordinary aliases use
17257/// their structured primary chain.
17258pub(crate) fn qualified_alias_reference_preserves_target(
17259    node: Node<'_>,
17260    target: &CodeUnit,
17261    analyzer: &CppGraphSource<'_>,
17262    visibility: &VisibilityIndex<'_>,
17263    file: &ProjectFile,
17264    source: &str,
17265) -> Option<QualifiedAliasReferenceKind> {
17266    if !matches!(
17267        node.kind(),
17268        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
17269    ) {
17270        return None;
17271    }
17272    let components = cpp_type_name_components(node, source)?;
17273    let name = components.last()?;
17274    analyzer.type_alias_provider().and_then(|provider| {
17275        visibility
17276            .visible_identifier_candidates(file, name)
17277            .find_map(|candidate| {
17278                let proof = provider.is_type_alias(candidate)
17279                    && canonical_cpp_scope_components(candidate) == components
17280                    && visibility.external_type_candidate_visible_in_context(
17281                        analyzer, file, candidate, node,
17282                    )
17283                    && match cpp_template_reference_arguments(node, source) {
17284                        Some(arguments) => visibility.template_alias_arguments_preserve_target(
17285                            analyzer, file, candidate, &arguments, target,
17286                        ),
17287                        None => visibility.structured_alias_primary_preserves_target(
17288                            analyzer, file, candidate, target,
17289                        ),
17290                    };
17291                proof.then(|| {
17292                    if cpp_template_reference_arguments(node, source).is_some()
17293                        && visibility.is_exhaustive_same_fqn_type_declaration_family(
17294                            analyzer, file, candidate,
17295                        )
17296                    {
17297                        QualifiedAliasReferenceKind::ExhaustiveTemplate
17298                    } else if qualified_alias_constructor_has_expression_argument(node)
17299                        || qualified_alias_local_constructor_declaration(node)
17300                    {
17301                        QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
17302                    } else {
17303                        QualifiedAliasReferenceKind::Ordinary
17304                    }
17305                })
17306            })
17307    })
17308}
17309
17310pub(crate) fn qualified_alias_reference_requires_terminal(
17311    reference: Option<QualifiedAliasReferenceKind>,
17312) -> bool {
17313    matches!(
17314        reference,
17315        Some(
17316            QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
17317                | QualifiedAliasReferenceKind::ExhaustiveTemplate
17318        )
17319    )
17320}
17321
17322fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
17323    let Some(declaration) = node.parent().filter(|parent| {
17324        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
17325    }) else {
17326        return false;
17327    };
17328    let mut cursor = declaration.walk();
17329    declaration.named_children(&mut cursor).any(|child| {
17330        child.kind() == "init_declarator"
17331            && child
17332                .child_by_field_name("value")
17333                .filter(|value| value.kind() == "argument_list")
17334                .is_some_and(|arguments| {
17335                    let mut cursor = arguments.walk();
17336                    arguments.named_children(&mut cursor).any(|argument| {
17337                        let is_parameter = matches!(
17338                            argument.kind(),
17339                            "parameter_declaration" | "optional_parameter_declaration"
17340                        );
17341                        if is_parameter {
17342                            argument
17343                                .child_by_field_name("type")
17344                                .is_some_and(|type_node| {
17345                                    type_node.kind() == "type_identifier"
17346                                        && argument.child_by_field_name("declarator").is_none()
17347                                })
17348                        } else {
17349                            !argument.kind().ends_with("_literal")
17350                                && !matches!(argument.kind(), "true" | "false" | "nullptr")
17351                        }
17352                    })
17353                })
17354    })
17355}
17356
17357/// Tree-sitter represents a local C++ direct construction such as
17358/// `Alias value(argument)` as a function declarator. Restrict that recovery to
17359/// declarations inside a compound statement so namespace-scope function
17360/// declarations with the same qualified return type stay full-range only.
17361fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
17362    let Some(declaration) = node.parent().filter(|parent| {
17363        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
17364    }) else {
17365        return false;
17366    };
17367    if declaration
17368        .parent()
17369        .is_none_or(|parent| parent.kind() != "compound_statement")
17370    {
17371        return false;
17372    }
17373    let mut cursor = declaration.walk();
17374    declaration
17375        .named_children(&mut cursor)
17376        .any(|child| child.kind() == "function_declarator")
17377}
17378
17379/// Return the terminal identifier represented by a callable or type callee.
17380///
17381/// Qualified, scoped, template, and field wrappers are traversed through their
17382/// grammar fields so both function calls and type constructions emit the token
17383/// that names the referenced declaration.
17384pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
17385    loop {
17386        let next = match node.kind() {
17387            "qualified_identifier"
17388            | "scoped_identifier"
17389            | "template_method"
17390            | "template_function"
17391            | "template_type" => node.child_by_field_name("name"),
17392            "field_expression" => node.child_by_field_name("field"),
17393            _ => None,
17394        };
17395        let Some(next) = next else {
17396            return node;
17397        };
17398        node = next;
17399    }
17400}
17401
17402#[derive(Clone, Copy)]
17403pub struct RecoveredRelationalTemplateMemberCall<'tree> {
17404    pub receiver: Node<'tree>,
17405    pub member: Node<'tree>,
17406    pub arity: usize,
17407}
17408
17409/// Recover `receiver.member<argument>(call_arguments)` when tree-sitter chose
17410/// nested relational expressions instead of a `template_method` call.
17411///
17412/// The recovery uses only grammar fields: the selected field must be the left
17413/// side of `<`, that expression must be the left side of `>`, and the right
17414/// side of `>` must be the parenthesized call arguments. Semantic callers must
17415/// additionally prove the receiver owner and the member's template status.
17416pub fn recovered_relational_template_member_call(
17417    field: Node<'_>,
17418) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
17419    if field.kind() != "field_expression" {
17420        return None;
17421    }
17422    let receiver = field
17423        .child_by_field_name("argument")
17424        .or_else(|| field.child_by_field_name("object"))?;
17425    let member = field.child_by_field_name("field")?;
17426    let less = field.parent()?;
17427    if less.kind() != "binary_expression"
17428        || less.child_by_field_name("left") != Some(field)
17429        || less
17430            .child_by_field_name("operator")
17431            .is_none_or(|operator| operator.kind() != "<")
17432        || less.child_by_field_name("right").is_none()
17433    {
17434        return None;
17435    }
17436    let greater = less.parent()?;
17437    if greater.kind() != "binary_expression"
17438        || greater.child_by_field_name("left") != Some(less)
17439        || greater
17440            .child_by_field_name("operator")
17441            .is_none_or(|operator| operator.kind() != ">")
17442    {
17443        return None;
17444    }
17445    let arguments = greater.child_by_field_name("right")?;
17446    if arguments.kind() != "parenthesized_expression" {
17447        return None;
17448    }
17449    let arity = parenthesized_call_argument_arity(arguments)?;
17450    Some(RecoveredRelationalTemplateMemberCall {
17451        receiver,
17452        member,
17453        arity,
17454    })
17455}
17456
17457fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
17458    let expression = arguments.named_child(0)?;
17459    if expression.kind() != "comma_expression" {
17460        return Some(1);
17461    }
17462    let mut arity = 0usize;
17463    let mut stack = vec![expression];
17464    while let Some(node) = stack.pop() {
17465        if node.kind() == "comma_expression" {
17466            stack.push(node.child_by_field_name("right")?);
17467            stack.push(node.child_by_field_name("left")?);
17468        } else {
17469            arity += 1;
17470        }
17471    }
17472    Some(arity)
17473}
17474
17475/// Whether `node` is part of a call's callee expression, walking only through
17476/// the grammar wrappers that can structurally contain that callee.
17477pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
17478    while let Some(parent) = node.parent() {
17479        match parent.kind() {
17480            "call_expression" => {
17481                return parent
17482                    .child_by_field_name("function")
17483                    .or_else(|| parent.named_child(0))
17484                    == Some(node);
17485            }
17486            "qualified_identifier"
17487            | "scoped_identifier"
17488            | "template_function"
17489            | "template_type"
17490            | "field_expression" => node = parent,
17491            _ => return false,
17492        }
17493    }
17494    false
17495}
17496
17497/// Expand a C++ type-name component to the complete written reference that
17498/// owns it. Qualified path wrappers that stop before a later component remain
17499/// separate, so a type used as an owner keeps its prefix range.
17500pub fn type_reference_hit_node(mut node: Node<'_>) -> Node<'_> {
17501    loop {
17502        let Some(parent) = node.parent() else {
17503            return node;
17504        };
17505        if parent.kind() == "template_type"
17506            && parent
17507                .child_by_field_name("name")
17508                .is_some_and(|name| same_node(name, node))
17509        {
17510            node = parent;
17511            continue;
17512        }
17513        if matches!(
17514            parent.kind(),
17515            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
17516        ) && (parent
17517            .child_by_field_name("scope")
17518            .is_some_and(|scope| same_node(scope, node))
17519            || parent
17520                .child_by_field_name("name")
17521                .is_some_and(|name| same_node(name, node)))
17522            && parent.end_byte() == node.end_byte()
17523        {
17524            node = parent;
17525            continue;
17526        }
17527        return node;
17528    }
17529}
17530
17531pub fn normalize_type_text(value: &str) -> String {
17532    strip_tag_type_prefix(
17533        normalize_cpp_whitespace(value)
17534            .trim_start_matches("const ")
17535            .trim_end_matches('*')
17536            .trim_end_matches('&')
17537            .trim(),
17538    )
17539    .to_string()
17540}
17541
17542use crate::declarations::strip_tag_type_prefix;
17543
17544pub fn normalize_reference_name(value: &str) -> Option<String> {
17545    let normalized = normalize_cpp_reference_text(value);
17546    (!normalized.is_empty()).then_some(normalized)
17547}
17548
17549pub fn normalize_cpp_reference_text(value: &str) -> String {
17550    let mut text = normalize_cpp_whitespace(value)
17551        .trim_start_matches("new ")
17552        .trim()
17553        .to_string();
17554    if let Some(index) = text.find(['(', '{']) {
17555        text.truncate(index);
17556    }
17557    if let Some(index) = text.find('<') {
17558        text.truncate(index);
17559    }
17560    let normalized = text
17561        .trim()
17562        .trim_start_matches("const ")
17563        .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
17564        .trim_matches(':')
17565        .trim();
17566    strip_tag_type_prefix(normalized).to_string()
17567}
17568
17569pub fn cpp_name_for(unit: &CodeUnit) -> String {
17570    let short = unit.short_name().replace(['.', '$'], "::");
17571    if unit.package_name().is_empty() {
17572        short
17573    } else {
17574        format!("{}::{}", unit.package_name(), short)
17575    }
17576}
17577
17578/// Render an indexed C++ qualified name from its authoritative FqName
17579/// segments. Unlike the legacy `cpp_name_for` renderer, this preserves dots
17580/// that belong to a template argument (for example `Args...`).
17581fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
17582    let fq = unit.fq();
17583    if fq.is_empty() {
17584        return None;
17585    }
17586    let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
17587    Some(
17588        fq.segments()
17589            .iter()
17590            .map(|&segment| interner.resolve(segment).0)
17591            .collect::<Vec<_>>()
17592            .join("::"),
17593    )
17594}
17595
17596fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
17597    canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
17598        || unit.fq().is_empty() && cpp_name_for(unit) == expected
17599}
17600
17601/// Return the indexed C++ owner scope without reparsing its rendered name.
17602///
17603/// Template spellings are opaque within an indexed `FqName` segment.  In
17604/// particular, the ellipsis in a parameter pack (`Args...`) is part of the
17605/// `AtomicHook<...>` type segment; feeding the legacy all-`::` rendering back
17606/// through `parse_symbol_path` would mistake those dots for component
17607/// separators.  Cache-loaded/legacy units may still have an empty structured
17608/// name, so retain the parser only as that explicit fallback.
17609pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
17610    let fq = unit.fq();
17611    if !fq.is_empty() {
17612        let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
17613        let scope = fq
17614            .segments()
17615            .iter()
17616            .filter_map(|&segment| {
17617                let (text, kind) = interner.resolve(segment);
17618                matches!(
17619                    kind,
17620                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
17621                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
17622                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
17623                )
17624                .then(|| text.to_string())
17625            })
17626            .collect();
17627        return scope;
17628    }
17629    brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
17630        brokk_bifrost_core::analyzer::Language::Cpp,
17631        &cpp_name_for(unit),
17632    )
17633}
17634
17635// fqname-M4: the second stage splits on the individual chars '.', '-', '>'
17636// (not the substring "->"), which deliberately reduces an `operator->`-style
17637// terminal segment to an empty tail rather than keeping it intact; the shared
17638// structured splitter's cpp operator-token merge would keep `operator->`
17639// whole instead, changing this function's result — `name_matches_callable`'s
17640// `expected.starts_with("operator")` fallback exists specifically to
17641// compensate for that reduction, and a pinned regression test
17642// (`operator-> must not be reduced with terminal_name-style punctuation
17643// splitting`) asserts today's char-class behavior. Not equivalence-provable;
17644// revisit alongside that pinned test if it is ever relaxed.
17645pub fn terminal_name(value: &str) -> &str {
17646    value
17647        .rsplit("::")
17648        .next()
17649        .unwrap_or(value)
17650        .rsplit(['.', '-', '>'])
17651        .next()
17652        .unwrap_or(value)
17653        .trim()
17654}
17655
17656pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
17657    terminal_name(&normalize_cpp_reference_text(value)) == expected
17658}
17659
17660pub fn name_matches_callable(value: &str, expected: &str) -> bool {
17661    name_matches_terminal(value, expected)
17662        || expected.starts_with("operator")
17663            && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
17664}
17665
17666pub fn name_mentions(value: &str, expected: &str) -> bool {
17667    normalize_cpp_reference_text(value)
17668        .split("::")
17669        .any(|part| part == expected)
17670}
17671
17672pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
17673    let cpp_name = cpp_name_for(unit);
17674    if reference.contains("::") {
17675        return reference == cpp_name;
17676    }
17677    reference == cpp_name
17678        || terminal_name(reference) == unit.identifier()
17679            && (unit.package_name().is_empty() || reference == unit.identifier())
17680}
17681
17682pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
17683    match kind {
17684        TargetKind::Type
17685        | TargetKind::Constructor
17686        | TargetKind::Method
17687        | TargetKind::MemberField => true,
17688        TargetKind::FreeFunction => unit.is_function(),
17689        TargetKind::GlobalField => unit.is_field(),
17690        TargetKind::Macro => unit.is_macro(),
17691    }
17692}
17693
17694fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
17695    let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
17696    let target_name = cpp_name_for(target);
17697    if normalized.contains("::") {
17698        return normalized == target_name;
17699    }
17700    if let Some(namespace) = alias.namespace.as_deref() {
17701        return namespace_prefixes(namespace)
17702            .into_iter()
17703            .any(|prefix| format!("{prefix}::{normalized}") == target_name);
17704    }
17705    target.package_name().is_empty() && normalized == target.identifier()
17706}
17707
17708/// The declared return type text of a C++ function unit, with leading declaration specifiers
17709/// stripped, e.g. `T*` for `T* operator->()`.
17710pub fn cpp_function_return_type_text(
17711    analyzer: &CppGraphSource<'_>,
17712    function: &CodeUnit,
17713) -> Option<String> {
17714    let metadata = analyzer.signature_metadata(function);
17715    let first = metadata.first()?.return_type_text()?;
17716    metadata
17717        .iter()
17718        .all(|metadata| metadata.return_type_text() == Some(first))
17719        .then(|| first.to_owned())
17720}
17721
17722pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
17723    // fqname-M4: `cpp_name_for` is a bespoke all-`::` rendering of the unit's
17724    // name (it replaces every `.`/`$` in `short_name` with `::`), which is NOT
17725    // the same string `default_parent_fq_name`/`fq().parent()` would render:
17726    // the structured `FqName`'s native cpp display deliberately keeps `.` (not
17727    // `::`) between a trailing `Package` segment and a following `Type`
17728    // segment (see `separator` in `fq_name.rs`, landed for issue #1163), so
17729    // popping the unit's own `fq()` segment would NOT reproduce this
17730    // fully-`::`-joined string. Left as a split on the locally-built
17731    // all-colon string rather than the unit's structured name.
17732    cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
17733        namespace
17734            .strip_prefix("anonymous_namespace::")
17735            .unwrap_or(namespace)
17736            .to_string()
17737    })
17738}
17739
17740/// The namespace lookup tiers, from the innermost namespace outward.
17741pub fn namespace_prefixes(namespace: &str) -> Vec<String> {
17742    // `namespace` is built by `cpp_name_for`/`cpp_namespace_for` with every
17743    // non-`::` separator already converted to `::`, so re-tokenizing it with
17744    // the shared structured splitter and progressively popping the last
17745    // component reproduces the `rsplit_once("::")` outward walk exactly (same
17746    // shape as `cpp_qualifier_lookup_tiers`'s namespace-chain walk).
17747    let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
17748        brokk_bifrost_core::analyzer::Language::Cpp,
17749        namespace,
17750    );
17751    let mut prefixes = Vec::new();
17752    while !parts.is_empty() {
17753        prefixes.push(parts.join("::"));
17754        parts.pop();
17755    }
17756    prefixes
17757}
17758
17759fn nearest_namespace_candidates(
17760    candidates: Vec<CodeUnit>,
17761    normalized: &str,
17762    lexical_namespace: Option<&str>,
17763) -> Vec<CodeUnit> {
17764    if normalized.contains("::") {
17765        return candidates;
17766    }
17767    if let Some(namespace) = lexical_namespace {
17768        for prefix in namespace_prefixes(namespace) {
17769            let scoped = candidates
17770                .iter()
17771                .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
17772                .cloned()
17773                .collect::<Vec<_>>();
17774            if !scoped.is_empty() {
17775                return scoped;
17776            }
17777        }
17778    }
17779    candidates
17780        .into_iter()
17781        .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
17782        .collect()
17783}
17784
17785pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
17786    let mut namespaces = Vec::new();
17787    for parent in enclosing_cpp_namespace_nodes(node) {
17788        if let Some(name) = parent.child_by_field_name("name") {
17789            let namespace = normalize_cpp_reference_text(node_text(name, source));
17790            if !namespace.is_empty() {
17791                namespaces.push(namespace);
17792            }
17793        }
17794    }
17795    if namespaces.is_empty() {
17796        None
17797    } else {
17798        Some(namespaces.join("::"))
17799    }
17800}
17801
17802/// Like [`precise_parent_of`], but drops module (namespace) parents. A namespace is a scope, not a
17803/// type or receiver, so namespace-scoped functions and constants resolve as free functions and
17804/// globals rather than members.
17805pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
17806    type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
17807}
17808
17809fn type_owner_resolution(
17810    analyzer: &CppGraphSource<'_>,
17811    code_unit: &CodeUnit,
17812) -> Option<ResolvedTypeOwner> {
17813    precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
17814}
17815
17816fn target_type_owner_resolution(
17817    analyzer: &CppGraphSource<'_>,
17818    code_unit: &CodeUnit,
17819) -> Option<ResolvedTypeOwner> {
17820    match type_owner_resolution(analyzer, code_unit) {
17821        Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
17822        Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
17823    }
17824}
17825
17826/// Recover method identity for an indexed out-of-line definition when the
17827/// ordinary parent edge is absent. Prefer the unique include-visible forward
17828/// declaration, then classify exact-FQN class declarations elsewhere in the
17829/// workspace. A unique complete declaration wins; otherwise multiple forward
17830/// declarations are one owner only when they all share one logical identity.
17831/// The qualified callable FQN proves that owner spelling even when its defining
17832/// header is outside the scan file's include closure, while unknown or competing
17833/// complete declarations remain ambiguous.
17834/// This is deliberately target-only: canonical declaration resolution must
17835/// continue to prefer the callable definition rather than replacing it with
17836/// the recovered owner.
17837fn target_forward_owner_resolution(
17838    analyzer: &CppGraphSource<'_>,
17839    code_unit: &CodeUnit,
17840) -> Option<ResolvedTypeOwner> {
17841    if !code_unit.is_function() {
17842        return None;
17843    }
17844    // A top-level free function has no owner at all, and `FqName::parent`
17845    // answers the empty name rather than `None` for a one-segment identity.
17846    // `default_parent_fq_name`, which this replaced, filtered that case out;
17847    // asking the relational store for the empty name is a batch error that
17848    // fails the whole target frontier.
17849    let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
17850    let cpp = analyzer.cpp?;
17851    let mut visible_files = HashSet::default();
17852    collect_include_closure(
17853        analyzer,
17854        cpp.include_target_index(),
17855        code_unit.source(),
17856        &mut visible_files,
17857        None,
17858    );
17859    let candidates = analyzer.workspace_definitions().exact(&owner_name);
17860    let visible_candidates = candidates
17861        .iter()
17862        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
17863        .cloned()
17864        .collect::<Vec<_>>();
17865    match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
17866        DirectOwnerResolution::UniqueFull(unit) => {
17867            return Some(ResolvedTypeOwner {
17868                unit,
17869                is_forward_declaration: false,
17870            });
17871        }
17872        DirectOwnerResolution::ForwardsOnly(forwards) => {
17873            return (forwards.len() == 1).then(|| ResolvedTypeOwner {
17874                unit: forwards.into_iter().next().unwrap(),
17875                is_forward_declaration: true,
17876            });
17877        }
17878        DirectOwnerResolution::Ambiguous => return None,
17879        DirectOwnerResolution::None => {}
17880    }
17881
17882    let candidates = candidates
17883        .into_iter()
17884        .filter(|candidate| candidate.is_class())
17885        .collect::<Vec<_>>();
17886    let (unit, is_forward_declaration) =
17887        match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
17888            DirectOwnerResolution::UniqueFull(unit) => (unit, false),
17889            DirectOwnerResolution::ForwardsOnly(forwards) => {
17890                (unique_logical_forward_owner(forwards)?, true)
17891            }
17892            DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
17893        };
17894    Some(ResolvedTypeOwner {
17895        unit,
17896        is_forward_declaration,
17897    })
17898}
17899
17900pub fn precise_parent_of(
17901    analyzer: &CppGraphSource<'_>,
17902    visibility: &VisibilityIndex<'_>,
17903    code_unit: &CodeUnit,
17904) -> Option<CodeUnit> {
17905    visibility.cached_precise_parent_of(analyzer, code_unit)
17906}
17907
17908fn precise_parent_resolution(
17909    analyzer: &CppGraphSource<'_>,
17910    code_unit: &CodeUnit,
17911) -> Option<ResolvedTypeOwner> {
17912    #[cfg(any(test, feature = "test-support"))]
17913    if let Some(cpp) = analyzer.cpp {
17914        cpp.record_cpp_parent_resolution_for_test();
17915    }
17916    if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
17917        return Some(ResolvedTypeOwner {
17918            unit,
17919            is_forward_declaration: false,
17920        });
17921    }
17922    let fallback = analyzer.parent_of(code_unit);
17923    if !code_unit.owner_is_type_scope() {
17924        return fallback.map(|unit| ResolvedTypeOwner {
17925            unit,
17926            is_forward_declaration: false,
17927        });
17928    }
17929    let owner_fq = code_unit
17930        .fq()
17931        .parent()
17932        .expect("a unit with an owner identifier has a structured parent");
17933    let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
17934    match same_source_owner(analyzer, code_unit, &owner_candidates) {
17935        DirectOwnerResolution::UniqueFull(owner) => {
17936            return Some(ResolvedTypeOwner {
17937                unit: owner,
17938                is_forward_declaration: false,
17939            });
17940        }
17941        DirectOwnerResolution::Ambiguous => return None,
17942        DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
17943    }
17944    match directly_included_owner(analyzer, code_unit, &owner_candidates) {
17945        DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
17946            unit: owner,
17947            is_forward_declaration: false,
17948        }),
17949        DirectOwnerResolution::Ambiguous => None,
17950        DirectOwnerResolution::ForwardsOnly(forwards) => {
17951            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17952                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17953                    unit: owner,
17954                    is_forward_declaration: false,
17955                }),
17956                FullOwnerResolution::None => {
17957                    unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
17958                        unit,
17959                        is_forward_declaration: true,
17960                    })
17961                }
17962                FullOwnerResolution::Ambiguous => None,
17963            }
17964        }
17965        DirectOwnerResolution::None => {
17966            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17967                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17968                    unit: owner,
17969                    is_forward_declaration: false,
17970                }),
17971                FullOwnerResolution::Ambiguous => None,
17972                FullOwnerResolution::None => fallback
17973                    .filter(|parent| {
17974                        parent.source() == code_unit.source()
17975                            && parent.fq() == &owner_fq
17976                            && (!parent.is_class()
17977                                || cpp_class_declaration_strength(analyzer, parent)
17978                                    == CppClassDeclarationStrength::Full)
17979                    })
17980                    .map(|unit| ResolvedTypeOwner {
17981                        unit,
17982                        is_forward_declaration: false,
17983                    }),
17984            }
17985        }
17986    }
17987}
17988
17989fn exact_structural_type_parent(
17990    analyzer: &CppGraphSource<'_>,
17991    code_unit: &CodeUnit,
17992) -> Option<CodeUnit> {
17993    if !code_unit.is_function() && !code_unit.is_field() {
17994        return None;
17995    }
17996    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
17997    let cpp = analyzer.cpp?;
17998    let parent = cpp.structural_parent_of(code_unit)?;
17999    (!parent.is_module()
18000        && parent.source() == code_unit.source()
18001        && parent.package_name() == code_unit.package_name()
18002        && parent.short_name() == encoded_owner)
18003        .then_some(parent)
18004}
18005
18006fn same_source_owner(
18007    analyzer: &CppGraphSource<'_>,
18008    code_unit: &CodeUnit,
18009    owner_candidates: &[CodeUnit],
18010) -> DirectOwnerResolution {
18011    let candidates = owner_candidates
18012        .iter()
18013        .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
18014        .cloned()
18015        .collect::<Vec<_>>();
18016    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
18017    classify_direct_owner_candidates(analyzer, candidates.into_iter())
18018}
18019
18020fn visible_full_cpp_owner(
18021    analyzer: &CppGraphSource<'_>,
18022    code_unit: &CodeUnit,
18023    owner_candidates: &[CodeUnit],
18024) -> FullOwnerResolution {
18025    let Some(cpp) = analyzer.cpp else {
18026        return FullOwnerResolution::None;
18027    };
18028    let mut visible_files = HashSet::default();
18029    collect_include_closure(
18030        analyzer,
18031        cpp.include_target_index(),
18032        code_unit.source(),
18033        &mut visible_files,
18034        None,
18035    );
18036    let candidates = owner_candidates
18037        .iter()
18038        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
18039        .cloned()
18040        .collect::<Vec<_>>();
18041    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
18042    match collapse_owner_candidates(candidates.into_iter().map(|candidate| {
18043        let strength = cpp_class_declaration_strength(analyzer, &candidate);
18044        (candidate, strength)
18045    })) {
18046        DirectOwnerResolution::UniqueFull(owner) => FullOwnerResolution::Unique(owner),
18047        DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {
18048            FullOwnerResolution::None
18049        }
18050        DirectOwnerResolution::Ambiguous => FullOwnerResolution::Ambiguous,
18051    }
18052}
18053
18054pub enum DirectOwnerResolution {
18055    None,
18056    ForwardsOnly(Vec<CodeUnit>),
18057    UniqueFull(CodeUnit),
18058    Ambiguous,
18059}
18060
18061enum FullOwnerResolution {
18062    None,
18063    Unique(CodeUnit),
18064    Ambiguous,
18065}
18066
18067fn directly_included_owner(
18068    analyzer: &CppGraphSource<'_>,
18069    code_unit: &CodeUnit,
18070    owner_candidates: &[CodeUnit],
18071) -> DirectOwnerResolution {
18072    let Some(cpp) = analyzer.cpp else {
18073        return DirectOwnerResolution::None;
18074    };
18075    let direct_includes: HashSet<ProjectFile> = analyzer
18076        .canonical_include_paths(code_unit.source())
18077        .unwrap_or_default()
18078        .into_iter()
18079        .flat_map(|include| {
18080            resolve_include_targets_with_index(
18081                code_unit.source(),
18082                &include,
18083                cpp.include_target_index(),
18084            )
18085        })
18086        .collect();
18087    let candidates = owner_candidates
18088        .iter()
18089        .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
18090        .cloned()
18091        .collect::<Vec<_>>();
18092    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
18093    classify_direct_owner_candidates(analyzer, candidates.into_iter())
18094}
18095
18096fn prefer_member_declaring_owners(
18097    analyzer: &CppGraphSource<'_>,
18098    member: &CodeUnit,
18099    candidates: Vec<CodeUnit>,
18100) -> Vec<CodeUnit> {
18101    let matching = candidates
18102        .iter()
18103        .filter(|owner| owner_declares_member(analyzer, owner, member))
18104        .cloned()
18105        .collect::<Vec<_>>();
18106    if matching.is_empty() {
18107        candidates
18108    } else {
18109        matching
18110    }
18111}
18112
18113fn owner_declares_member(
18114    analyzer: &CppGraphSource<'_>,
18115    owner: &CodeUnit,
18116    member: &CodeUnit,
18117) -> bool {
18118    analyzer.direct_children(owner).into_iter().any(|child| {
18119        child.kind() == member.kind()
18120            && child.identifier() == member.identifier()
18121            && child.signature() == member.signature()
18122    })
18123}
18124
18125fn classify_direct_owner_candidates(
18126    analyzer: &CppGraphSource<'_>,
18127    candidates: impl Iterator<Item = CodeUnit>,
18128) -> DirectOwnerResolution {
18129    collapse_owner_candidates(candidates.map(|candidate| {
18130        let strength = cpp_class_declaration_strength(analyzer, &candidate);
18131        (candidate, strength)
18132    }))
18133}
18134
18135pub fn collapse_owner_candidates(
18136    candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
18137) -> DirectOwnerResolution {
18138    let mut full_definition = None;
18139    let mut forwards = Vec::new();
18140    for (candidate, strength) in candidates {
18141        match strength {
18142            CppClassDeclarationStrength::Full if full_definition.is_some() => {
18143                return DirectOwnerResolution::Ambiguous;
18144            }
18145            CppClassDeclarationStrength::Full => full_definition = Some(candidate),
18146            CppClassDeclarationStrength::Forward => forwards.push(candidate),
18147            CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
18148        }
18149    }
18150    if let Some(owner) = full_definition {
18151        DirectOwnerResolution::UniqueFull(owner)
18152    } else if !forwards.is_empty() {
18153        DirectOwnerResolution::ForwardsOnly(forwards)
18154    } else {
18155        DirectOwnerResolution::None
18156    }
18157}
18158
18159#[cfg(any(test, feature = "test-support"))]
18160pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
18161    unique_logical_forward_owner(forwards)
18162}
18163
18164fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
18165    let first = forwards.pop()?;
18166    forwards
18167        .iter()
18168        .all(|forward| same_logical_symbol(forward, &first))
18169        .then_some(first)
18170}
18171
18172pub fn cpp_class_declaration_strength(
18173    analyzer: &CppGraphSource<'_>,
18174    candidate: &CodeUnit,
18175) -> CppClassDeclarationStrength {
18176    // The answer is a pure function of the unit's ranges and its file's tree,
18177    // and the inverse scan asks it once per declaration seed. On a translation
18178    // unit the parser could not fully recover, each ask re-derives the
18179    // export-macro recovery shapes from the file's `ERROR` subtrees, so without
18180    // this memo one file's scan is quadratic in its own size: 97% of Catch2's
18181    // 284 s inverse scan of `extras/catch_amalgamated.cpp` was in this call
18182    // (#1496).
18183    let Some(cpp) = analyzer.cpp else {
18184        return uncached_cpp_class_declaration_strength(analyzer, candidate);
18185    };
18186    if let Some(strength) = cpp.cached_class_declaration_strength(candidate) {
18187        return strength;
18188    }
18189    let strength = uncached_cpp_class_declaration_strength(analyzer, candidate);
18190    cpp.cache_class_declaration_strength(candidate, strength);
18191    strength
18192}
18193
18194fn uncached_cpp_class_declaration_strength(
18195    analyzer: &CppGraphSource<'_>,
18196    candidate: &CodeUnit,
18197) -> CppClassDeclarationStrength {
18198    if let Some(facts) = analyzer.declaration_source_properties(candidate) {
18199        if facts
18200            .iter()
18201            .any(|fact| fact.class_strength == CppClassDeclarationStrength::Full)
18202        {
18203            return CppClassDeclarationStrength::Full;
18204        }
18205        if facts
18206            .iter()
18207            .any(|fact| fact.class_strength == CppClassDeclarationStrength::Forward)
18208        {
18209            return CppClassDeclarationStrength::Forward;
18210        }
18211    }
18212    if let Some(cpp) = analyzer.cpp
18213        && let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source())
18214    {
18215        return cpp_class_declaration_strength_in_tree(
18216            analyzer,
18217            &cpp.recovered_export_class_index(analyzer.token, candidate.source()),
18218            candidate,
18219            prepared.source(),
18220            prepared.tree().root_node(),
18221        );
18222    }
18223    let Some(source) = analyzer.indexed_source(candidate.source()) else {
18224        return CppClassDeclarationStrength::Unknown;
18225    };
18226    let mut parser = Parser::new();
18227    if parser
18228        .set_language(&tree_sitter_cpp::LANGUAGE.into())
18229        .is_err()
18230    {
18231        return CppClassDeclarationStrength::Unknown;
18232    }
18233    let Some(tree) = parser.parse(&source, None) else {
18234        return CppClassDeclarationStrength::Unknown;
18235    };
18236    // This branch reparses a file the analyzer has no prepared tree for, so its
18237    // recovery index is that one tree's and cannot be shared.
18238    let recovered_export_classes =
18239        CppRecoveredExportClassIndex::build(tree.root_node(), source.as_str());
18240    cpp_class_declaration_strength_in_tree(
18241        analyzer,
18242        &recovered_export_classes,
18243        candidate,
18244        &source,
18245        tree.root_node(),
18246    )
18247}
18248
18249fn cpp_class_declaration_strength_in_tree(
18250    analyzer: &CppGraphSource<'_>,
18251    recovered_export_classes: &CppRecoveredExportClassIndex,
18252    candidate: &CodeUnit,
18253    source: &str,
18254    root: Node<'_>,
18255) -> CppClassDeclarationStrength {
18256    let ranges = analyzer.ranges(candidate);
18257    let mut saw_forward = false;
18258    for range in ranges {
18259        // The recovered export-macro shapes answer for their own ranges; only a
18260        // range no recovery claims is read as a plain specifier.
18261        match recovered_class_body_at(
18262            recovered_export_classes,
18263            root,
18264            source,
18265            candidate.identifier(),
18266            &range,
18267        ) {
18268            Some(true) => return CppClassDeclarationStrength::Full,
18269            Some(false) => {
18270                saw_forward = true;
18271                continue;
18272            }
18273            None => {}
18274        }
18275        // Only a node covering the range's start byte can be the specifier for
18276        // this range, so apply that test where nodes enter the stack rather
18277        // than where they leave it. Pushing first meant one ask enqueued every
18278        // sibling at every level it descended, which on a translation unit with
18279        // thousands of top-level declarations is a per-ask cost proportional to
18280        // the file (#1496).
18281        let covers_range_start = |node: &Node<'_>| {
18282            node.start_byte() <= range.start_byte && node.end_byte() >= range.start_byte
18283        };
18284        let mut stack = Vec::new();
18285        if covers_range_start(&root) {
18286            stack.push(root);
18287        }
18288        while let Some(node) = stack.pop() {
18289            if node.start_byte() == range.start_byte
18290                && node.end_byte() == range.end_byte
18291                && matches!(
18292                    node.kind(),
18293                    "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
18294                )
18295            {
18296                if cpp_class_node_has_body(node) {
18297                    return CppClassDeclarationStrength::Full;
18298                }
18299                saw_forward = true;
18300            }
18301            let mut cursor = node.walk();
18302            stack.extend(node.named_children(&mut cursor).filter(covers_range_start));
18303        }
18304    }
18305    if saw_forward {
18306        CppClassDeclarationStrength::Forward
18307    } else {
18308        CppClassDeclarationStrength::Unknown
18309    }
18310}
18311
18312fn cpp_class_node_has_body(node: Node<'_>) -> bool {
18313    child_by_field_or_named_child_of_kinds(
18314        node,
18315        "body",
18316        &[
18317            "declaration_list",
18318            "field_declaration_list",
18319            "enumerator_list",
18320        ],
18321    )
18322    .is_some()
18323}
18324
18325#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18326enum CppCTagKind {
18327    Struct,
18328    Union,
18329}
18330
18331fn indexed_c_tag_kind(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CppCTagKind> {
18332    let declaration = analyzer.get_source(code_unit, false)?;
18333    let mut parser = Parser::new();
18334    parser
18335        .set_language(&tree_sitter_cpp::LANGUAGE.into())
18336        .ok()?;
18337    let tree = parser.parse(&declaration, None)?;
18338    let mut stack = vec![tree.root_node()];
18339    while let Some(node) = stack.pop() {
18340        let kind = match node.kind() {
18341            "struct_specifier" => CppCTagKind::Struct,
18342            "union_specifier" => CppCTagKind::Union,
18343            _ => {
18344                let mut cursor = node.walk();
18345                stack.extend(node.named_children(&mut cursor));
18346                continue;
18347            }
18348        };
18349        if node
18350            .child_by_field_name("name")
18351            .is_some_and(|name| node_text(name, &declaration) == code_unit.identifier())
18352        {
18353            return Some(kind);
18354        }
18355        let mut cursor = node.walk();
18356        stack.extend(node.named_children(&mut cursor));
18357    }
18358    None
18359}
18360
18361pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
18362    if !code_unit.owner_is_type_scope() {
18363        return None;
18364    }
18365    let owner_fq = code_unit.fq().parent()?;
18366    ctx.analyzer
18367        .workspace_definitions()
18368        .exact(&owner_fq)
18369        .into_iter()
18370        .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
18371}
18372
18373pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18374    same_logical_symbol(left, right) && left.source() == right.source()
18375}
18376
18377pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18378    same_symbol(left, right) || same_logical_symbol(left, right)
18379}
18380
18381pub fn same_visible_global_field_symbol(
18382    analyzer: &CppGraphSource<'_>,
18383    internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
18384    left: &CodeUnit,
18385    right: &CodeUnit,
18386) -> bool {
18387    if same_symbol(left, right) {
18388        return true;
18389    }
18390    if !same_logical_symbol(left, right) {
18391        return false;
18392    }
18393    if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
18394        || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
18395    {
18396        left.source() == right.source()
18397    } else {
18398        true
18399    }
18400}
18401
18402fn cpp_global_field_has_internal_linkage_cached(
18403    analyzer: &CppGraphSource<'_>,
18404    cache: &mut HashMap<CodeUnit, bool>,
18405    candidate: &CodeUnit,
18406) -> bool {
18407    if let Some(internal) = cache.get(candidate) {
18408        return *internal;
18409    }
18410    #[cfg(any(test, feature = "test-support"))]
18411    note_cpp_global_field_internal_linkage_classification_for_test();
18412    let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
18413    cache.insert(candidate.clone(), internal);
18414    internal
18415}
18416
18417#[cfg(any(test, feature = "test-support"))]
18418thread_local! {
18419    static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
18420}
18421
18422#[cfg(any(test, feature = "test-support"))]
18423fn note_cpp_global_field_internal_linkage_classification_for_test() {
18424    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
18425        count.set(count.get() + 1);
18426    });
18427}
18428
18429#[cfg(any(test, feature = "test-support"))]
18430pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
18431    body: impl FnOnce() -> T,
18432) -> (T, usize) {
18433    with_thread_local_counter_for_test(
18434        &CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST,
18435        body,
18436    )
18437}
18438
18439#[cfg(any(test, feature = "test-support"))]
18440fn with_thread_local_counter_for_test<T>(
18441    counter: &'static std::thread::LocalKey<Cell<usize>>,
18442    body: impl FnOnce() -> T,
18443) -> (T, usize) {
18444    counter.with(|count| {
18445        count.set(0);
18446        let result = body();
18447        let observed = count.get();
18448        count.set(0);
18449        (result, observed)
18450    })
18451}
18452
18453pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18454    left.kind() == right.kind()
18455        && left.fq_name() == right.fq_name()
18456        && left.signature() == right.signature()
18457}
18458
18459/// Select one visible spelling from an alias family only when its members
18460/// agree on one structured target. A single candidate needs no target proof.
18461pub fn select_alias_spelling_with_agreed_targets<'a, T: Eq>(
18462    candidates: &[&'a CodeUnit],
18463    mut structured_target: impl FnMut(&CodeUnit) -> Option<T>,
18464) -> Option<&'a CodeUnit> {
18465    let first = *candidates.first()?;
18466    if candidates.len() == 1 {
18467        return Some(first);
18468    }
18469    let first_target = structured_target(first)?;
18470    candidates
18471        .iter()
18472        .skip(1)
18473        .all(|candidate| structured_target(candidate).is_some_and(|target| target == first_target))
18474        .then_some(first)
18475}
18476
18477pub fn cpp_global_field_has_internal_linkage(
18478    analyzer: &CppGraphSource<'_>,
18479    candidate: &CodeUnit,
18480) -> bool {
18481    if !candidate.is_field() || candidate.short_name().contains('.') {
18482        return false;
18483    }
18484    let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
18485        return false;
18486    };
18487    match local_linkage {
18488        CppFieldLinkage::Internal => true,
18489        CppFieldLinkage::External => false,
18490        CppFieldLinkage::InternalUnlessExternalPeer => {
18491            !cpp_global_field_linkage_peers(analyzer, candidate)
18492                .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
18493                .any(|linkage| matches!(linkage, CppFieldLinkage::External))
18494        }
18495    }
18496}
18497
18498fn cpp_global_field_linkage_peers<'a>(
18499    analyzer: &CppGraphSource<'a>,
18500    candidate: &'a CodeUnit,
18501) -> impl Iterator<Item = CodeUnit> + 'a {
18502    let name = candidate.fq().clone();
18503    analyzer
18504        .workspace_definitions()
18505        .exact(&name)
18506        .into_iter()
18507        .filter(move |peer| {
18508            if peer == candidate {
18509                return false;
18510            }
18511            #[cfg(any(test, feature = "test-support"))]
18512            note_cpp_global_field_linkage_peer_inspection_for_test();
18513            same_logical_symbol(peer, candidate)
18514        })
18515}
18516
18517#[cfg(any(test, feature = "test-support"))]
18518thread_local! {
18519    static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
18520}
18521
18522#[cfg(any(test, feature = "test-support"))]
18523fn note_cpp_global_field_linkage_peer_inspection_for_test() {
18524    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
18525        count.set(count.get() + 1);
18526    });
18527}
18528
18529#[cfg(any(test, feature = "test-support"))]
18530pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
18531    body: impl FnOnce() -> T,
18532) -> (T, usize) {
18533    with_thread_local_counter_for_test(&CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST, body)
18534}
18535
18536fn cpp_global_field_declaration_linkage(
18537    analyzer: &CppGraphSource<'_>,
18538    candidate: &CodeUnit,
18539) -> Option<CppFieldLinkage> {
18540    analyzer.cpp_field_linkage(candidate)
18541}
18542
18543/// Agreed alias target spelling captured from the declaring AST type field.
18544pub fn cpp_canonical_alias_target_text(
18545    analyzer: &CppGraphSource<'_>,
18546    unit: &CodeUnit,
18547) -> Option<String> {
18548    let facts = analyzer.declaration_source_properties(unit)?;
18549    let first = facts.first()?.alias_target_text.as_ref()?;
18550    facts
18551        .iter()
18552        .all(|fact| fact.alias_target_text.as_ref() == Some(first))
18553        .then(|| first.clone())
18554}
18555
18556#[cfg(test)]
18557mod tests {
18558    #[test]
18559    #[hardwon("05fbd05c6bd7: C++ declaration recovery regressed for this structured syntax edge")]
18560    fn issue_3089_statement_formal_at_end_of_replacement() {
18561        let parameters = vec!["handle".to_owned(), "block".to_owned()];
18562        for replacement in [
18563            "do { header_event_t* event; if ((handle)->active) block } while (0)",
18564            "do { header_event_t* event; block } while (0)",
18565        ] {
18566            assert!(
18567                super::VisibilityIndex::parse_macro_replacement_body(replacement, &parameters)
18568                    .is_some(),
18569                "{replacement}"
18570            );
18571        }
18572    }
18573    use super::*;
18574    use brokk_bifrost_test_macros::hardwon;
18575
18576    #[test]
18577    #[hardwon("2e5169325a14: C++ declaration recovery regressed for this structured syntax edge")]
18578    fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
18579        let source = "int size(void) { return sizeof(((Payload))); }\n";
18580        let mut parser = Parser::new();
18581        parser
18582            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18583            .expect("C++ grammar");
18584        let tree = parser.parse(source, None).expect("fixture tree");
18585        let start = source.find("Payload").expect("sizeof operand");
18586        let node = tree
18587            .root_node()
18588            .named_descendant_for_byte_range(start, start + "Payload".len())
18589            .expect("focused operand");
18590        let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
18591        let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
18592
18593        assert_eq!(node.kind(), "identifier");
18594        assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
18595        assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
18596    }
18597
18598    fn parse_cpp(source: &str) -> Tree {
18599        crate::diagnostics::parse_cpp_tree(source)
18600    }
18601
18602    #[test]
18603    #[hardwon("#4271: source splitting included a new-array declarator in the allocation type")]
18604    fn cpp_new_expression_type_comes_from_the_structured_type_field() {
18605        let source = "auto value = new  ns::Box<int> [ 3 ];";
18606        let tree = parse_cpp(source);
18607        let mut pending = vec![tree.root_node()];
18608        let allocation = loop {
18609            let node = pending.pop().expect("new expression in fixture");
18610            if node.kind() == "new_expression" {
18611                break node;
18612            }
18613            let mut cursor = node.walk();
18614            pending.extend(
18615                node.named_children(&mut cursor)
18616                    .collect::<Vec<_>>()
18617                    .into_iter()
18618                    .rev(),
18619            );
18620        };
18621
18622        let type_node = cpp_new_expression_type_node(allocation).expect("structured type field");
18623        assert_eq!(allocation.child_by_field_name("type"), Some(type_node));
18624        assert_eq!(node_text(type_node, source), "ns::Box<int>");
18625        assert_eq!(
18626            allocation
18627                .child_by_field_name("declarator")
18628                .expect("array declarator")
18629                .kind(),
18630            "new_declarator"
18631        );
18632
18633        let old_split = normalize_cpp_whitespace(node_text(allocation, source));
18634        let old_split = old_split
18635            .strip_prefix("new ")
18636            .unwrap_or(old_split.as_str())
18637            .split(['(', '{'])
18638            .next()
18639            .expect("old split result");
18640        assert_ne!(old_split.trim(), node_text(type_node, source));
18641    }
18642
18643    fn named_node_at<'tree>(tree: &'tree Tree, source: &str, needle: &str) -> Node<'tree> {
18644        let start = source.find(needle).expect("fixture needle");
18645        tree.root_node()
18646            .named_descendant_for_byte_range(start, start + needle.len())
18647            .expect("node at needle")
18648    }
18649
18650    fn prepared_cpp(source: &str) -> PreparedSyntaxTree {
18651        let tree = crate::diagnostics::parse_cpp_tree(source);
18652        PreparedSyntaxTree::new(
18653            PreparedSyntaxSource::Exact(Arc::from(source)),
18654            tree,
18655            compute_line_starts(source),
18656            LanguageDialect::Standard(Language::Cpp),
18657            PreparedSourceOrigin::Disk,
18658            None,
18659        )
18660    }
18661
18662    fn unresolved_include_before(source: &str, reference: &str) -> bool {
18663        let file = ProjectFile::new(std::env::temp_dir(), "issue-3078.cpp");
18664        let prepared = prepared_cpp(source);
18665        let facts = collect_structured_include_facts(&prepared);
18666        let include_targets = IncludeTargetIndex::build([&file]);
18667        has_unresolved_include_visible_before_in_prepared(
18668            &file,
18669            &prepared,
18670            &include_targets,
18671            &facts,
18672            source.find(reference).expect("reference fixture"),
18673        )
18674    }
18675
18676    #[test]
18677    #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
18678    fn unresolved_include_before_reference_is_visible() {
18679        let source = "#include \"missing.h\"\nint use = Missing;\n";
18680        assert!(unresolved_include_before(source, "Missing"));
18681    }
18682
18683    #[test]
18684    #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
18685    fn unresolved_include_after_reference_is_not_visible() {
18686        let source = "int use = Missing;\n#include \"missing.h\"\n";
18687        assert!(!unresolved_include_before(source, "Missing"));
18688    }
18689
18690    #[test]
18691    #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
18692    fn unresolved_include_in_incompatible_sibling_branch_is_not_visible() {
18693        let source = "#if FEATURE\n#include \"missing.h\"\n#else\nint use = Missing;\n#endif\n";
18694        assert!(!unresolved_include_before(source, "Missing"));
18695    }
18696
18697    #[test]
18698    #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
18699    fn unresolved_include_in_current_branch_is_visible() {
18700        let source =
18701            "#if FEATURE\n#include \"missing.h\"\nint use = Missing;\n#else\nint other;\n#endif\n";
18702        assert!(unresolved_include_before(source, "Missing"));
18703    }
18704
18705    /// Two macro-decorated class heads make tree-sitter close `detail` at the
18706    /// first class's `}`, `matchers` at the second's, and `app` at `detail`'s
18707    /// real `}`; the tail parses at translation-unit level and the two real
18708    /// closes for `matchers` and `app` land in a trailing ERROR (#1537).
18709    const STOLEN_BRACE_CASCADE: &str = r#"namespace app {
18710namespace matchers {
18711    namespace detail {
18712        class API [[nodiscard]] First {
18713        public:
18714            int value() const { return count_ + 1; }
18715        private:
18716            int count_;
18717        };
18718        class API [[nodiscard]] Second {
18719        public:
18720            int value() const { return count_ + 2; }
18721        private:
18722            int count_;
18723        };
18724    } // namespace detail
18725
18726    template <typename T>
18727    void tail_function(MatcherBase<T> const& value);
18728
18729    class TailClass {};
18730} // namespace matchers
18731} // namespace app
18732
18733struct AfterAll {};
18734"#;
18735
18736    #[test]
18737    #[hardwon("c820d8c6a8e3: C++ declaration recovery regressed for this structured syntax edge")]
18738    fn orphaned_namespace_scope_index_restores_a_stolen_brace_cascade() {
18739        let source = STOLEN_BRACE_CASCADE;
18740        let tree = parse_cpp(source);
18741        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
18742
18743        let tail_class = named_node_at(&tree, source, "TailClass");
18744        assert!(
18745            !has_ancestor_kind(tail_class, "namespace_definition"),
18746            "the fixture must reproduce the recovery: the tail has no namespace ancestor"
18747        );
18748        let displaced = named_node_at(&tree, source, "Second");
18749        assert_eq!(
18750            enclosing_namespace_components(displaced, source),
18751            Some(vec!["app".to_string(), "matchers".to_string()]),
18752            "the fixture must displace the second class out of detail"
18753        );
18754
18755        let components = |needle: &str| {
18756            index.enclosing_namespace_components(named_node_at(&tree, source, needle), source)
18757        };
18758        assert_eq!(components("First"), ["app", "matchers", "detail"]);
18759        assert_eq!(components("Second"), ["app", "matchers", "detail"]);
18760        assert_eq!(components("MatcherBase<T>"), ["app", "matchers"]);
18761        assert_eq!(components("tail_function"), ["app", "matchers"]);
18762        assert_eq!(components("TailClass"), ["app", "matchers"]);
18763        assert!(components("AfterAll").is_empty());
18764    }
18765
18766    #[test]
18767    #[hardwon("c820d8c6a8e3: C++ declaration recovery regressed for this structured syntax edge")]
18768    fn orphaned_namespace_scope_index_is_empty_without_lost_scopes() {
18769        let clean = "namespace a { namespace b { class C {}; } class D {}; }\n";
18770        let tree = parse_cpp(clean);
18771        assert!(!tree.root_node().has_error());
18772        assert!(OrphanedNamespaceScopeIndex::build(tree.root_node(), clean).is_empty());
18773
18774        // A namespace that merely contains a parse error closes where its
18775        // brace says; the declarations after it keep their parsed scope.
18776        let damaged = "namespace a { namespace b { UNKNOWN_MACRO(x) } class C {}; }\n";
18777        let tree = parse_cpp(damaged);
18778        assert!(tree.root_node().has_error());
18779        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), damaged);
18780        assert_eq!(
18781            index.enclosing_namespace_components(named_node_at(&tree, damaged, "class C"), damaged),
18782            ["a"]
18783        );
18784    }
18785
18786    /// A function-like `#define` in a class body, whose replacement returns a
18787    /// SFINAE type, makes tree-sitter give up on the whole enclosing namespace:
18788    /// `namespace app {` becomes an `ERROR` whose own `namespace`, name and `{`
18789    /// tokens are the only trace of the head, and every declaration the
18790    /// namespace holds becomes their flat sibling. This is Catch2's
18791    /// `catch_decomposer.hpp`, reduced (issue #3084).
18792    const COLLAPSED_NAMESPACE_HEAD: &str = r#"namespace app {
18793
18794    class Target {
18795        int value_;
18796    };
18797
18798    template <typename T>
18799    class Holder {
18800    public:
18801        explicit constexpr Holder( T lhs ): m_lhs( lhs ) {}
18802
18803#define HOLDER_DEFINE_OP( id, op )                                             \
18804    template <typename U>                                                      \
18805    constexpr friend auto operator op( Holder&& lhs, U&& rhs )                 \
18806        -> std::enable_if_t<is_##id##_comparable<T, U>::value, Target> {       \
18807        return Target{};                                                       \
18808    }
18809
18810        HOLDER_DEFINE_OP( equal, == )
18811#undef HOLDER_DEFINE_OP
18812        T m_lhs;
18813    };
18814
18815    class Tail {};
18816}
18817"#;
18818
18819    #[test]
18820    #[hardwon("a438f125e172: C++ declaration recovery regressed for this structured syntax edge")]
18821    fn orphaned_namespace_scope_index_names_a_collapsed_namespace_head() {
18822        let source = COLLAPSED_NAMESPACE_HEAD;
18823        let tree = parse_cpp(source);
18824        let target = named_node_at(&tree, source, "class Target");
18825
18826        assert!(
18827            !has_ancestor_kind(target, "namespace_definition"),
18828            "the fixture must reproduce the collapse: the class has no namespace ancestor"
18829        );
18830        let head = target.parent().expect("the collapsed namespace envelope");
18831        assert_eq!(
18832            head.kind(),
18833            "ERROR",
18834            "the fixture must keep the namespace head in an ERROR node"
18835        );
18836
18837        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
18838        assert_eq!(
18839            index.enclosing_namespace_components(target, source),
18840            ["app"]
18841        );
18842        assert_eq!(
18843            index.enclosing_namespace_levels(target, source),
18844            vec![(
18845                "app".to_string(),
18846                Range {
18847                    start_byte: 0,
18848                    end_byte: source.len() - 1,
18849                    start_line: 1,
18850                    end_line: 25,
18851                },
18852                Range {
18853                    start_byte: 10,
18854                    end_byte: 13,
18855                    start_line: 1,
18856                    end_line: 1,
18857                },
18858            )],
18859            "the recovered namespace owns its full construct, not its first member or just its header"
18860        );
18861    }
18862
18863    #[test]
18864    #[hardwon("#5868: a literal namespace head inside a damaged function wrapper lost its owner")]
18865    fn namespace_body_name_components_recovers_literal_function_head() {
18866        // Independently authored direct-helper contract, not an index or
18867        // short-body reproduction. The index deliberately leaves clean
18868        // subtrees intact; head recognition alone does not make this simple
18869        // body an indexed recovery region. Corpus index/short-body/outside
18870        // ownership remains required by the private root golden prewriter.
18871        let source = "OPAQUE_PREFIX\nnamespace recovered\n{\nint member;\n}\nint member;\n";
18872        let tree = parse_cpp(source);
18873        let keyword_start = source.find("namespace recovered").unwrap();
18874        let keyword = tree
18875            .root_node()
18876            .named_descendant_for_byte_range(keyword_start, keyword_start + "namespace".len())
18877            .expect("literal keyword node");
18878        assert_eq!(keyword.kind(), "identifier");
18879        assert_eq!(keyword.child_count(), 0);
18880        assert!(!keyword.is_missing() && !keyword.has_error());
18881        assert_eq!(node_text(keyword, source), "namespace");
18882        let wrapper = keyword.parent().expect("actual wrapper");
18883        assert_eq!(wrapper.kind(), "function_definition");
18884        assert!(wrapper.has_error());
18885        let mut cursor = wrapper.walk();
18886        let children = wrapper
18887            .children(&mut cursor)
18888            .filter(|child| child.kind() != "comment")
18889            .collect::<Vec<_>>();
18890        assert_eq!(
18891            children.len(),
18892            4,
18893            "all named and unnamed children accounted: {children:?}"
18894        );
18895        let prefix = children[0];
18896        assert_eq!(prefix.kind(), "type_identifier");
18897        assert_eq!(prefix.child_count(), 0);
18898        assert!(!prefix.is_missing() && !prefix.has_error());
18899        assert_eq!(wrapper.child_by_field_name("type"), Some(prefix));
18900        assert_eq!(children[1], keyword);
18901        assert_eq!(wrapper.child_by_field_name("declarator"), Some(keyword));
18902        let error = children[2];
18903        assert_eq!(error.kind(), "ERROR");
18904        assert!(!error.is_missing());
18905        let mut cursor = error.walk();
18906        let error_children = error
18907            .children(&mut cursor)
18908            .filter(|child| child.kind() != "comment")
18909            .collect::<Vec<_>>();
18910        assert_eq!(
18911            error_children.len(),
18912            1,
18913            "exact literal-name error envelope: {error_children:?}"
18914        );
18915        let name = error_children[0];
18916        assert!(matches!(name.kind(), "identifier" | "namespace_identifier"));
18917        assert_eq!(name.child_count(), 0);
18918        assert!(!name.is_missing() && !name.has_error());
18919        assert_eq!(node_text(name, source), "recovered");
18920        let body = wrapper.child_by_field_name("body").expect("actual body");
18921        assert_eq!(children[3], body);
18922        assert_eq!(body.kind(), "compound_statement");
18923        assert!(!body.is_missing());
18924        let open = body.child(0).expect("actual first child");
18925        assert_eq!(open.kind(), "{");
18926        assert!(!open.is_missing() && !open.has_error());
18927        assert_eq!(open.start_byte(), body.start_byte());
18928        let (head, names) = namespace_body_name_components(wrapper, body, source)
18929            .expect("strict synthetic literal head recognized");
18930        assert_eq!(head, keyword);
18931        assert_eq!(
18932            names
18933                .iter()
18934                .map(|(name, _)| name.as_str())
18935                .collect::<Vec<_>>(),
18936            ["recovered"]
18937        );
18938        assert_eq!(
18939            (names[0].1.start_byte, names[0].1.end_byte),
18940            (name.start_byte(), name.end_byte())
18941        );
18942        let inside = named_node_at(&tree, source, "int member;");
18943        assert!(body.start_byte() < inside.start_byte() && inside.end_byte() < body.end_byte());
18944    }
18945
18946    #[test]
18947    #[hardwon("#5868: refusal controls must actually exercise the namespace body helper")]
18948    fn orphaned_namespace_scope_index_refuses_unproven_function_heads() {
18949        for refusal in [
18950            "int ordinary(int value) { return value; }",
18951            "OPAQUE_PREFIX namespace_ recovered { int member; }",
18952            "OPAQUE_PREFIX namespace recovered other { int member; }",
18953            "OPAQUE_PREFIX namespace recovered(int value) { return value; }",
18954        ] {
18955            let tree = parse_cpp(refusal);
18956            let mut stack = vec![tree.root_node()];
18957            let mut helper_calls = 0;
18958            while let Some(node) = stack.pop() {
18959                if node.kind() == "function_definition"
18960                    && let Some(body) = node.child_by_field_name("body")
18961                {
18962                    helper_calls += 1;
18963                    assert_eq!(
18964                        (node.start_byte(), node.end_byte()),
18965                        (0, refusal.len()),
18966                        "guard control must exercise its complete envelope: source={refusal}, node={node:?}"
18967                    );
18968                    assert!(
18969                        namespace_body_name_components(node, body, refusal).is_none(),
18970                        "refused shape acquired namespace: source={refusal}, node={node:?}, body={body:?}"
18971                    );
18972                }
18973                let mut cursor = node.walk();
18974                stack.extend(node.named_children(&mut cursor));
18975            }
18976            assert_eq!(
18977                helper_calls,
18978                1,
18979                "guard control must reach exactly its actual function/body helper call: source={refusal}, tree={:?}",
18980                tree.root_node()
18981            );
18982        }
18983
18984        // The measured '=' case has no function/body envelope. A missing
18985        // body is likewise parser-absence coverage, not a passed helper guard.
18986        for absent in [
18987            "OPAQUE_PREFIX namespace recovered = other { int member; }",
18988            "OPAQUE_PREFIX namespace recovered int member;",
18989        ] {
18990            let tree = parse_cpp(absent);
18991            assert!(
18992                tree.root_node().has_error(),
18993                "expected malformed input: {absent}"
18994            );
18995            let mut stack = vec![tree.root_node()];
18996            let mut helper_candidates = 0;
18997            while let Some(node) = stack.pop() {
18998                if node.kind() == "function_definition"
18999                    && node.child_by_field_name("body").is_some()
19000                {
19001                    helper_candidates += 1;
19002                }
19003                let mut cursor = node.walk();
19004                stack.extend(node.named_children(&mut cursor));
19005            }
19006            assert_eq!(
19007                helper_candidates,
19008                0,
19009                "parser-absence control unexpectedly acquired a helper envelope: source={absent}, tree={:?}",
19010                tree.root_node()
19011            );
19012        }
19013    }
19014
19015    /// A macro that opens a block: `APP_IF_ACTIVE` expands to `if (active) {`,
19016    /// so the `{` of a scope the source really opens is part of the macro's
19017    /// `preproc_arg` and never becomes a `{` token. The tree therefore holds
19018    /// one more real `}` than real `{`, and a file-global stack has to assign
19019    /// those closes to the wrong opens: the `}` on line 9 takes `check`'s body,
19020    /// `check`'s own `}` on line 11 takes `detail`, line 15 takes `app`, and
19021    /// line 16 arrives with nothing open. This is the state simdjson's
19022    /// `compile_time_json-inl.h` reaches eight times over (#3328).
19023    const MACRO_OPENED_SCOPE: &str = r#"#define APP_IF_ACTIVE if (active) {
19024
19025namespace app {
19026namespace detail {
19027
19028bool check(bool active) {
19029  APP_IF_ACTIVE
19030    return true;
19031  }
19032  return false;
19033}
19034
19035class Tail {};
19036
19037} // namespace detail
19038} // namespace app
19039"#;
19040
19041    /// simdjson's `include/simdjson/compile_time_json-inl.h` at `9b33047a`,
19042    /// reduced. ABI 15 with runtime 0.25.10 now keeps each case-label character
19043    /// literal and the nested namespace definitions. The malformed function
19044    /// body still causes a missing outer namespace close. Keep the original
19045    /// source as a positive for real inner closes, without inventing the outer
19046    /// close that the parser could not read (#3328).
19047    const REFLECTION_CASE_LABEL_RECOVERY: &str = r#"namespace simdjson {
19048namespace compile_time {
19049namespace number_parsing {
19050consteval int leading_zeroes(uint64_t input_num, int last_bit = 0) {
19051    auto hex_to_u32 = [&] [[nodiscard]] () -> uint32_t {
19052      auto digit = [](uint8_t c) -> uint32_t {
19053      }
19054    }
19055    if (a) {
19056      while (a) {
19057      }
19058    }
19059    switch (c) {
19060    case '{': {
19061    }
19062    }
19063  }
19064  while (a) {
19065    cursor += field.second;
19066    if (a) {
19067    case '{': {
19068      if (a) {
19069      }
19070    }
19071    case 'n': {
19072      if (a) {
19073      }
19074    }
19075    }
19076  }
19077  if (a) {
19078    simdjson_consteval_error("Expected '}'");
19079  }
19080}
19081}
19082}
19083"#;
19084
19085    /// Real `{` and `}` tokens of a tree, the stream the index pairs.
19086    fn real_brace_token_counts(root: Node<'_>) -> (usize, usize) {
19087        let mut stack = vec![root];
19088        let mut counts = (0, 0);
19089        while let Some(node) = stack.pop() {
19090            match (node.kind(), node.is_missing()) {
19091                ("{", false) => counts.0 += 1,
19092                ("}", false) => counts.1 += 1,
19093                _ => {}
19094            }
19095            let mut cursor = node.walk();
19096            stack.extend(node.children(&mut cursor));
19097        }
19098        counts
19099    }
19100
19101    /// The open brace of `head`, which is the key its close lands under.
19102    fn namespace_open_byte(source: &str, head: &str) -> usize {
19103        let at = source.find(head).expect("fixture namespace head");
19104        at + head.len() - 1
19105    }
19106
19107    fn assert_namespace_heads_have_no_matching_close(root: Node<'_>, source: &str, heads: &[&str]) {
19108        let index = OrphanedNamespaceScopeIndex::build(root, source);
19109        for head in heads {
19110            assert_eq!(
19111                index.matching_close_brace(namespace_open_byte(source, head)),
19112                None,
19113                "{head} must not claim a close belonging to an inner scope"
19114            );
19115        }
19116    }
19117
19118    #[test]
19119    #[hardwon("1352f739be3d: C++ declaration recovery regressed for this structured syntax edge")]
19120    fn orphaned_namespace_scope_index_pairs_nothing_when_a_scope_open_is_lost() {
19121        let source = MACRO_OPENED_SCOPE;
19122        let tree = parse_cpp(source);
19123        let root = tree.root_node();
19124        assert_eq!(
19125            real_brace_token_counts(root),
19126            (4, 5),
19127            "the fixture must reproduce the loss: one scope the source opens \
19128             has no `{{` token, so the stream holds an extra real close"
19129        );
19130
19131        assert_namespace_heads_have_no_matching_close(
19132            root,
19133            source,
19134            &["namespace app {", "namespace detail {"],
19135        );
19136    }
19137
19138    #[test]
19139    #[hardwon("5312adbcf9bf: C++ declaration recovery regressed for this structured syntax edge")]
19140    fn orphaned_namespace_scope_index_leaves_a_declaration_after_every_close_at_file_scope() {
19141        // The stack proves no pairing in this file, so every restored level
19142        // keeps its best-effort scope. None of it may reach a declaration the
19143        // file writes after its last close.
19144        let source = format!("{MACRO_OPENED_SCOPE}struct AfterAll {{}};\n");
19145        let tree = parse_cpp(&source);
19146        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), &source);
19147        let after_all = named_node_at(&tree, &source, "struct AfterAll");
19148        assert!(
19149            index
19150                .enclosing_namespace_components(after_all, &source)
19151                .is_empty()
19152        );
19153    }
19154
19155    #[test]
19156    #[hardwon("1352f739be3d: C++ declaration recovery regressed for this structured syntax edge")]
19157    fn orphaned_namespace_scope_index_preserves_structured_reflection_closes() {
19158        let source = REFLECTION_CASE_LABEL_RECOVERY;
19159        let tree = parse_cpp(source);
19160        let root = tree.root_node();
19161        let mut stack = vec![root];
19162        let mut mis_lexed_opens = Vec::new();
19163        while let Some(node) = stack.pop() {
19164            if node.kind() == "character"
19165                && &source[node.start_byte()..node.end_byte()] == "{"
19166                && node
19167                    .parent()
19168                    .is_some_and(|parent| parent.kind() != "char_literal")
19169            {
19170                mis_lexed_opens.push(node.start_position().row + 1);
19171            }
19172            let mut cursor = node.walk();
19173            stack.extend(node.children(&mut cursor));
19174        }
19175        assert!(
19176            mis_lexed_opens.is_empty(),
19177            "ABI15 should keep each case-label `{{` inside its `char_literal`; \
19178             the old ABI14 mis-lex is no longer a valid recovery witness"
19179        );
19180        assert_eq!(
19181            real_brace_token_counts(root),
19182            (17, 17),
19183            "the improved parse retains balanced real brace tokens"
19184        );
19185        assert_eq!(root.kind(), "translation_unit");
19186        assert!(root.has_error(), "the malformed body remains incomplete");
19187        let index = OrphanedNamespaceScopeIndex::build(root, source);
19188        assert_eq!(
19189            index.matching_close_brace(namespace_open_byte(source, "namespace simdjson {")),
19190            None,
19191            "a missing outer close must not borrow an inner namespace's real close"
19192        );
19193        for (head, close) in [
19194            ("namespace compile_time {", source.len() - 2),
19195            ("namespace number_parsing {", source.len() - 4),
19196        ] {
19197            assert_eq!(
19198                index
19199                    .matching_close_brace(namespace_open_byte(source, head))
19200                    .map(|range| range.start_byte),
19201                Some(close),
19202                "{head} retains its own real close"
19203            );
19204        }
19205    }
19206
19207    #[test]
19208    #[hardwon("#2243: an empty namespace could hide a nested indexed owner suffix")]
19209    fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
19210        let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
19211        assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
19212        assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
19213        assert!(indexed_namespace_path_is_recoverable(
19214            &["cache".to_string()],
19215            &indexed,
19216            1,
19217        ));
19218    }
19219
19220    #[test]
19221    #[hardwon("#1876: lookup ordering was partial and changed with unrelated workspace content")]
19222    fn sort_lookup_units_totally_orders_every_identity_field() {
19223        let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
19224        let base = CodeUnit::with_signature(
19225            file.clone(),
19226            CodeUnitType::Function,
19227            "scope",
19228            "value",
19229            Some("()".to_string()),
19230            false,
19231        );
19232        let different_kind = CodeUnit::with_signature(
19233            file.clone(),
19234            CodeUnitType::Field,
19235            "scope",
19236            "value",
19237            Some("()".to_string()),
19238            false,
19239        );
19240        let synthetic = base.with_synthetic(true);
19241
19242        let interner = segment_interner();
19243        let mut member_fq = FqName::new();
19244        member_fq.push(interner.intern("scope", SegmentKind::Package));
19245        member_fq.push(interner.intern("value", SegmentKind::Member));
19246        let different_package_boundary = CodeUnit::from_fq(
19247            file.clone(),
19248            CodeUnitType::Function,
19249            member_fq,
19250            0,
19251            Some("()".to_string()),
19252            false,
19253        );
19254
19255        let mut unknown_fq = FqName::new();
19256        unknown_fq.push(interner.intern("scope", SegmentKind::Package));
19257        unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
19258        let different_segment_kind = CodeUnit::from_fq(
19259            file,
19260            CodeUnitType::Function,
19261            unknown_fq,
19262            1,
19263            Some("()".to_string()),
19264            false,
19265        );
19266
19267        let input = vec![
19268            base,
19269            different_kind,
19270            synthetic,
19271            different_package_boundary,
19272            different_segment_kind,
19273        ];
19274        let mut expected = input.clone();
19275        sort_lookup_units(&mut expected);
19276        assert!(expected.windows(2).all(|pair| {
19277            let mut ordered = pair.to_vec();
19278            sort_lookup_units(&mut ordered);
19279            ordered == pair && pair[0] != pair[1]
19280        }));
19281
19282        let mut reversed = input.clone();
19283        reversed.reverse();
19284        sort_lookup_units(&mut reversed);
19285        assert_eq!(reversed, expected);
19286
19287        let mut rotated = input;
19288        rotated.rotate_left(2);
19289        sort_lookup_units(&mut rotated);
19290        assert_eq!(rotated, expected);
19291    }
19292
19293    #[test]
19294    #[hardwon("6ffe85773652: C++ declaration recovery regressed for this structured syntax edge")]
19295    fn structured_conditional_array_bound_preserves_the_real_guard() {
19296        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";
19297        let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
19298        let parse = |source: &str| {
19299            let mut parser = Parser::new();
19300            parser
19301                .set_language(&tree_sitter_cpp::LANGUAGE.into())
19302                .expect("C++ grammar");
19303            parser.parse(source, None).expect("fixture tree")
19304        };
19305
19306        let tree = parse(damaged);
19307        let root = tree.root_node();
19308        assert!(
19309            !root.has_error(),
19310            "the native conditional-array grammar now reads the original recovery witness cleanly: {}",
19311            root.to_sexp()
19312        );
19313        let target = damaged.find("target").expect("target byte");
19314        let declaration = root
19315            .descendant_for_byte_range(target, target + "target".len())
19316            .and_then(|mut node| {
19317                loop {
19318                    if node.kind() == "declaration" {
19319                        break Some(node);
19320                    }
19321                    node = node.parent()?;
19322                }
19323            })
19324            .expect("declaration after the displaced terminator");
19325        let outer = declaration
19326            .parent()
19327            .filter(|node| node.kind() == "preproc_ifdef")
19328            .expect("ordinary outer include guard");
19329        let bound = outer
19330            .named_children(&mut outer.walk())
19331            .filter(|node| node.kind() == "declaration")
19332            .filter_map(|node| node.child_by_field_name("declarator"))
19333            .find(|node| node.kind() == "array_declarator")
19334            .and_then(|node| node.child_by_field_name("size"))
19335            .expect("structured conditional array bound");
19336        assert_eq!(bound.kind(), "preproc_array_size");
19337        let conditional = bound
19338            .named_children(&mut bound.walk())
19339            .find(|node| node.kind() == "preproc_ifdef")
19340            .expect("array-size conditional");
19341        assert!(conditional.end_byte() <= declaration.start_byte());
19342        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
19343        assert!(
19344            !std::iter::successors(Some(declaration), |node| node.parent())
19345                .any(|ancestor| same_node(ancestor, conditional)),
19346            "the declaration must not inherit the array-size feature guard"
19347        );
19348        assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
19349        assert!(preprocessor_conditional_contains_descendant(
19350            outer,
19351            declaration
19352        ));
19353
19354        let tree = parse(guarded);
19355        let conditional = tree
19356            .root_node()
19357            .named_child(0)
19358            .filter(|node| node.kind() == "preproc_ifdef")
19359            .expect("ordinary conditional");
19360        let declaration = conditional
19361            .named_children(&mut conditional.walk())
19362            .find(|node| node.kind() == "declaration")
19363            .expect("guarded declaration");
19364        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
19365        assert!(preprocessor_conditional_contains_descendant(
19366            conditional,
19367            declaration
19368        ));
19369
19370        let damaged_alternative = format!(
19371            "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
19372            "UNUSED(value)\n".repeat(64)
19373        );
19374        let tree = parse(&damaged_alternative);
19375        let conditional = tree
19376            .root_node()
19377            .named_child(0)
19378            .filter(|node| node.kind() == "preproc_ifdef")
19379            .expect("outer conditional with an alternative");
19380        assert!(conditional.has_error());
19381        assert!(conditional.child_by_field_name("alternative").is_some());
19382        assert!(
19383            conditional
19384                .child(conditional.child_count() - 1)
19385                .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
19386        );
19387        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
19388
19389        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";
19390        let tree = parse(split_declaration);
19391        let root = tree.root_node();
19392        let conditional = root
19393            .named_children(&mut root.walk())
19394            .find(|node| node.kind() == "preproc_ifdef" && node.start_position().row == 3)
19395            .expect("split declaration conditional");
19396        let target = split_declaration
19397            .find("static int target")
19398            .expect("target byte");
19399        let boundary =
19400            cpp_displaced_preprocessor_boundary(conditional).expect("split declaration boundary");
19401        assert!(boundary.end_byte <= target, "{boundary:?}");
19402        assert_eq!(boundary.end_line, 9, "{boundary:?}");
19403        let target_node = root
19404            .descendant_for_byte_range(target, target + "static".len())
19405            .expect("target node");
19406        assert!(!preprocessor_conditional_contains_descendant(
19407            conditional,
19408            target_node
19409        ));
19410    }
19411
19412    #[test]
19413    #[hardwon("6ba7704de3ed: C++ declaration recovery regressed for this structured syntax edge")]
19414    fn fragmented_reference_guard_is_recovered() {
19415        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";
19416        let mut parser = Parser::new();
19417        parser
19418            .set_language(&tree_sitter_cpp::LANGUAGE.into())
19419            .expect("C++ grammar");
19420        let tree = parser.parse(source, None).expect("fixture tree");
19421        let start = source.rfind("helper").expect("reference byte");
19422        let node = tree
19423            .root_node()
19424            .descendant_for_byte_range(start, start + "helper".len())
19425            .expect("reference node");
19426        let mut expected = HashSet::default();
19427        expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
19428            vec![
19429                BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
19430                BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
19431            ],
19432        )));
19433        assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
19434    }
19435
19436    #[test]
19437    fn expression_defined_and_ifndef_guards_are_incompatible() {
19438        let source = "#if defined(WIN_MODE)\nint selected;\n#endif\n#ifndef WIN_MODE\nint rejected;\n#endif\n";
19439        let mut parser = Parser::new();
19440        parser
19441            .set_language(&tree_sitter_cpp::LANGUAGE.into())
19442            .expect("C++ grammar");
19443        let tree = parser.parse(source, None).expect("fixture tree");
19444        let root = tree.root_node();
19445        let selected_start = source.find("selected").expect("selected declaration");
19446        let rejected_start = source.find("rejected").expect("rejected declaration");
19447        let selected = root
19448            .descendant_for_byte_range(selected_start, selected_start + "selected".len())
19449            .expect("selected node");
19450        let rejected = root
19451            .descendant_for_byte_range(rejected_start, rejected_start + "rejected".len())
19452            .expect("rejected node");
19453        let selected_guards =
19454            preprocessor_guard_environment(selected, source).expect("selected guards");
19455        let rejected_guards =
19456            preprocessor_guard_environment(rejected, source).expect("rejected guards");
19457
19458        assert!(
19459            merge_preprocessor_guards(&selected_guards, &rejected_guards).is_none(),
19460            "opposite spellings of one macro guard must contradict"
19461        );
19462    }
19463
19464    #[test]
19465    #[hardwon("02b3765c2a06: C++ declaration recovery regressed for this structured syntax edge")]
19466    fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
19467        let source = r#"#ifdef _WIN32
19468#if defined(__cplusplus)
19469extern "C"
19470#endif
19471int platform_api(void);
19472#endif
19473
19474#ifdef _WIN32
19475static int entropy_target(void) { return 0; }
19476#else
19477#ifdef HAVE_COMMON_RANDOM
19478static int other_target(void) { return 0; }
19479#elif defined(HAVE_GETENTROPY)
19480static int entropy_target(void) { return 1; }
19481static int use_entropy(void) { return entropy_target(); }
19482#endif
19483#endif
19484"#;
19485        let mut parser = Parser::new();
19486        parser
19487            .set_language(&tree_sitter_cpp::LANGUAGE.into())
19488            .expect("C++ grammar");
19489        let tree = parser.parse(source, None).expect("fixture tree");
19490        let start = source.rfind("entropy_target()").expect("reference");
19491        let node = tree
19492            .root_node()
19493            .descendant_for_byte_range(start, start + "entropy_target".len())
19494            .expect("reference node");
19495        let guards = preprocessor_guard_environment(node, source).expect("active C branch");
19496        assert!(
19497            guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
19498            "{guards:#?}"
19499        );
19500        assert!(
19501            guards.contains(&PreprocessorGuard::Undefined(
19502                "HAVE_COMMON_RANDOM".to_string()
19503            )),
19504            "{guards:#?}"
19505        );
19506        assert!(
19507            guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
19508            "{guards:#?}"
19509        );
19510        assert!(
19511            !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
19512            "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
19513        );
19514    }
19515
19516    #[test]
19517    #[hardwon("1e74d25492c1: C++ declaration recovery regressed for this structured syntax edge")]
19518    fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
19519        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";
19520        let mut parser = Parser::new();
19521        parser
19522            .set_language(&tree_sitter_cpp::LANGUAGE.into())
19523            .expect("C++ grammar");
19524        let tree = parser.parse(source, None).expect("fixture tree");
19525        let root = tree.root_node();
19526        let node_at = |text: &str, start: usize| {
19527            root.descendant_for_byte_range(start, start + text.len())
19528                .expect("token node")
19529        };
19530
19531        let key_start = source.find("case KEY").expect("case label") + "case ".len();
19532        let guard_start = source.find("ENABLE_KEYS").expect("guard name");
19533        assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
19534        assert!(!is_ordinary_macro_reference_node(node_at(
19535            "ENABLE_KEYS",
19536            guard_start,
19537        )));
19538    }
19539
19540    #[test]
19541    #[hardwon("31913fee42a0: C++ declaration recovery regressed for this structured syntax edge")]
19542    fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
19543        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";
19544        let mut parser = Parser::new();
19545        parser
19546            .set_language(&tree_sitter_cpp::LANGUAGE.into())
19547            .expect("C++ grammar");
19548        let tree = parser.parse(source, None).expect("fixture tree");
19549        let root = tree.root_node();
19550        let definition_start = source.find("target(void)").expect("definition");
19551        let reference_start = source.rfind("target()").expect("reference");
19552        let definition = root
19553            .descendant_for_byte_range(definition_start, definition_start + "target".len())
19554            .expect("definition node");
19555        let reference = root
19556            .descendant_for_byte_range(reference_start, reference_start + "target".len())
19557            .expect("reference node");
19558        let required =
19559            preprocessor_guard_environment(definition, source).expect("definition guard");
19560        let active = preprocessor_guard_environment(reference, source).expect("reference guard");
19561        assert!(guard_requirements_hold_at_reference(
19562            &required,
19563            Some(&active)
19564        ));
19565    }
19566
19567    #[test]
19568    #[hardwon("31913fee42a0: C++ declaration recovery regressed for this structured syntax edge")]
19569    fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
19570        let source = "g_autoptr(FuChunkArray) self = make_array();";
19571        let mut parser = Parser::new();
19572        parser
19573            .set_language(&tree_sitter_cpp::LANGUAGE.into())
19574            .expect("C++ grammar");
19575        let tree = parser.parse(source, None).expect("fixture tree");
19576        let statement = tree.root_node().named_child(0).expect("statement");
19577        let binding =
19578            recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
19579        assert_eq!(binding.name, "self");
19580        assert_eq!(binding.type_name, "FuChunkArray");
19581        assert_eq!(binding.pointer_depth, 1);
19582
19583        let near_miss = "holder(FuChunkArray) self = make_array();";
19584        let tree = parser.parse(near_miss, None).expect("near-miss tree");
19585        let statement = tree.root_node().named_child(0).expect("statement");
19586        assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
19587    }
19588
19589    #[test]
19590    #[hardwon("dc1b8c5a6ee8: C++ declaration recovery regressed for this structured syntax edge")]
19591    fn boolean_guard_normalization_proves_equivalence_and_implication() {
19592        let windows = BooleanGuardExpression::Defined("WIN32".to_string());
19593        let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
19594        let negated_windows_branch =
19595            BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
19596        let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
19597        assert_eq!(negated_windows_branch, portable);
19598
19599        let missing_a = BooleanGuardExpression::Undefined("A".to_string());
19600        let missing_b = BooleanGuardExpression::Undefined("B".to_string());
19601        let missing_c = BooleanGuardExpression::Undefined("C".to_string());
19602        let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
19603        let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
19604        assert!(fallback_branch.implies(&fallback_declaration));
19605        assert!(
19606            BooleanGuardExpression::Truthy("FEATURE".to_string())
19607                .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
19608        );
19609        assert!(
19610            BooleanGuardExpression::Undefined("FEATURE".to_string())
19611                .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
19612        );
19613        assert!(
19614            !BooleanGuardExpression::Defined("FEATURE".to_string())
19615                .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
19616        );
19617        assert!(!fallback_declaration.implies(&fallback_branch));
19618    }
19619
19620    #[test]
19621    #[hardwon("15e5ef579cdd: C++ declaration recovery regressed for this structured syntax edge")]
19622    fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
19623        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";
19624        let mut parser = Parser::new();
19625        parser
19626            .set_language(&tree_sitter_cpp::LANGUAGE.into())
19627            .expect("C++ grammar");
19628        let tree = parser.parse(source, None).expect("fixture tree");
19629        let root = tree.root_node();
19630        let call = |marker: &str| {
19631            let start = source.find(marker).expect("call marker");
19632            let mut node = root
19633                .descendant_for_byte_range(start, start + "helper".len())
19634                .expect("call name node");
19635            loop {
19636                if node.kind() == "call_expression" {
19637                    break node;
19638                }
19639                node = node.parent().expect("call expression ancestor");
19640            }
19641        };
19642        let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
19643        let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
19644        let keyword_call = call("helper(NULL, template); /* bound */");
19645        let keyword_arguments = keyword_call
19646            .child_by_field_name("arguments")
19647            .expect("keyword argument list");
19648        assert_eq!(
19649            recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
19650            1
19651        );
19652
19653        let nested_call = call("helper(NULL, template); /* nested bound */");
19654        let nested_arguments = nested_call
19655            .child_by_field_name("arguments")
19656            .expect("nested keyword argument list");
19657        assert_eq!(
19658            recovered_c_keyword_argument_count(&c_file, nested_call, nested_arguments, source),
19659            1
19660        );
19661
19662        let lambda_call = call("helper(NULL, template); }(); /* lambda scope */");
19663        let lambda_arguments = lambda_call
19664            .child_by_field_name("arguments")
19665            .expect("lambda keyword argument list");
19666        assert_eq!(
19667            recovered_c_keyword_argument_count(&c_file, lambda_call, lambda_arguments, source),
19668            0,
19669            "a lambda body must not borrow the enclosing collapsed C parameter"
19670        );
19671
19672        assert_eq!(
19673            recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
19674            0
19675        );
19676
19677        let unbound_call = call("helper(NULL, template); /* unbound */");
19678        let unbound_arguments = unbound_call
19679            .child_by_field_name("arguments")
19680            .expect("unbound argument list");
19681        assert_eq!(
19682            recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
19683            0
19684        );
19685    }
19686
19687    #[test]
19688    #[brokk_bifrost_test_macros::hardwon(
19689        "#4407: collapsed C keyword parameters need structural position proof"
19690    )]
19691    fn c_keyword_argument_recovery_rejects_non_parameter_keyword_positions() {
19692        let source = r#"static int helper(const char *left, wchar_t *right) { return 0; }
19693int type_keyword(template *value) {
19694    return helper(NULL, template); /* type */
19695}
19696int qualifier_keyword(const *template) {
19697    return helper(NULL, template); /* qualifier */
19698}
19699int annotation_keyword(wchar_t *value __attribute__((template))) {
19700    return helper(NULL, template); /* annotation */
19701}
19702int default_keyword(wchar_t *value = template) {
19703    return helper(NULL, template); /* default */
19704}
19705int nested_expression(wchar_t *value, (template)) {
19706    return helper(NULL, template); /* nested */
19707}
19708int malformed_head = (wchar_t *template) {
19709    return helper(NULL, template); /* malformed */
19710};
19711"#;
19712        let tree = parse_cpp(source);
19713        let root = tree.root_node();
19714        let call = |marker: &str| {
19715            let start = source.find(marker).expect("call marker");
19716            let mut node = root
19717                .descendant_for_byte_range(start, start + "helper".len())
19718                .expect("call name node");
19719            loop {
19720                if node.kind() == "call_expression" {
19721                    break node;
19722                }
19723                node = node.parent().expect("call expression ancestor");
19724            }
19725        };
19726        let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-near-miss.c");
19727        for marker in [
19728            "helper(NULL, template); /* type */",
19729            "helper(NULL, template); /* qualifier */",
19730            "helper(NULL, template); /* annotation */",
19731            "helper(NULL, template); /* default */",
19732            "helper(NULL, template); /* nested */",
19733            "helper(NULL, template); /* malformed */",
19734        ] {
19735            let keyword_call = call(marker);
19736            let keyword_arguments = keyword_call
19737                .child_by_field_name("arguments")
19738                .expect("keyword argument list");
19739            assert_eq!(
19740                recovered_c_keyword_argument_count(
19741                    &c_file,
19742                    keyword_call,
19743                    keyword_arguments,
19744                    source,
19745                ),
19746                0,
19747                "keyword in {marker} must not prove a parameter binding"
19748            );
19749        }
19750    }
19751
19752    #[test]
19753    #[hardwon("31982bd4d78a: C++ declaration recovery regressed for this structured syntax edge")]
19754    fn c_function_declarator_recovery_accepts_invocations_not_binders() {
19755        let source = r#"#define MAKE(type) type *value
19756MAKE(int *);
19757typedef struct Item Item;
19758struct CPUX86State { struct { int ZMM_L(int); } xmm_regs[8]; };
19759void gen_op_movl(void *s, int first, int second) { }
19760const char *strZ(const char *value) { return value; }
19761int body(void *s) {
19762    MAKE(int *);
19763    gen_op_movl(s, offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)),
19764                offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)));
19765    execvp(strZ(value), UNCONSTIFY(char **, args));
19766}
19767 #define DEV_CHECK_PRESENCE(TYPE, MEMBER, DEVTYPE, PROPERTY, VALUE) \
19768    if (!((TYPE)target)->MEMBER) { check(DEVTYPE, PROPERTY, VALUE); }
19769int recovered_deviation(struct Deviation *d, struct Target *target, void *ctx) {
19770    if (d->units) {
19771        switch (target->nodetype) {
19772        case 1:
19773        case 2:
19774            break;
19775        default:
19776            AMEND_WRONG_NODETYPE("deviation", "replace", "units");
19777        }
19778        DEV_CHECK_PRESENCE(struct Item *, units, "replacing", "units", d->units);
19779        lysdict_remove(ctx, ((struct Item *)target)->units);
19780        DUP_STRING_GOTO(ctx, d->units, ((struct Item *)target)->units, ret, cleanup);
19781    }
19782     return 0;
19783 }
19784STATIC EFI_STATUS Encode () { return 0; }
19785"#;
19786        let tree = parse_cpp(source);
19787        let top_macro_start = source.find("MAKE(int *);").expect("top macro");
19788        let top_macro = tree
19789            .root_node()
19790            .named_descendant_for_byte_range(top_macro_start, top_macro_start + 4)
19791            .expect("top macro node");
19792        let body_macro_start = source
19793            .match_indices("MAKE(int *);")
19794            .nth(1)
19795            .expect("body macro")
19796            .0;
19797        let body_macro = tree
19798            .root_node()
19799            .named_descendant_for_byte_range(body_macro_start, body_macro_start + 4)
19800            .expect("body macro node");
19801        let function_call_start = source
19802            .find("gen_op_movl(s, offsetof(CPUX86State")
19803            .expect("function call");
19804        let function_call = tree
19805            .root_node()
19806            .named_descendant_for_byte_range(function_call_start, function_call_start + 11)
19807            .expect("function call node");
19808        let strz_start = source.find("strZ(value)").expect("nested function call");
19809        let strz = tree
19810            .root_node()
19811            .named_descendant_for_byte_range(strz_start, strz_start + 4)
19812            .expect("nested function call node");
19813        let recovered_call_start = source.find("lysdict_remove(ctx").expect("recovered call");
19814        let recovered_call = tree
19815            .root_node()
19816            .named_descendant_for_byte_range(
19817                recovered_call_start,
19818                recovered_call_start + "lysdict_remove".len(),
19819            )
19820            .expect("recovered call node");
19821        let binder_start = source.find("Encode").expect("binder");
19822        let binder = tree
19823            .root_node()
19824            .named_descendant_for_byte_range(binder_start, binder_start + 6)
19825            .expect("binder node");
19826
19827        assert!(recovered_c_function_declarator_invocation(top_macro));
19828        assert!(recovered_c_function_declarator_invocation(body_macro));
19829        assert!(recovered_c_function_declarator_invocation(function_call));
19830        assert!(recovered_c_function_declarator_invocation(strz));
19831        assert!(recovered_c_function_declarator_invocation(recovered_call));
19832        assert!(!recovered_c_function_declarator_invocation(binder));
19833    }
19834
19835    #[test]
19836    #[hardwon("31982bd4d78a: C++ declaration recovery regressed for this structured syntax edge")]
19837    fn c_parenthesized_declarator_recovery_keeps_keyword_argument_and_rejects_siblings() {
19838        let source = r#"typedef int krb5_context;
19839int helper(int first, int second) { return first + second; }
19840static krb5_context ctx;
19841int main(int argc, char **argv) {
19842    int ccinitial;
19843    const char *collection_name, *typename;
19844    typename = helper(ctx, ccinitial);
19845    return 0;
19846}
19847"#;
19848        let tree = parse_cpp(source);
19849        let ctx = tree
19850            .root_node()
19851            .descendant_for_byte_range(
19852                source.find("ctx, ccinitial").expect("ctx argument"),
19853                source.find("ctx, ccinitial").expect("ctx argument") + 3,
19854            )
19855            .expect("ctx node");
19856        let ccinitial_start = source.find("ctx, ccinitial").expect("ctx argument") + 5;
19857        let ccinitial = tree
19858            .root_node()
19859            .descendant_for_byte_range(ccinitial_start, ccinitial_start + "ccinitial".len())
19860            .expect("sibling node");
19861        let typename = named_node_at(&tree, source, "typename = helper");
19862        let helper = named_node_at(&tree, source, "helper(ctx, ccinitial)");
19863
19864        assert_eq!(ctx.kind(), "identifier");
19865        assert!(recovered_c_parenthesized_declarator_reference(ctx));
19866        assert!(!recovered_c_parenthesized_declarator_reference(ccinitial));
19867        assert!(!recovered_c_parenthesized_declarator_reference(typename));
19868        assert!(!recovered_c_parenthesized_declarator_reference(helper));
19869    }
19870
19871    fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
19872        let mut parser = Parser::new();
19873        parser
19874            .set_language(&tree_sitter_cpp::LANGUAGE.into())
19875            .expect("C++ grammar");
19876        let tree = parser.parse(source, None).expect("C++ fixture tree");
19877        let mut stack = vec![tree.root_node()];
19878        while let Some(node) = stack.pop() {
19879            if node.kind() == "enum_specifier" {
19880                return flattened_macro_namespace_components(node, source);
19881            }
19882            let mut cursor = node.walk();
19883            let children = node.named_children(&mut cursor).collect::<Vec<_>>();
19884            stack.extend(children.into_iter().rev());
19885        }
19886        None
19887    }
19888
19889    #[test]
19890    #[hardwon("0e6df36dfa5d: C++ declaration recovery regressed for this structured syntax edge")]
19891    fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
19892        let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
19893namespace detail
19894{
19895enum class value_t { null };
19896}
19897NLOHMANN_JSON_NAMESPACE_END
19898NLOHMANN_JSON_NAMESPACE_BEGIN
19899namespace next
19900{
19901struct next_type {};
19902}
19903NLOHMANN_JSON_NAMESPACE_END
19904"#;
19905        assert_eq!(
19906            first_enum_flattened_namespace(complete),
19907            Some(vec!["detail".to_string()])
19908        );
19909
19910        let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
19911        assert_eq!(
19912            first_enum_flattened_namespace(&stale_end),
19913            Some(vec!["detail".to_string()]),
19914            "a stale end marker before the begin marker must not replace the intended namespace"
19915        );
19916
19917        let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
19918namespace detail
19919{
19920enum class value_t { null };
19921}
19922struct next_type {};
19923"#;
19924        assert_eq!(first_enum_flattened_namespace(incomplete), None);
19925    }
19926}
19927
19928/// Comparator laws for the total C++ lookup order introduced by #1876.
19929///
19930/// `sort_lookup_units` is the single tie-break the C++ resolver applies before
19931/// any "first wins" selection (template families in #1836, the visible
19932/// identifier index, the type-candidate lists). If its comparator is not a
19933/// total order over CodeUnit identity, some pair stays tied and the survivor
19934/// falls back to the order the units arrived in -- which is FxHash iteration
19935/// order over keys whose hash covers the absolute workspace root. That is the
19936/// exact mechanism behind #1836 and the #414 / #432 heisenbug, so the laws are
19937/// checked generatively rather than on one hand-picked list.
19938///
19939/// CodeUnit identity is `source`, `kind`, `fq`, `package_segment_count`,
19940/// `signature` and `synthetic` (see `impl PartialEq for CodeUnit`); a CodeUnit
19941/// carries no range, so declaration ranges are covered by the workspace-level
19942/// property in `tests/suite_analyzers/determinism_properties.rs` instead.
19943#[cfg(test)]
19944mod lookup_order_properties {
19945    use super::*;
19946    use proptest::prelude::*;
19947
19948    /// Segment spellings the C++ extractor and the shared renderer actually
19949    /// produce, including the `$`-joined nested spellings and non-ASCII
19950    /// identifiers that a byte-wise comparison has to keep apart.
19951    const ATOMS: [&str; 9] = ["a", "b", "A", "a$b", "a$", "$a", "ab", "naïve", "識別子"];
19952    const REL_PATHS: [&str; 3] = ["a.cpp", "b.cpp", "sub/a.cpp"];
19953    /// Two roots so the order is pinned across workspaces as well as inside
19954    /// one: the root path is precisely the byte string that used to leak into
19955    /// iteration order.
19956    const ROOT_NAMES: [&str; 2] = ["ws", "ws_much_longer_root_name"];
19957    const SIGNATURES: [Option<&str>; 3] = [None, Some("()"), Some("(int)")];
19958    const KINDS: [CodeUnitType; 6] = [
19959        CodeUnitType::Class,
19960        CodeUnitType::Function,
19961        CodeUnitType::Field,
19962        CodeUnitType::Module,
19963        CodeUnitType::Macro,
19964        CodeUnitType::FileScope,
19965    ];
19966
19967    /// Where one unit sits relative to another under the comparator that
19968    /// `sort_lookup_units` owns.
19969    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
19970    enum ProbedOrder {
19971        Before,
19972        Tied,
19973        After,
19974        /// Both directions reported "strictly first": the comparator is not
19975        /// dual, and no sort over it can be order-independent.
19976        Contradictory,
19977    }
19978
19979    impl ProbedOrder {
19980        fn mirror(self) -> Self {
19981            match self {
19982                ProbedOrder::Before => ProbedOrder::After,
19983                ProbedOrder::After => ProbedOrder::Before,
19984                other => other,
19985            }
19986        }
19987
19988        /// -1 / 0 / +1, so transitivity reads as the `<= 0` law.
19989        fn signum(self) -> i8 {
19990            match self {
19991                ProbedOrder::Before => -1,
19992                ProbedOrder::Tied => 0,
19993                ProbedOrder::After => 1,
19994                ProbedOrder::Contradictory => panic!("probed a non-dual comparator"),
19995            }
19996        }
19997    }
19998
19999    /// Read the comparator through its only caller.
20000    ///
20001    /// `sort_lookup_units` is a stable sort, so for a two-element slice the
20002    /// output says exactly whether the comparator put the second element
20003    /// strictly first. Sorting both arrangements of one pair therefore reports
20004    /// the comparator's verdict in both directions, including the contradictory
20005    /// case a single sort would hide.
20006    fn probe_order(left: &CodeUnit, right: &CodeUnit) -> ProbedOrder {
20007        if left == right {
20008            // A stable sort cannot distinguish two equal values, and `Equal` is
20009            // the only verdict a total order can give them.
20010            return ProbedOrder::Tied;
20011        }
20012        let mut forward = vec![left.clone(), right.clone()];
20013        sort_lookup_units(&mut forward);
20014        let mut backward = vec![right.clone(), left.clone()];
20015        sort_lookup_units(&mut backward);
20016        let left_first = backward[0] == *left;
20017        let right_first = forward[0] == *right;
20018        match (left_first, right_first) {
20019            (true, true) => ProbedOrder::Contradictory,
20020            (true, false) => ProbedOrder::Before,
20021            (false, true) => ProbedOrder::After,
20022            (false, false) => ProbedOrder::Tied,
20023        }
20024    }
20025
20026    fn code_unit_strategy() -> impl Strategy<Value = CodeUnit> {
20027        (
20028            0..ROOT_NAMES.len(),
20029            0..REL_PATHS.len(),
20030            0..KINDS.len(),
20031            prop::collection::vec((0..ATOMS.len(), 0..SegmentKind::ALL.len()), 1..=3),
20032            0..3usize,
20033            0..SIGNATURES.len(),
20034            any::<bool>(),
20035        )
20036            .prop_map(
20037                |(root, rel_path, kind, segments, package_prefix, signature, synthetic)| {
20038                    let source = ProjectFile::new(
20039                        std::env::temp_dir().join(ROOT_NAMES[root]),
20040                        REL_PATHS[rel_path],
20041                    );
20042                    let interner = segment_interner();
20043                    let mut fq = FqName::new();
20044                    for (atom, segment_kind) in &segments {
20045                        fq.push(interner.intern(ATOMS[*atom], SegmentKind::ALL[*segment_kind]));
20046                    }
20047                    // `from_fq` requires a non-empty declaration tail.
20048                    let package_segment_count = package_prefix % fq.len();
20049                    CodeUnit::from_fq(
20050                        source,
20051                        KINDS[kind],
20052                        fq,
20053                        package_segment_count,
20054                        SIGNATURES[signature].map(str::to_string),
20055                        synthetic,
20056                    )
20057                },
20058            )
20059    }
20060
20061    proptest! {
20062        #![proptest_config(ProptestConfig::with_cases(256))]
20063
20064        /// Reflexivity and duality: a unit ties with itself, and no pair is
20065        /// strictly first in both directions.
20066        #[test]
20067        #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
20068        fn lookup_order_is_reflexive_and_dual(
20069            left in code_unit_strategy(),
20070            right in code_unit_strategy(),
20071        ) {
20072            prop_assert_eq!(
20073                probe_order(&left, &left),
20074                ProbedOrder::Tied,
20075                "a unit must tie with itself: {:?}",
20076                left
20077            );
20078            let forward = probe_order(&left, &right);
20079            prop_assert_ne!(
20080                forward,
20081                ProbedOrder::Contradictory,
20082                "comparator put each of these strictly first: left={:?} right={:?}",
20083                left,
20084                right
20085            );
20086            prop_assert_eq!(
20087                probe_order(&right, &left),
20088                forward.mirror(),
20089                "compare(b, a) must reverse compare(a, b): left={:?} right={:?}",
20090                left,
20091                right
20092            );
20093        }
20094
20095        /// Transitivity: `a <= b` and `b <= c` imply `a <= c`.
20096        #[test]
20097        #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
20098        fn lookup_order_is_transitive(
20099            a in code_unit_strategy(),
20100            b in code_unit_strategy(),
20101            c in code_unit_strategy(),
20102        ) {
20103            let ab = probe_order(&a, &b);
20104            let bc = probe_order(&b, &c);
20105            let ac = probe_order(&a, &c);
20106            for (probed, pair) in [(ab, "a,b"), (bc, "b,c"), (ac, "a,c")] {
20107                prop_assert_ne!(
20108                    probed,
20109                    ProbedOrder::Contradictory,
20110                    "comparator is not dual over {}: a={:?} b={:?} c={:?}",
20111                    pair,
20112                    a,
20113                    b,
20114                    c
20115                );
20116            }
20117            if ab.signum() <= 0 && bc.signum() <= 0 {
20118                prop_assert!(
20119                    ac.signum() <= 0,
20120                    "transitivity broken: a<=b ({:?}) and b<=c ({:?}) but a?c is {:?}; \
20121                     a={:?} b={:?} c={:?}",
20122                    ab,
20123                    bc,
20124                    ac,
20125                    a,
20126                    b,
20127                    c
20128                );
20129            }
20130        }
20131
20132        /// The property #1876 exists for: only identical identities may tie.
20133        /// A tie between distinct units is the residual hash-order dependence.
20134        #[test]
20135        #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
20136        fn lookup_order_separates_distinct_identities(
20137            left in code_unit_strategy(),
20138            right in code_unit_strategy(),
20139        ) {
20140            if probe_order(&left, &right) == ProbedOrder::Tied {
20141                prop_assert_eq!(
20142                    &left,
20143                    &right,
20144                    "distinct identities tied, so their order is whatever order they \
20145                     arrived in: left_segments={:?} right_segments={:?}",
20146                    left.fq_segments_debug(),
20147                    right.fq_segments_debug()
20148                );
20149            }
20150        }
20151
20152        /// The consequence the resolver relies on: the sorted list is a
20153        /// function of the SET of units, not of the order they were pushed in.
20154        #[test]
20155        #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
20156        fn lookup_sort_is_permutation_invariant(
20157            units in prop::collection::vec(code_unit_strategy(), 1..=8),
20158        ) {
20159            let mut sorted = units.clone();
20160            sort_lookup_units(&mut sorted);
20161            for rotation in 0..units.len() {
20162                for reversed in [false, true] {
20163                    let mut permuted = units.clone();
20164                    permuted.rotate_left(rotation);
20165                    if reversed {
20166                        permuted.reverse();
20167                    }
20168                    sort_lookup_units(&mut permuted);
20169                    prop_assert_eq!(
20170                        &permuted,
20171                        &sorted,
20172                        "sorting a permutation gave a different list \
20173                         (rotation={}, reversed={}): input={:?}",
20174                        rotation,
20175                        reversed,
20176                        units
20177                    );
20178                }
20179            }
20180        }
20181    }
20182}