Skip to main content

brokk_bifrost_cpp/graph/
resolver.rs

1use crate::call_match::{CppArgType, normalize_cpp_type_name};
2use crate::compile_context::CppCompileContext;
3use crate::declarations::cpp_displaced_preprocessor_branch;
4#[cfg(test)]
5use crate::declarations::cpp_displaced_preprocessor_terminator;
6use crate::declarations::{
7    CppComparableNode, CppComparableParameter, CppComparableSlot, CppRecoveredExportClassIndex,
8    cpp_displaced_preprocessor_boundary, cpp_export_macro_token, cpp_template_term,
9    enclosing_cpp_namespace_nodes, macro_modifier_identifier, node_text, normalize_cpp_whitespace,
10    recovered_class_body_at, recovered_function_like_field_declarator,
11    recovered_pyobject_head_field,
12};
13use crate::graph::CppGraphSource;
14use crate::graph::extractor::ScanCtx;
15use crate::graph::syntax::{
16    SentinelInput, function_macro_replacement_span, object_macro_replacement_type_references,
17    parse_sentinel, qualified_name_parts,
18};
19use crate::graph_support::CppSource;
20use crate::imports::{
21    IncludeTargetIndex, include_path_from_node, resolve_include_targets_with_index,
22};
23use crate::source_context::cpp_guard_set_to_runtime;
24use crate::structural::is_cpp_preprocessor_conditional;
25use crate::template_heads::{
26    CppTemplateBinding, CppTemplateContext, CppTemplateReconciliation,
27    cpp_declaration_template_context,
28};
29pub use brokk_bifrost_core::analyzer::cpp_facts::{
30    CppClassDeclarationStrength, CppEnumOwnerKind as EnumOwnerKind,
31};
32use brokk_bifrost_core::analyzer::cpp_facts::{
33    CppDeclaredFieldTypeFact as DeclaredFieldTypeFact,
34    CppStructuredAliasTarget as StructuredAliasTarget,
35};
36use brokk_bifrost_core::analyzer::fq_name::{FqName, SegmentKind, segment_interner};
37use brokk_bifrost_core::analyzer::model::{
38    CallableArity, CallableLinkage, CodeUnitType, CppFieldLinkage, CppTemplateExpression,
39    CppTemplateMetadata, CppTemplateParameterMetadata, CppTemplateTerm, Language, LanguageDialect,
40    SignatureMetadata, StructuredTypeName,
41};
42use brokk_bifrost_core::analyzer::pool_memo::PoolSafeMemo;
43use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
44#[cfg(test)]
45use brokk_bifrost_core::analyzer::prepared_syntax::{PreparedSourceOrigin, PreparedSyntaxSource};
46use brokk_bifrost_core::analyzer::query_token::QueryToken;
47use brokk_bifrost_core::analyzer::semantic_diagnostics::contains_node as node_range_contains;
48use brokk_bifrost_core::analyzer::source_facts::SourceDeclarationId;
49use brokk_bifrost_core::analyzer::structural::adapter_helpers::{field_name_in_parent, node_range};
50use brokk_bifrost_core::analyzer::tree_walk::{
51    ParentIndex, child_by_field_or_named_child_of_kinds, children_iter, first_named_child_of_kind,
52    named_children_iter, node_for_exact_range, push_named_children_reversed,
53    single_named_child_matching,
54};
55use brokk_bifrost_core::analyzer::usages::common::same_node;
56use brokk_bifrost_core::analyzer::usages::local_inference::LocalInferenceEngine;
57use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
58use brokk_bifrost_core::cancellation::CancellationToken;
59use brokk_bifrost_core::hash::{HashMap, HashSet};
60#[cfg(test)]
61use brokk_bifrost_core::text_utils::compute_line_starts;
62use std::borrow::Cow;
63#[cfg(any(test, feature = "test-support"))]
64use std::cell::Cell;
65use std::cell::OnceCell;
66use std::cmp::Ordering as CmpOrdering;
67use std::collections::BTreeSet;
68use std::hash::Hash;
69use std::sync::atomic::{AtomicUsize, Ordering};
70use std::sync::{Arc, Mutex, OnceLock, RwLock};
71use std::time::{Duration, Instant};
72use tree_sitter::{Node, Parser, Tree};
73
74const PREPROCESSOR_CONTEXT_CACHE_CAP: usize = 8192;
75const CALLABLE_METADATA_CACHE_CAP: usize = 512;
76const MACRO_LEXICAL_INDEX_CACHE_CAP: usize = 64;
77
78#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
79struct PreprocessorContextNodeKey {
80    id: usize,
81    start_byte: usize,
82    end_byte: usize,
83}
84
85/// Query-local memo for malformed-preprocessor boundary discovery.
86///
87/// The boundary is a property of one conditional AST node, but the resolver
88/// asks for it once per declaration below that node and once in each of the
89/// guard/family passes. Keeping this memo on the C++ visitor avoids repeating
90/// the bounded subtree scans without retaining anything after one translation
91/// unit is parsed. A full cache falls back to the uncached computation, so its
92/// memory is explicitly capped and its admission cannot affect answers.
93#[derive(Default)]
94pub(crate) struct PreprocessorContextCache {
95    displaced_boundaries: HashMap<PreprocessorContextNodeKey, Option<usize>>,
96    displaced_branches: HashMap<PreprocessorContextNodeKey, Option<DisplacedBranchDirective>>,
97}
98
99/// The end byte of a [`cpp_displaced_preprocessor_branch`] directive and
100/// whether it is an `#else` (otherwise an `#elif` form).
101#[derive(Clone, Copy)]
102struct DisplacedBranchDirective {
103    end_byte: usize,
104    is_else: bool,
105}
106
107impl PreprocessorContextCache {
108    fn displaced_branch(&mut self, conditional: Node<'_>) -> Option<DisplacedBranchDirective> {
109        let key = PreprocessorContextNodeKey {
110            id: conditional.id(),
111            start_byte: conditional.start_byte(),
112            end_byte: conditional.end_byte(),
113        };
114        if let Some(directive) = self.displaced_branches.get(&key) {
115            return *directive;
116        }
117        let directive = cpp_displaced_preprocessor_branch(conditional).map(|directive| {
118            DisplacedBranchDirective {
119                end_byte: directive.end_byte(),
120                is_else: directive.kind() == "#else",
121            }
122        });
123        if self.displaced_branches.len() < PREPROCESSOR_CONTEXT_CACHE_CAP {
124            self.displaced_branches.insert(key, directive);
125        }
126        directive
127    }
128
129    fn displaced_boundary_end(&mut self, conditional: Node<'_>) -> Option<usize> {
130        let key = PreprocessorContextNodeKey {
131            id: conditional.id(),
132            start_byte: conditional.start_byte(),
133            end_byte: conditional.end_byte(),
134        };
135        if let Some(boundary) = self.displaced_boundaries.get(&key) {
136            return *boundary;
137        }
138        let boundary =
139            cpp_displaced_preprocessor_boundary(conditional).map(|boundary| boundary.end_byte);
140        if self.displaced_boundaries.len() < PREPROCESSOR_CONTEXT_CACHE_CAP {
141            self.displaced_boundaries.insert(key, boundary);
142        }
143        boundary
144    }
145}
146
147#[cfg(any(test, feature = "test-support"))]
148thread_local! {
149    static BOUNDED_VISIBILITY_DECLARATION_READ_COUNT: Cell<usize> = const { Cell::new(0) };
150    static BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT: Cell<usize> = const { Cell::new(0) };
151}
152
153#[derive(Clone, Copy, PartialEq, Eq)]
154pub enum TargetKind {
155    Type,
156    Constructor,
157    FreeFunction,
158    Method,
159    GlobalField,
160    MemberField,
161    Macro,
162}
163
164pub enum LexicalTypeResolution {
165    Resolved {
166        unit: CodeUnit,
167        components: Vec<String>,
168        candidates: Vec<CodeUnit>,
169    },
170    Ambiguous,
171    Missing,
172}
173
174#[derive(Clone, Copy)]
175enum TypeCandidateResolution<'a> {
176    Canonical,
177    PreserveAlias,
178    PreserveTarget(&'a CodeUnit),
179}
180
181/// Why a name did not reduce to one indexed type declaration.
182///
183/// The two answers are not interchangeable. `Ambiguous` means the index holds
184/// several declarations and the caller must choose; `Unresolvable` means the
185/// index holds none, which is a boundary the workspace cannot see past. A
186/// `using`/`typedef` alias to a template parameter or to a standard-library
187/// type is unresolvable, and reporting it as ambiguity produced an `ambiguous`
188/// answer with an empty candidate list (#1828).
189#[derive(Clone, Copy, Debug, PartialEq, Eq)]
190enum TypeCandidateFailure {
191    Ambiguous,
192    Unresolvable,
193}
194
195impl TypeCandidateFailure {
196    fn lexical_resolution(self) -> LexicalTypeResolution {
197        match self {
198            Self::Ambiguous => LexicalTypeResolution::Ambiguous,
199            Self::Unresolvable => LexicalTypeResolution::Missing,
200        }
201    }
202}
203
204pub enum LexicalCallableValueResolution {
205    Type(CodeUnit),
206    FreeFunction(CodeUnit),
207    Ambiguous,
208    Missing,
209}
210
211pub enum UsingEnumMemberResolution {
212    Resolved { owner: CodeUnit, member: CodeUnit },
213    Ambiguous,
214    Missing,
215}
216
217pub enum NamespaceValueResolution {
218    Resolved,
219    Ambiguous,
220    Missing,
221}
222
223#[derive(Clone, Debug, PartialEq, Eq)]
224pub enum OrdinaryMacroReferenceResolution {
225    Resolved(CodeUnit),
226    Ambiguous,
227    Missing,
228}
229
230#[derive(Clone, Debug, PartialEq, Eq)]
231pub enum RecoveredCReferenceRanges {
232    Complete(Vec<Range>),
233    LimitExceeded,
234}
235
236pub fn resolve_namespace_value(
237    analyzer: &CppGraphSource<'_>,
238    visibility: &VisibilityIndex<'_>,
239    file: &ProjectFile,
240    namespace: &str,
241    name: &str,
242    before_byte: usize,
243) -> NamespaceValueResolution {
244    let mut matches = Vec::new();
245    for candidate in visibility.visible_identifier_candidates(file, name) {
246        if type_owner_of(analyzer, candidate).is_some()
247            || candidate.package_name() != namespace
248            || (candidate.source() == file
249                && !analyzer
250                    .ranges(candidate)
251                    .iter()
252                    .any(|range| range.start_byte < before_byte))
253            || matches
254                .iter()
255                .any(|existing| same_visible_symbol(existing, candidate))
256        {
257            continue;
258        }
259        matches.push(candidate.clone());
260        if matches.len() > 1 {
261            return NamespaceValueResolution::Ambiguous;
262        }
263    }
264    matches
265        .pop()
266        .map(|_| NamespaceValueResolution::Resolved)
267        .unwrap_or(NamespaceValueResolution::Missing)
268}
269
270pub(crate) struct ScopedUsingEnumOwners {
271    scopes: Vec<Vec<CodeUnit>>,
272}
273
274/// Same-file class and namespace imports collected by the targeted scanner's AST prepass.
275/// Cross-file and inherited class imports are deliberately not inferred without persisted
276/// evidence; a missing imported enumerator therefore remains unproven rather than being
277/// misresolved.
278pub(crate) struct SemanticUsingEnumOwners {
279    class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
280    namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
281}
282
283pub(crate) enum SemanticUsingEnumMemberResolution {
284    Class(UsingEnumMemberResolution),
285    Namespace(UsingEnumMemberResolution),
286    Missing,
287}
288
289impl SemanticUsingEnumOwners {
290    pub(crate) fn new() -> Self {
291        Self {
292            class_imports: HashMap::default(),
293            namespace_imports: HashMap::default(),
294        }
295    }
296
297    pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
298        let imports = self.class_imports.entry(class).or_default();
299        if !imports
300            .iter()
301            .any(|existing| same_visible_symbol(existing, &enum_owner))
302        {
303            imports.push(enum_owner);
304        }
305    }
306
307    pub fn import_namespace(
308        &mut self,
309        namespace: Vec<String>,
310        declaration_byte: usize,
311        enum_owner: CodeUnit,
312    ) {
313        let imports = self.namespace_imports.entry(namespace).or_default();
314        if !imports
315            .iter()
316            .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
317        {
318            imports.push((declaration_byte, enum_owner));
319        }
320    }
321
322    pub fn resolve_member(
323        &self,
324        visibility: &VisibilityIndex<'_>,
325        file: &ProjectFile,
326        class: Option<&CodeUnit>,
327        namespace: &[String],
328        before_byte: usize,
329        name: &str,
330    ) -> SemanticUsingEnumMemberResolution {
331        if let Some(class) = class
332            && let Some((_, imports)) = self
333                .class_imports
334                .iter()
335                .find(|(owner, _)| same_visible_symbol(owner, class))
336        {
337            let resolution =
338                resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
339            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
340                return SemanticUsingEnumMemberResolution::Class(resolution);
341            }
342        }
343        for prefix_len in (0..=namespace.len()).rev() {
344            let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
345                continue;
346            };
347            let owners = imports
348                .iter()
349                .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
350                .map(|(_, owner)| owner);
351            let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
352            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
353                return SemanticUsingEnumMemberResolution::Namespace(resolution);
354            }
355        }
356        SemanticUsingEnumMemberResolution::Missing
357    }
358}
359
360fn resolve_using_enum_member_for_owners<'a>(
361    visibility: &VisibilityIndex<'_>,
362    file: &ProjectFile,
363    owners: impl IntoIterator<Item = &'a CodeUnit>,
364    name: &str,
365) -> UsingEnumMemberResolution {
366    let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
367    for owner in owners {
368        for member in visibility.visible_members_for_owner_name(file, owner, name) {
369            if !member.is_field()
370                || matches.iter().any(|(existing_owner, existing_member)| {
371                    same_visible_symbol(existing_owner, owner)
372                        && same_visible_symbol(existing_member, member)
373                })
374            {
375                continue;
376            }
377            matches.push((owner.clone(), member.clone()));
378        }
379    }
380    match matches.len() {
381        0 => UsingEnumMemberResolution::Missing,
382        1 => {
383            let (owner, member) = matches.pop().expect("one using-enum match");
384            UsingEnumMemberResolution::Resolved { owner, member }
385        }
386        _ => UsingEnumMemberResolution::Ambiguous,
387    }
388}
389
390impl ScopedUsingEnumOwners {
391    pub(crate) fn new() -> Self {
392        Self {
393            scopes: vec![Vec::new()],
394        }
395    }
396
397    pub fn enter_scope(&mut self) {
398        self.scopes.push(Vec::new());
399    }
400
401    pub fn exit_scope(&mut self) {
402        brokk_bifrost_core::analyzer::usages::exit_nested_scope(&mut self.scopes);
403    }
404
405    pub fn import(&mut self, owner: CodeUnit) {
406        let scope = self
407            .scopes
408            .last_mut()
409            .expect("using-enum scope stack is never empty");
410        if !scope
411            .iter()
412            .any(|existing| same_visible_symbol(existing, &owner))
413        {
414            scope.push(owner);
415        }
416    }
417
418    pub fn resolve_member(
419        &self,
420        visibility: &VisibilityIndex<'_>,
421        file: &ProjectFile,
422        name: &str,
423    ) -> UsingEnumMemberResolution {
424        for scope in self.scopes.iter().rev() {
425            let resolution =
426                resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
427            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
428                return resolution;
429            }
430        }
431        UsingEnumMemberResolution::Missing
432    }
433}
434
435#[derive(Clone)]
436pub struct TargetSpec {
437    pub target: CodeUnit,
438    pub kind: TargetKind,
439    pub owner: Option<CodeUnit>,
440    pub member_name: String,
441    pub callable_arity: Option<CallableArity>,
442    pub activated_callable_arities: Vec<ActivatedCallableArity>,
443    pub param_types: Option<Vec<String>>,
444    pub enum_owner_kind: EnumOwnerKind,
445    pub owner_is_forward_declaration: bool,
446    pub callable_has_definition_body: bool,
447}
448
449#[derive(Clone, Copy)]
450pub struct ActivatedCallableArity {
451    pub activation_byte: usize,
452    pub arity: CallableArity,
453}
454
455#[derive(Debug, PartialEq, Eq, Hash)]
456pub struct TypeScanKey {
457    target: LogicalSymbolKey,
458    member_name: String,
459}
460
461#[derive(Clone, Debug, PartialEq, Eq, Hash)]
462struct LogicalSymbolKey {
463    kind: CodeUnitType,
464    fq_name: String,
465    signature: Option<String>,
466}
467
468struct ResolvedTypeOwner {
469    unit: CodeUnit,
470    is_forward_declaration: bool,
471}
472
473impl TargetSpec {
474    pub fn type_scan_key(&self) -> Option<TypeScanKey> {
475        (self.kind == TargetKind::Type).then(|| TypeScanKey {
476            target: logical_symbol_key(&self.target),
477            member_name: self.member_name.clone(),
478        })
479    }
480
481    pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
482        if target.is_class() {
483            return Some(Self::new(
484                target.clone(),
485                TargetKind::Type,
486                Some(target.clone()),
487                target.identifier().to_string(),
488                None,
489                None,
490            ));
491        }
492
493        if target.is_field() {
494            // A namespace (module) is not a receiver: a namespace-scoped constant such as
495            // `example::DefaultPrefix` is referenced unqualified from inside the namespace and
496            // qualified from outside, exactly like a global. Treating a module owner as a
497            // member-field owner makes the receiver/owner-context match reject every valid
498            // reference, so resolve it as a global field instead.
499            let owner = type_owner_of(analyzer, target);
500            let kind = if owner.is_some() {
501                TargetKind::MemberField
502            } else {
503                TargetKind::GlobalField
504            };
505            let enum_owner_kind = owner
506                .as_ref()
507                .map(|owner| classify_enum_owner(analyzer, owner))
508                .unwrap_or(EnumOwnerKind::NonEnum);
509            let mut spec = Self::new(
510                target.clone(),
511                kind,
512                owner,
513                target.identifier().to_string(),
514                None,
515                None,
516            );
517            spec.enum_owner_kind = enum_owner_kind;
518            return Some(spec);
519        }
520
521        if target.is_function() {
522            // Free functions declared inside a namespace have a module owner; that namespace is
523            // not a call receiver, so resolve them as free functions rather than methods.
524            let owner_resolution = target_type_owner_resolution(analyzer, target);
525            let owner_is_forward_declaration = owner_resolution
526                .as_ref()
527                .is_some_and(|owner| owner.is_forward_declaration);
528            let owner = owner_resolution.map(|owner| owner.unit);
529            let kind = if owner.as_ref().is_some_and(|owner| {
530                target.identifier() == owner.identifier()
531                    || analyzer
532                        .cpp
533                        .and_then(|cpp| cpp.template_metadata(owner))
534                        .is_some_and(|metadata| metadata.primary_name == target.identifier())
535            }) {
536                TargetKind::Constructor
537            } else if owner.is_some() {
538                TargetKind::Method
539            } else {
540                TargetKind::FreeFunction
541            };
542            let mut spec = Self::new(
543                target.clone(),
544                kind,
545                owner,
546                target.identifier().to_string(),
547                cpp_callable_arity(analyzer, target),
548                cpp_callable_parameter_types(analyzer, target),
549            );
550            spec.owner_is_forward_declaration = owner_is_forward_declaration;
551            spec.callable_has_definition_body =
552                callable_target_has_definition_body(analyzer, target);
553            return Some(spec);
554        }
555
556        if target.is_macro() {
557            return Some(Self::new(
558                target.clone(),
559                TargetKind::Macro,
560                None,
561                target.identifier().to_string(),
562                None,
563                None,
564            ));
565        }
566
567        None
568    }
569
570    pub fn with_visible_callable_arities<'a>(
571        &'a self,
572        analyzer: &CppGraphSource<'_>,
573        cpp: &dyn CppSource,
574        visibility: &VisibilityIndex<'_>,
575        file: &ProjectFile,
576        prepared: &PreparedSyntaxTree,
577    ) -> Cow<'a, Self> {
578        let macro_parameter_arity =
579            visibility.callable_parameter_macro_arity(&self.target, self.param_types.as_deref());
580        let activated_callable_arities =
581            visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
582        if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
583            return Cow::Borrowed(self);
584        }
585        let mut effective = self.clone();
586        if let Some(macro_parameter_arity) = macro_parameter_arity {
587            effective.callable_arity = Some(macro_parameter_arity);
588        }
589        effective.activated_callable_arities = activated_callable_arities;
590        Cow::Owned(effective)
591    }
592
593    pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
594        let base = self.callable_arity?;
595        Some(
596            self.activated_callable_arities
597                .iter()
598                .filter(|candidate| candidate.activation_byte <= byte)
599                .fold(base, |arity, candidate| {
600                    merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
601                }),
602        )
603    }
604
605    pub fn new(
606        target: CodeUnit,
607        kind: TargetKind,
608        owner: Option<CodeUnit>,
609        member_name: String,
610        callable_arity: Option<CallableArity>,
611        param_types: Option<Vec<String>>,
612    ) -> Self {
613        Self {
614            target,
615            kind,
616            owner,
617            member_name,
618            callable_arity,
619            activated_callable_arities: Vec::new(),
620            param_types,
621            enum_owner_kind: EnumOwnerKind::NonEnum,
622            owner_is_forward_declaration: false,
623            callable_has_definition_body: false,
624        }
625    }
626}
627
628fn callable_target_has_definition_body(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> bool {
629    analyzer
630        .signature_metadata(target)
631        .iter()
632        .any(|metadata| !metadata.is_declaration_only())
633}
634
635fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
636    LogicalSymbolKey {
637        kind: unit.kind(),
638        fq_name: unit.fq_name(),
639        signature: unit.signature().map(str::to_string),
640    }
641}
642
643fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
644    let Some(facts) = analyzer.declaration_source_properties(owner) else {
645        return EnumOwnerKind::NonEnum;
646    };
647    let Some(first) = facts.first() else {
648        return EnumOwnerKind::NonEnum;
649    };
650    if facts.iter().all(|fact| fact.enum_kind == first.enum_kind) {
651        first.enum_kind
652    } else {
653        EnumOwnerKind::NonEnum
654    }
655}
656
657#[derive(Clone, PartialEq, Eq, Hash)]
658pub struct CppScanBinding {
659    pub unit: Option<CodeUnit>,
660    pub type_name: Option<String>,
661    pub indirection: i32,
662}
663
664impl CppScanBinding {
665    pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
666        Self::from_type_name(cpp_name_for(&unit), Some(unit), indirection)
667    }
668
669    pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
670        Self {
671            type_name: Some(type_name),
672            unit,
673            indirection,
674        }
675    }
676
677    pub fn as_arg_type(&self) -> Option<CppArgType> {
678        let name = self
679            .type_name
680            .clone()
681            .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
682        Some(CppArgType {
683            name,
684            unit: self.unit.clone(),
685            indirection: self.indirection,
686            pointee_const: false,
687        })
688    }
689}
690
691type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
692pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
693pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
694type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
695type MacroEnvironmentCheckpointCell = Arc<OnceLock<MacroEnvironmentCheckpoints>>;
696type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
697type MacroLexicalTemplateCache =
698    HashMap<(ProjectFile, usize), Option<crate::graph::macro_lexical::MacroTemplate>>;
699
700type MacroLocalBindingTemplateCache =
701    HashMap<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>;
702type MacroReplacementBodyCache = HashMap<(ProjectFile, usize), Option<Arc<ParsedReplacementBody>>>;
703type MacroTypeParameterCache = HashMap<(ProjectFile, usize), Option<Arc<[usize]>>>;
704type StructuredIncludeFactCell = Arc<OnceLock<Arc<[StructuredIncludeFact]>>>;
705
706struct StructuredIncludeFact {
707    start_byte: usize,
708    end_byte: usize,
709    path: String,
710}
711
712#[derive(Clone, Default)]
713pub struct MacroEnvironment {
714    bindings: HashMap<String, MacroBinding>,
715    known_undefined_names: HashSet<String>,
716    /// Names the translation unit's compile command proves defined (#2011):
717    /// the `-D`s that survive command ordering, intersected across every
718    /// configuration naming the TU. Seeded once at TU start. An explicit
719    /// `#undef` seen later lands in `known_undefined_names` and wins.
720    build_proven_defines: HashSet<String>,
721    unknown_names: bool,
722    applied_pragma_once_files: HashSet<ProjectFile>,
723    maybe_applied_pragma_once_files: HashSet<ProjectFile>,
724}
725
726/// How many macro events one checkpoint window may cover.
727///
728/// A request for an environment replays only the events between the nearest
729/// earlier checkpoint and its own frontier, so one file's whole scan costs its
730/// event count (the checkpoint build) plus this many applications per request,
731/// whatever order the requests arrive in. The forward cursor this replaced was
732/// optimal for one worker reading one file in byte order and quadratic for the
733/// inverse, which asks many workers for positions that move backwards (#1496).
734pub const MACRO_ENVIRONMENT_CHECKPOINT_STRIDE: usize = 32;
735
736/// One event prefix of a file whose environment the index keeps.
737struct MacroEnvironmentCheckpoint {
738    /// How many of the file's events this environment has applied.
739    frontier: usize,
740    environment: Arc<MacroEnvironment>,
741}
742
743/// The replay checkpoints for one file's macro events, ascending by frontier
744/// and always starting at frontier zero (the compile-proven defines alone).
745///
746/// Checkpoints use an adaptive stride no greater than
747/// [`MACRO_ENVIRONMENT_CHECKPOINT_STRIDE`], and one lands directly after every
748/// `#include` event. Applying an include event replays the included file's
749/// complete event list, so keeping one there holds that unbounded cost out of
750/// every later replay window.
751struct MacroEnvironmentCheckpoints {
752    checkpoints: Vec<MacroEnvironmentCheckpoint>,
753}
754
755impl MacroEnvironmentCheckpoints {
756    /// The latest checkpoint at or before `frontier`.
757    fn at_or_before(&self, frontier: usize) -> &MacroEnvironmentCheckpoint {
758        let index = self
759            .checkpoints
760            .partition_point(|checkpoint| checkpoint.frontier <= frontier);
761        assert!(
762            index > 0,
763            "a checkpoint vector starts at frontier zero, which precedes every request"
764        );
765        &self.checkpoints[index - 1]
766    }
767}
768
769fn clone_or_default_file_cell<T: Default + Clone>(
770    cells: &Mutex<HashMap<ProjectFile, T>>,
771    file: &ProjectFile,
772    poison_message: &str,
773) -> T {
774    cells
775        .lock()
776        .expect(poison_message)
777        .entry(file.clone())
778        .or_default()
779        .clone()
780}
781
782impl MacroEnvironment {
783    fn binding(&self, name: &str) -> Option<&MacroBinding> {
784        self.bindings.get(name)
785    }
786
787    fn may_bind(&self, name: &str) -> bool {
788        self.bindings.contains_key(name) || self.unknown_names
789    }
790
791    fn insert(&mut self, name: String, binding: MacroBinding) {
792        self.known_undefined_names.remove(&name);
793        self.bindings.insert(name, binding);
794    }
795
796    fn remove(&mut self, name: &str) {
797        self.bindings.remove(name);
798        self.known_undefined_names.insert(name.to_string());
799    }
800
801    fn remove_known_undefined(&mut self, name: &str) {
802        self.known_undefined_names.remove(name);
803    }
804
805    fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
806        for binding in self.bindings.values_mut() {
807            *binding = MacroBinding::uncertain_from(binding, source, byte);
808        }
809        self.known_undefined_names.clear();
810        // An untracked include could `#undef` a command-line define, so the
811        // may-hold filter must stop treating the build facts as decisive from
812        // here on. The additive proof path keeps its facts: they still hold at
813        // the include chain's activation point.
814        self.build_proven_defines.clear();
815        self.unknown_names = true;
816    }
817
818    fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
819        guards.iter().all(|guard| self.guard_may_hold(guard))
820    }
821
822    fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
823        self.boolean_guard_may_hold(&guard.as_boolean_expression())
824    }
825
826    fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
827        match expression {
828            BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
829            BooleanGuardExpression::Undefined(name) => {
830                self.bindings
831                    .get(name)
832                    .is_none_or(|binding| !binding.is_exact())
833                    && (!self.build_proven_defines.contains(name)
834                        || self.known_undefined_names.contains(name))
835            }
836            BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
837            BooleanGuardExpression::Opaque(_)
838            | BooleanGuardExpression::NegatedOpaque(_)
839            | BooleanGuardExpression::Constant(true) => true,
840            BooleanGuardExpression::Constant(false) => false,
841            BooleanGuardExpression::All(expressions) => expressions
842                .iter()
843                .all(|expression| self.boolean_guard_may_hold(expression)),
844            BooleanGuardExpression::Any(expressions) => expressions
845                .iter()
846                .any(|expression| self.boolean_guard_may_hold(expression)),
847        }
848    }
849}
850
851fn any_visible_source_file_matches(
852    visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
853    file: &ProjectFile,
854    mut predicate: impl FnMut(&ProjectFile) -> bool,
855) -> bool {
856    match visible_source_files_by_root.get(file) {
857        Some(files) => files.iter().any(predicate),
858        None => predicate(file),
859    }
860}
861
862#[derive(Clone)]
863pub enum EffectiveUsingTarget {
864    Ordinary {
865        name: String,
866        target_components: Vec<String>,
867        global: bool,
868    },
869    Namespace {
870        namespace_components: Vec<String>,
871        global: bool,
872    },
873}
874
875#[derive(Clone)]
876pub struct OrdinaryTypeImport {
877    pub target: EffectiveUsingTarget,
878    pub source: ProjectFile,
879    pub declaration_byte: usize,
880    pub scope_start: usize,
881    pub scope_end: usize,
882    pub scope_depth: usize,
883    pub block_scope: bool,
884    pub lexical_depth: usize,
885    pub declaration_namespace: Vec<String>,
886    pub namespace_scope: Option<Vec<String>>,
887    pub resolved_target_components: Option<Vec<String>>,
888    pub required_guards: HashSet<PreprocessorGuard>,
889}
890
891#[derive(Clone)]
892pub struct ConditionalIncludeProjection {
893    pub activation_byte: usize,
894    pub required_guards: HashSet<PreprocessorGuard>,
895    /// The subset of `required_guards` a lone `#if` contributes: a branch no
896    /// sibling `#else` completes, so no configuration is obliged to take it.
897    /// [`IncludePathAdmission::Compatible`] still demands those.
898    pub partial_guards: HashSet<PreprocessorGuard>,
899}
900
901#[derive(Default)]
902pub struct SourceUsingIndex {
903    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
904    pub directives: Vec<OrdinaryTypeImport>,
905}
906
907#[derive(Default)]
908pub struct ProjectUsingIndex {
909    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
910    pub directives: Vec<OrdinaryTypeImport>,
911}
912
913type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
914
915pub struct EffectiveUsingIndex {
916    projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
917}
918
919impl EffectiveUsingIndex {
920    fn new(_root: ProjectFile) -> Self {
921        Self {
922            projected_by_name: Mutex::new(HashMap::default()),
923        }
924    }
925
926    pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
927        self.projected_by_name
928            .lock()
929            .expect("C++ effective-using projection cache poisoned")
930            .entry(name.to_string())
931            .or_default()
932            .clone()
933    }
934}
935
936pub enum OrdinaryTypeImportResolution {
937    Resolved {
938        target: CodeUnit,
939        target_components: Vec<String>,
940        lexical_depth: usize,
941        is_direct: bool,
942    },
943    Ambiguous {
944        lexical_depth: usize,
945    },
946    Missing,
947}
948
949type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
950type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
951type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
952type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
953type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
954type ParserAliasTargetMatchCell = Arc<OnceLock<bool>>;
955type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
956type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
957
958/// One callable declaration's inputs to [`VisibilityIndex::same_logical_callable`],
959/// read from its declaration syntax rather than from its persisted signature
960/// string: the comparable shape of each parameter, and the trailing identity
961/// suffix that shape does not carry.
962struct ExtractedComparable {
963    shapes: Vec<CppComparableSlot>,
964    suffix: String,
965    /// The template levels of the same occurrence, or `None` when its syntax
966    /// is unavailable or outside what [`crate::template_heads`] models.
967    template: Option<CppTemplateContext>,
968}
969
970/// How many alias hops [`VisibilityIndex::same_logical_callable`] follows
971/// before giving up on a written type name. A visited set already stops a
972/// cycle; this stops an adversarially long chain from costing a lookup per hop.
973const MAX_COMPARABLE_ALIAS_HOPS: usize = 32;
974
975/// Guard admission for a conditional include path. Exact visibility
976/// (`foreign_declaration_reachable_at_reference`) always uses `Proven`: the
977/// effective source and build context must imply the path. Candidate discovery
978/// (`foreign_declaration_may_be_reachable_from_raw_guards`) takes
979/// `include_path_admission(file)`, which is `Compatible` for completed families
980/// when no compile coverage describes the file; partial-family requirements
981/// still need proof. Discovery alone never establishes an exact definition or
982/// a proven inverse hit (#6902).
983#[derive(Clone, Copy, Debug, PartialEq, Eq)]
984enum IncludePathAdmission {
985    Proven,
986    Compatible,
987}
988
989impl IncludePathAdmission {
990    fn admits(
991        self,
992        required: &HashSet<PreprocessorGuard>,
993        partial: &HashSet<PreprocessorGuard>,
994        reference_guards: Option<&HashSet<PreprocessorGuard>>,
995    ) -> bool {
996        match self {
997            Self::Proven => guard_requirements_hold_at_reference(required, reference_guards),
998            Self::Compatible => {
999                guard_requirements_hold_at_reference(partial, reference_guards)
1000                    && guards_compatible_at_reference(required, reference_guards)
1001            }
1002        }
1003    }
1004}
1005
1006/// Per-query C++ visibility facts with a borrowed source and scope proof.
1007/// Cache only `data()` across calls, and reattach the current source and token
1008/// with `from_data`; cloning the analyzer would lose its query read cache.
1009pub struct VisibilityIndex<'a> {
1010    cpp: &'a dyn CppSource,
1011    token: QueryToken<'a>,
1012    data: Arc<VisibilityIndexData>,
1013}
1014
1015impl std::ops::Deref for VisibilityIndex<'_> {
1016    type Target = VisibilityIndexData;
1017
1018    fn deref(&self) -> &Self::Target {
1019        &self.data
1020    }
1021}
1022
1023/// Owned visibility facts. They retain no source or request-scope borrow.
1024/// The caller keeps these facts inside the query that built them.
1025pub struct VisibilityIndexData {
1026    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1027    visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
1028    global_field_internal_linkage: HashMap<CodeUnit, bool>,
1029    visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
1030    alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
1031    visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
1032    parser_alias_target_matches:
1033        RwLock<HashMap<(ProjectFile, String, LogicalSymbolKey), ParserAliasTargetMatchCell>>,
1034    ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
1035    project_using_index: OnceLock<ProjectUsingIndex>,
1036    namespace_alias_sites: Mutex<HashMap<ProjectFile, super::namespace_alias::NamespaceAliasCell>>,
1037    callable_reference_specs:
1038        Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
1039    structured_include_fact_cells: Mutex<HashMap<ProjectFile, StructuredIncludeFactCell>>,
1040    include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
1041    compile_proven_guard_cells: Mutex<HashMap<ProjectFile, Arc<HashSet<PreprocessorGuard>>>>,
1042    include_path_admission_cells: Mutex<HashMap<ProjectFile, IncludePathAdmission>>,
1043    conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
1044    #[cfg(any(test, feature = "test-support"))]
1045    conditional_include_projection_index_build_count: AtomicUsize,
1046    #[cfg(any(test, feature = "test-support"))]
1047    conditional_include_projection_state_count: AtomicUsize,
1048    #[cfg(any(test, feature = "test-support"))]
1049    conditional_include_target_state_count: AtomicUsize,
1050    #[cfg(any(test, feature = "test-support"))]
1051    include_activation_build_count: AtomicUsize,
1052    #[cfg(any(test, feature = "test-support"))]
1053    using_donor_activation_count: AtomicUsize,
1054    #[cfg(any(test, feature = "test-support"))]
1055    using_namespace_lookup_count: AtomicUsize,
1056    #[cfg(any(test, feature = "test-support"))]
1057    using_name_candidate_inspection_count: AtomicUsize,
1058    #[cfg(any(test, feature = "test-support"))]
1059    callable_reference_spec_build_count: AtomicUsize,
1060    #[cfg(any(test, feature = "test-support"))]
1061    alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
1062    #[cfg(any(test, feature = "test-support"))]
1063    visible_parser_alias_name_set_build_count: AtomicUsize,
1064    parser_alias_fallback_calls: AtomicUsize,
1065    parser_alias_fallback_files: AtomicUsize,
1066    parser_alias_source_parses: AtomicUsize,
1067    parser_alias_fallback_elapsed_micros: AtomicUsize,
1068    field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
1069    structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
1070    callable_comparables: Mutex<HashMap<CodeUnit, Option<Arc<ExtractedComparable>>>>,
1071    class_definition_agreements: Mutex<HashMap<(CodeUnit, CodeUnit), bool>>,
1072    /// Complete callable metadata is expensive for reconciled C++ members:
1073    /// each read can rebuild the declaration/definition evidence. Reuse it
1074    /// across the member, arity, identity, and navigation phases of this
1075    /// visibility query, with an explicit cap so the query never retains a
1076    /// workspace-sized unit table.
1077    callable_metadata: Mutex<HashMap<CodeUnit, Arc<Vec<SignatureMetadata>>>>,
1078    comparable_name_declarations: Mutex<HashMap<StructuredTypeName, Option<CodeUnit>>>,
1079    indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
1080    indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
1081    precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1082    c_tag_kind_cache: Mutex<HashMap<CodeUnit, Option<CppCTagKind>>>,
1083    c_tag_complete_definition_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1084    macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
1085    macro_event_name_sets: Mutex<HashMap<ProjectFile, Arc<HashSet<String>>>>,
1086    pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
1087    // The environment at selected event prefixes of each file, built once and read by every
1088    // worker. The authoritative differential shares this index across target workers whose
1089    // frontiers interleave arbitrarily and move backwards, so a forward cursor -- per worker or
1090    // not -- replayed a file's events once per backward request. Checkpoints answer any position
1091    // with a binary search and at most one stride of replay, and being immutable they need no
1092    // per-worker copy (#1496).
1093    macro_environment_checkpoints: Mutex<HashMap<ProjectFile, MacroEnvironmentCheckpointCell>>,
1094    macro_replacements: Mutex<MacroReplacementCache>,
1095    macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
1096    pub(crate) macro_lexical_templates: Mutex<MacroLexicalTemplateCache>,
1097    macro_lexical_indexes:
1098        Mutex<HashMap<ProjectFile, Arc<crate::graph::macro_lexical::MacroLexicalIndex>>>,
1099    macro_replacement_bodies: Mutex<MacroReplacementBodyCache>,
1100    macro_type_parameters: Mutex<MacroTypeParameterCache>,
1101    callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
1102    #[cfg(any(test, feature = "test-support"))]
1103    pub macro_replacement_parse_count: AtomicUsize,
1104    #[cfg(any(test, feature = "test-support"))]
1105    pub macro_event_application_count: AtomicUsize,
1106    /// How many files had their checkpoint vector built. One build per file
1107    /// per query even when workers race for the same file.
1108    #[cfg(any(test, feature = "test-support"))]
1109    pub macro_environment_checkpoint_build_count: AtomicUsize,
1110    /// How many requests landed off a checkpoint and so had to copy one and
1111    /// replay the events after it.
1112    #[cfg(any(test, feature = "test-support"))]
1113    pub macro_environment_copy_count: AtomicUsize,
1114    #[cfg(any(test, feature = "test-support"))]
1115    pub macro_environment_request_count: AtomicUsize,
1116    cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
1117    cpp_template_families: HashMap<String, Vec<CodeUnit>>,
1118    #[cfg(any(test, feature = "test-support"))]
1119    qualified_candidate_inspections: AtomicUsize,
1120    #[cfg(any(test, feature = "test-support"))]
1121    target_preserving_type_resolution_count: AtomicUsize,
1122    #[cfg(any(test, feature = "test-support"))]
1123    visibility_identifier_lookup_count: usize,
1124    #[cfg(any(test, feature = "test-support"))]
1125    visibility_identifier_batch_count: usize,
1126}
1127
1128impl Drop for VisibilityIndexData {
1129    fn drop(&mut self) {
1130        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_none() {
1131            return;
1132        }
1133        #[cfg(any(test, feature = "test-support"))]
1134        eprintln!(
1135            "BIFROST_CPP_MACRO_STATS requests={} copies={} checkpoint_builds={} applications={}",
1136            self.macro_environment_request_count.load(Ordering::Relaxed),
1137            self.macro_environment_copy_count.load(Ordering::Relaxed),
1138            self.macro_environment_checkpoint_build_count
1139                .load(Ordering::Relaxed),
1140            self.macro_event_application_count.load(Ordering::Relaxed),
1141        );
1142        let calls = self.parser_alias_fallback_calls.load(Ordering::Relaxed);
1143        if calls == 0 {
1144            return;
1145        }
1146        eprintln!(
1147            "BIFROST_CPP_ALIAS_FALLBACK_STATS calls={} files={} source_parses={} elapsed_ms={}",
1148            calls,
1149            self.parser_alias_fallback_files.load(Ordering::Relaxed),
1150            self.parser_alias_source_parses.load(Ordering::Relaxed),
1151            self.parser_alias_fallback_elapsed_micros
1152                .load(Ordering::Relaxed)
1153                / 1_000,
1154        );
1155    }
1156}
1157
1158#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1159pub enum PreprocessorGuard {
1160    Defined(String),
1161    Undefined(String),
1162    Boolean(BooleanGuardExpression),
1163    Expression(String),
1164    NegatedExpression(String),
1165    Constant(bool),
1166}
1167
1168#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1169pub enum BooleanGuardExpression {
1170    Defined(String),
1171    Undefined(String),
1172    Truthy(String),
1173    Falsy(String),
1174    Opaque(String),
1175    NegatedOpaque(String),
1176    All(Vec<BooleanGuardExpression>),
1177    Any(Vec<BooleanGuardExpression>),
1178    Constant(bool),
1179}
1180
1181impl BooleanGuardExpression {
1182    fn negated(&self) -> Self {
1183        match self {
1184            Self::Defined(name) => Self::Undefined(name.clone()),
1185            Self::Undefined(name) => Self::Defined(name.clone()),
1186            Self::Truthy(name) => Self::Falsy(name.clone()),
1187            Self::Falsy(name) => Self::Truthy(name.clone()),
1188            Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
1189            Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
1190            Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
1191            Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
1192            Self::Constant(value) => Self::Constant(!value),
1193        }
1194    }
1195
1196    fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
1197        Self::normalized(expressions, true)
1198    }
1199
1200    fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
1201        Self::normalized(expressions, false)
1202    }
1203
1204    fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
1205        let mut normalized = Vec::new();
1206        for expression in expressions {
1207            match expression {
1208                Self::All(nested) if conjunction => normalized.extend(nested),
1209                Self::Any(nested) if !conjunction => normalized.extend(nested),
1210                Self::Constant(value) if value == conjunction => {}
1211                Self::Constant(value) => return Self::Constant(value),
1212                expression => normalized.push(expression),
1213            }
1214        }
1215        normalized.sort_unstable();
1216        normalized.dedup();
1217        match normalized.len() {
1218            0 => Self::Constant(conjunction),
1219            1 => normalized.pop().expect("one Boolean guard expression"),
1220            _ if conjunction => Self::All(normalized),
1221            _ => Self::Any(normalized),
1222        }
1223    }
1224
1225    fn implies(&self, required: &Self) -> bool {
1226        if self == required
1227            || matches!(self, Self::Constant(false))
1228            || matches!(required, Self::Constant(true))
1229        {
1230            return true;
1231        }
1232        if matches!(
1233            (self, required),
1234            (Self::Truthy(active), Self::Defined(required))
1235                | (Self::Undefined(active), Self::Falsy(required))
1236                if active == required
1237        ) {
1238            return true;
1239        }
1240        match self {
1241            Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
1242            Self::All(active) => match required {
1243                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1244                _ => active.iter().any(|expression| expression.implies(required)),
1245            },
1246            _ => match required {
1247                Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
1248                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1249                _ => false,
1250            },
1251        }
1252    }
1253
1254    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1255        match self {
1256            Self::Defined(name)
1257            | Self::Undefined(name)
1258            | Self::Truthy(name)
1259            | Self::Falsy(name) => name == macro_name,
1260            // Opaque expressions have structured conditional ownership but no
1261            // structured macro operands, so any mutation may change them.
1262            Self::Opaque(_) | Self::NegatedOpaque(_) => true,
1263            Self::All(expressions) | Self::Any(expressions) => expressions
1264                .iter()
1265                .any(|expression| expression.may_depend_on_macro(macro_name)),
1266            Self::Constant(_) => false,
1267        }
1268    }
1269
1270    pub fn heap_size(&self) -> usize {
1271        match self {
1272            Self::Defined(value)
1273            | Self::Undefined(value)
1274            | Self::Truthy(value)
1275            | Self::Falsy(value)
1276            | Self::Opaque(value)
1277            | Self::NegatedOpaque(value) => value.len(),
1278            Self::All(expressions) | Self::Any(expressions) => {
1279                expressions
1280                    .iter()
1281                    .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
1282                        size.saturating_add(std::mem::size_of::<Self>())
1283                            .saturating_add(expression.heap_size())
1284                    })
1285            }
1286            Self::Constant(_) => 0,
1287        }
1288    }
1289}
1290
1291impl PreprocessorGuard {
1292    fn as_boolean_expression(&self) -> BooleanGuardExpression {
1293        match self {
1294            Self::Defined(name) => BooleanGuardExpression::Defined(name.clone()),
1295            Self::Undefined(name) => BooleanGuardExpression::Undefined(name.clone()),
1296            Self::Boolean(expression) => expression.clone(),
1297            Self::Constant(value) => BooleanGuardExpression::Constant(*value),
1298            Self::Expression(expression) => BooleanGuardExpression::Opaque(expression.clone()),
1299            Self::NegatedExpression(expression) => {
1300                BooleanGuardExpression::NegatedOpaque(expression.clone())
1301            }
1302        }
1303    }
1304
1305    fn negated(&self) -> Self {
1306        match self {
1307            Self::Defined(name) => Self::Undefined(name.clone()),
1308            Self::Undefined(name) => Self::Defined(name.clone()),
1309            Self::Boolean(expression) => Self::Boolean(expression.negated()),
1310            Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1311            Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1312            Self::Constant(value) => Self::Constant(!value),
1313        }
1314    }
1315
1316    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1317        match self {
1318            Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1319            Self::Boolean(expression) => expression.may_depend_on_macro(macro_name),
1320            // These expressions could not be lowered to a Boolean operand
1321            // tree, so their dependencies remain unknown.
1322            Self::Expression(_) | Self::NegatedExpression(_) => true,
1323            Self::Constant(_) => false,
1324        }
1325    }
1326}
1327
1328#[derive(Clone, PartialEq, Eq)]
1329pub enum MacroDefinition {
1330    Object {
1331        replacement: String,
1332    },
1333    Function {
1334        parameters: Vec<String>,
1335        replacement: String,
1336    },
1337    VariadicFunction {
1338        parameters: Vec<String>,
1339        replacement: String,
1340    },
1341    Unsupported,
1342}
1343
1344#[derive(Clone, Debug, PartialEq, Eq)]
1345pub enum MacroIncludeProtection {
1346    MacroGuard(String),
1347    PragmaOnce,
1348    None,
1349}
1350
1351enum ParsedMacroReplacement {
1352    Parsed { source: String, tree: Tree },
1353    Unsupported,
1354}
1355
1356/// The sentinel that gives a function-like macro replacement a parseable
1357/// statement context after phase-2 splicing. The only bytes ahead of it are
1358/// this prefix.
1359const MACRO_BODY_SENTINEL_PREFIX: &str = "void __bifrost_macro_body() { ";
1360const MACRO_BODY_SENTINEL_SUFFIX: &str = "; }";
1361
1362// Each parsed byte owns its physical source byte, or an empty range for
1363// synthetic punctuation. Deleted splices own no parsed bytes.
1364type MacroOriginMap = Box<[std::ops::Range<usize>]>;
1365
1366/// A function-like macro replacement parsed inside a sentinel function body.
1367///
1368/// Tree-sitter keeps a `#define NAME(a) ...` replacement as one opaque
1369/// `preproc_arg`. Wrapping that exact byte slice in a function body recovers
1370/// its statements, declarations, and member calls as ordinary C++ structure.
1371/// The spliced text begins at [`Self::body_offset`]. [`Self::file_range`]
1372/// subtracts that prefix and uses physical byte origins to recover a node
1373/// range in the defining replacement.
1374pub struct ParsedReplacementBody {
1375    pub source: String,
1376    pub tree: Tree,
1377    pub body_offset: usize,
1378    pub parameters: Vec<String>,
1379    /// Each parsed replacement byte maps to its original physical byte. A
1380    /// deleted splice creates a gap; a synthetic statement separator maps to
1381    /// an empty range, keeping source-backed ranges exact after substitution.
1382    original_offsets: MacroOriginMap,
1383}
1384
1385impl ParsedReplacementBody {
1386    /// The sentinel function body holding the replacement's statements.
1387    pub fn statements(&self) -> Option<Node<'_>> {
1388        first_descendant_of_kind(self.tree.root_node(), "function_definition")?
1389            .child_by_field_name("body")
1390    }
1391
1392    /// The byte range `node` occupies in the file that defines the macro.
1393    ///
1394    /// `replacement_start` is the logical replacement span's start byte.
1395    /// The origin map accounts for deleted continuations and any synthetic
1396    /// statement separators inserted while parsing the replacement.
1397    pub fn file_range(&self, node: Node<'_>, replacement_start: usize) -> std::ops::Range<usize> {
1398        assert!(
1399            node.start_byte() >= self.body_offset,
1400            "synthetic sentinel node cannot be mapped to a macro replacement"
1401        );
1402        assert!(
1403            node.end_byte() >= self.body_offset,
1404            "synthetic sentinel node cannot be mapped to a macro replacement"
1405        );
1406        let start_offset = node.start_byte() - self.body_offset;
1407        let end_offset = node.end_byte() - self.body_offset;
1408        assert!(start_offset <= end_offset);
1409        let start_origin = self
1410            .original_offsets
1411            .get(start_offset)
1412            .expect("replacement node start must have a source mapping")
1413            .start;
1414        let end_origin = if start_offset == end_offset {
1415            start_origin
1416        } else {
1417            self.original_offsets
1418                .get(end_offset - 1)
1419                .expect("replacement node end must have a source mapping")
1420                .end
1421        };
1422        assert!(start_origin <= end_origin);
1423        let start = replacement_start + start_origin;
1424        let end = replacement_start + end_origin;
1425        start..end
1426    }
1427
1428    /// Whether the replacement names the variadic argument pack.
1429    ///
1430    /// `__VA_ARGS__` parses as an ordinary identifier, so the sentinel tree
1431    /// gives no error for it even though the expansion it stands for is
1432    /// unknown at the definition. Reject it from the parsed tree rather than
1433    /// by scanning the replacement text.
1434    fn expands_variadic_arguments(&self) -> bool {
1435        let mut stack = vec![self.tree.root_node()];
1436        while let Some(node) = stack.pop() {
1437            if matches!(
1438                node.kind(),
1439                "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
1440            ) && node_text(node, &self.source) == "__VA_ARGS__"
1441            {
1442                return true;
1443            }
1444            push_named_children_reversed(node, &mut stack);
1445        }
1446        false
1447    }
1448}
1449
1450/// Parse the value of a C++ integer-literal token when it fits in `i128`.
1451pub fn parse_cpp_integer_literal(text: &str) -> Option<i128> {
1452    let compact = text.chars().filter(|ch| *ch != '\'').collect::<String>();
1453    let (radix, digits_start, digit_matches): (u32, usize, fn(char) -> bool) =
1454        if compact.starts_with("0x") || compact.starts_with("0X") {
1455            (16, 2, |ch| ch.is_ascii_hexdigit())
1456        } else if compact.starts_with("0b") || compact.starts_with("0B") {
1457            (2, 2, |ch| matches!(ch, '0' | '1'))
1458        } else if compact.starts_with('0') && compact.len() > 1 {
1459            (8, 0, |ch| matches!(ch, '0'..='7'))
1460        } else {
1461            (10, 0, |ch| ch.is_ascii_digit())
1462        };
1463    let digit_len = compact[digits_start..]
1464        .chars()
1465        .take_while(|ch| digit_matches(*ch))
1466        .map(char::len_utf8)
1467        .sum::<usize>();
1468    if digit_len == 0 {
1469        return None;
1470    }
1471    let digits_end = digits_start + digit_len;
1472    if !compact[digits_end..]
1473        .chars()
1474        .all(|ch| matches!(ch, 'u' | 'U' | 'l' | 'L' | 'z' | 'Z'))
1475    {
1476        return None;
1477    }
1478    i128::from_str_radix(&compact[digits_start..digits_end], radix).ok()
1479}
1480
1481#[derive(Clone)]
1482enum MacroLocalBindingTypeTemplate {
1483    Parameter(usize),
1484    Fixed(String),
1485}
1486
1487#[derive(Clone)]
1488struct MacroLocalBindingTemplate {
1489    name: String,
1490    declared_type: MacroLocalBindingTypeTemplate,
1491    pointer_depth: i32,
1492}
1493
1494/// A local declaration contributed by one structurally known function-like macro.
1495///
1496/// `type_node` points into the invocation syntax when the replacement's type
1497/// is one of the macro parameters. Consumers can therefore use their normal
1498/// lexical type resolver without parsing replacement text themselves.
1499/// `proven_unit` carries a structured C-tag resolution when the grammar splits
1500/// an explicit `struct` or `union` argument across recovery nodes.
1501pub struct MacroLocalBinding<'tree> {
1502    pub name: String,
1503    pub type_name: String,
1504    pub type_node: Option<Node<'tree>>,
1505    pub pointer_depth: i32,
1506    pub proven_unit: Option<CodeUnit>,
1507}
1508
1509/// The source-backed declaration introduced by a function-like macro. The
1510/// declaration belongs to the macro definition, while the reference range
1511/// returned by [`VisibilityIndex::macro_lexical_references`] belongs to the
1512/// definition body, invocation argument, or caller scope where substitution
1513/// makes the binding visible.
1514#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1515pub enum MacroLexicalBindingKind {
1516    Parameter,
1517    Local,
1518}
1519
1520#[derive(Clone, Debug, PartialEq, Eq)]
1521pub struct MacroLexicalBinding {
1522    pub definition: ProjectFile,
1523    pub kind: MacroLexicalBindingKind,
1524    pub name: String,
1525    pub name_range: std::ops::Range<usize>,
1526    pub declaration_range: std::ops::Range<usize>,
1527}
1528
1529/// Lexical macro references are query-local facts. A cancelled or bounded
1530/// walk is kept distinct from a complete empty enumeration so authoritative
1531/// inverse callers can preserve their completeness status.
1532#[derive(Clone, Debug, Default, PartialEq, Eq)]
1533pub struct MacroLexicalReferences {
1534    pub references: Vec<(std::ops::Range<usize>, MacroLexicalBinding)>,
1535    pub truncated: bool,
1536    pub cancelled: bool,
1537}
1538
1539fn macro_replacement_type_parameters(
1540    body: &ParsedReplacementBody,
1541    parameters: &[String],
1542) -> Option<Vec<usize>> {
1543    let mut found = Vec::new();
1544    let mut stack = vec![body.tree.root_node()];
1545    while let Some(node) = stack.pop() {
1546        let type_position = node.kind() == "type_identifier"
1547            || (node.kind() == "identifier"
1548                && node.parent().is_some_and(|parent| {
1549                    parent.kind() == "type_descriptor"
1550                        && parent.child_by_field_name("type") == Some(node)
1551                }));
1552        let offsetof_type_position = node.kind() == "identifier"
1553            && node
1554                .parent()
1555                .filter(|parent| parent.kind() == "argument_list")
1556                .and_then(|arguments| arguments.parent())
1557                .is_some_and(|call| {
1558                    call.kind() == "call_expression"
1559                        && call
1560                            .child_by_field_name("function")
1561                            .is_some_and(|function| {
1562                                function.kind() == "identifier"
1563                                    && node_text(function, &body.source) == "offsetof"
1564                            })
1565                        && call
1566                            .child_by_field_name("arguments")
1567                            .is_some_and(|arguments| {
1568                                argument_children(arguments).next() == Some(node)
1569                            })
1570                });
1571        if (type_position || offsetof_type_position)
1572            && let Some(index) = parameters
1573                .iter()
1574                .position(|parameter| parameter == node_text(node, &body.source))
1575            && !found.contains(&index)
1576        {
1577            found.push(index);
1578        }
1579        push_named_children_reversed(node, &mut stack);
1580    }
1581    (!found.is_empty()).then_some(found)
1582}
1583
1584fn macro_replacement_type_parameter(
1585    body: &ParsedReplacementBody,
1586    parameters: &[String],
1587) -> Option<usize> {
1588    let mut parameters = macro_replacement_type_parameters(body, parameters)?;
1589    (parameters.len() == 1).then(|| parameters.pop().unwrap())
1590}
1591
1592/// The grammar retains a type-shaped macro invocation as a macro type
1593/// specifier even when it stands alone in a C block. Its descriptor is one
1594/// actual argument, just as the argument list of an expression-shaped call is.
1595pub(crate) fn function_macro_invocation_parts(node: Node<'_>) -> Option<(Node<'_>, Vec<Node<'_>>)> {
1596    let (name, arguments) = match node.kind() {
1597        "call_expression" => (
1598            node.child_by_field_name("function")?,
1599            argument_children(node.child_by_field_name("arguments")?).collect(),
1600        ),
1601        "macro_type_specifier" => (
1602            node.child_by_field_name("name")?,
1603            vec![node.child_by_field_name("type")?],
1604        ),
1605        _ => return None,
1606    };
1607    (name.kind() == "identifier").then_some((name, arguments))
1608}
1609
1610pub(crate) fn macro_type_argument_node<'tree>(
1611    node: Node<'tree>,
1612    source: &str,
1613) -> Option<Node<'tree>> {
1614    match node.kind() {
1615        "type_descriptor" => {
1616            let type_child = node
1617                .child_by_field_name("type")
1618                .or_else(|| first_type_child(node))?;
1619            for index in (0..node.named_child_count()).rev() {
1620                let child = node.named_child(index)?;
1621                if child != type_child
1622                    && matches!(
1623                        child.kind(),
1624                        "identifier"
1625                            | "type_identifier"
1626                            | "qualified_identifier"
1627                            | "scoped_type_identifier"
1628                    )
1629                {
1630                    return Some(child);
1631                }
1632            }
1633            macro_type_argument_node(type_child, source)
1634        }
1635        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => {
1636            node.child_by_field_name("name")
1637        }
1638        "identifier" if matches!(node_text(node, source), "struct" | "union") => node
1639            .next_named_sibling()
1640            .filter(|sibling| sibling.is_error() && sibling.named_child_count() == 1)
1641            .and_then(|error| error.named_child(0))
1642            .filter(|name| matches!(name.kind(), "identifier" | "type_identifier")),
1643        _ => cpp_name_component_nodes(node).is_some().then_some(node),
1644    }
1645}
1646
1647fn c_function_macro_argument<'tree>(
1648    node: Node<'tree>,
1649) -> Option<(Node<'tree>, usize, Node<'tree>)> {
1650    let mut current = node;
1651    while let Some(parent) = current.parent() {
1652        if parent.kind() == "argument_list" {
1653            let call = parent.parent().filter(|call| {
1654                call.kind() == "call_expression"
1655                    && call.child_by_field_name("arguments") == Some(parent)
1656                    && call
1657                        .child_by_field_name("function")
1658                        .is_some_and(|function| {
1659                            function.kind() == "identifier"
1660                                && !node_range_contains(function, current)
1661                        })
1662            })?;
1663            let mut actuals = argument_children(parent).enumerate();
1664            let (index, argument) = actuals.find(|(_, argument)| {
1665                node_range_contains(*argument, node)
1666                    && (argument.start_byte() == current.start_byte()
1667                        || argument.end_byte() == current.end_byte())
1668            })?;
1669            return Some((call, index, argument));
1670        }
1671        current = parent;
1672    }
1673    None
1674}
1675
1676/// Recover GLib's `g_autoptr(T) name = value` declaration from the CST shape
1677/// produced by tree-sitter-cpp for C source. The grammar retains the macro
1678/// invocation as the assignment's left operand and the declared name as one
1679/// adjacent `ERROR(identifier)` node, so no macro text splitting is needed.
1680/// The macro-declaration grammar reads the same line as a clean declaration
1681/// whose type is a `macro_type_specifier`; the `g_autoptr` name gate is what
1682/// keeps an arbitrary function-like type macro from becoming a binding.
1683fn recognized_c_macro_declarator_binding<'tree>(
1684    statement: Node<'tree>,
1685    source: &str,
1686) -> Option<MacroLocalBinding<'tree>> {
1687    if statement.kind() == "declaration" {
1688        let specifier = statement
1689            .child_by_field_name("type")
1690            .filter(|type_node| type_node.kind() == "macro_type_specifier")?;
1691        let macro_name = specifier
1692            .child_by_field_name("name")
1693            .filter(|name| name.kind() == "identifier")?;
1694        if node_text(macro_name, source) != "g_autoptr" {
1695            return None;
1696        }
1697        let type_node = specifier
1698            .child_by_field_name("type")
1699            .and_then(|descriptor| descriptor.child_by_field_name("type"))?;
1700        let declarator = statement
1701            .child_by_field_name("declarator")
1702            .filter(|declarator| declarator.kind() == "init_declarator")?;
1703        let name_node = declarator
1704            .child_by_field_name("declarator")
1705            .filter(|name| name.kind() == "identifier")?;
1706        declarator.child_by_field_name("value")?;
1707        let name = node_text(name_node, source).trim();
1708        let type_name = node_text(type_node, source).trim();
1709        if name.is_empty() || type_name.is_empty() {
1710            return None;
1711        }
1712        return Some(MacroLocalBinding {
1713            name: name.to_string(),
1714            type_name: type_name.to_string(),
1715            type_node: Some(type_node),
1716            pointer_depth: 1,
1717            proven_unit: None,
1718        });
1719    }
1720    let assignment = match statement.kind() {
1721        "assignment_expression" => statement,
1722        "expression_statement" => {
1723            single_named_child_matching(statement, |child| !child.is_extra())?
1724        }
1725        _ => return None,
1726    };
1727    if assignment.kind() != "assignment_expression" {
1728        return None;
1729    }
1730    let call = assignment.child_by_field_name("left")?;
1731    if call.kind() != "call_expression" {
1732        return None;
1733    }
1734    let function = call.child_by_field_name("function")?;
1735    if function.kind() != "identifier" || node_text(function, source) != "g_autoptr" {
1736        return None;
1737    }
1738    let arguments = call.child_by_field_name("arguments")?;
1739    let mut actuals = argument_children(arguments);
1740    let type_node = actuals.next()?;
1741    if actuals.next().is_some()
1742        || !matches!(
1743            type_node.kind(),
1744            "identifier"
1745                | "type_identifier"
1746                | "qualified_identifier"
1747                | "scoped_type_identifier"
1748                | "template_type"
1749        )
1750    {
1751        return None;
1752    }
1753    let name_node = (0..assignment.named_child_count())
1754        .filter_map(|index| assignment.named_child(index))
1755        .filter(|child| child.kind() == "ERROR")
1756        .filter_map(|error| {
1757            (error.named_child_count() == 1)
1758                .then(|| error.named_child(0))
1759                .flatten()
1760        })
1761        .find(|node| node.kind() == "identifier")?;
1762    let name = node_text(name_node, source).trim();
1763    let type_name = node_text(type_node, source).trim();
1764    if name.is_empty() || type_name.is_empty() {
1765        return None;
1766    }
1767    Some(MacroLocalBinding {
1768        name: name.to_string(),
1769        type_name: type_name.to_string(),
1770        type_node: Some(type_node),
1771        pointer_depth: 1,
1772        proven_unit: None,
1773    })
1774}
1775
1776#[derive(Clone, PartialEq, Eq)]
1777pub struct MacroBinding {
1778    source: ProjectFile,
1779    declaration_byte: usize,
1780    definition: MacroDefinition,
1781    exact: bool,
1782}
1783
1784impl MacroBinding {
1785    fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1786        Self {
1787            source: source.clone(),
1788            declaration_byte,
1789            definition: MacroDefinition::Unsupported,
1790            exact: false,
1791        }
1792    }
1793
1794    fn is_exact(&self) -> bool {
1795        self.exact
1796    }
1797
1798    fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1799        Self {
1800            source: source.clone(),
1801            declaration_byte,
1802            definition: current.definition.clone(),
1803            exact: false,
1804        }
1805    }
1806}
1807
1808/// The preprocessor conditionals whose truth decides whether one macro event
1809/// applies, by the start byte of each conditional node. Empty means the event
1810/// is unconditional.
1811///
1812/// [`VisibilityIndex::macro_event_condition_value`] needs exactly the
1813/// conditional ancestors that structurally contain the event, and deciding
1814/// containment means asking [`cpp_displaced_preprocessor_boundary`] for an
1815/// `#endif` tree-sitter displaced into error recovery -- a walk of the
1816/// conditional's whole subtree. That answer is a fact about the tree alone, so
1817/// it is settled once, when the events are collected, instead of on every
1818/// replay of the event (#1496).
1819type OwningPreprocessorConditionals = Box<[usize]>;
1820
1821#[derive(Clone)]
1822pub enum MacroEvent {
1823    Define {
1824        name: String,
1825        binding: MacroBinding,
1826        byte: usize,
1827        conditionals: OwningPreprocessorConditionals,
1828    },
1829    Undef {
1830        name: String,
1831        byte: usize,
1832        conditionals: OwningPreprocessorConditionals,
1833    },
1834    Include {
1835        targets: Vec<ProjectFile>,
1836        byte: usize,
1837        conditionals: OwningPreprocessorConditionals,
1838    },
1839    Invalidate {
1840        byte: usize,
1841    },
1842}
1843
1844impl MacroEvent {
1845    pub fn byte(&self) -> usize {
1846        match self {
1847            Self::Define { byte, .. }
1848            | Self::Undef { byte, .. }
1849            | Self::Include { byte, .. }
1850            | Self::Invalidate { byte } => *byte,
1851        }
1852    }
1853}
1854
1855#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1856pub enum CallArityEvidence {
1857    Exact(usize),
1858    Unknown,
1859}
1860
1861impl CallArityEvidence {
1862    pub fn exact(self) -> Option<usize> {
1863        match self {
1864            Self::Exact(arity) => Some(arity),
1865            Self::Unknown => None,
1866        }
1867    }
1868
1869    pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1870        self.exact().map(|arity| expected.accepts(arity))
1871    }
1872}
1873
1874struct CppAlias {
1875    name: String,
1876    target: String,
1877    namespace: Option<String>,
1878}
1879
1880type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1881
1882/// Why template-argument resolution failed. Definition diagnostics render
1883/// each mode differently; graph scans only care that the resolution is
1884/// unproven and match `Err(_)`.
1885#[derive(Debug, Clone, PartialEq, Eq)]
1886pub enum CppTemplateResolutionError {
1887    /// A template alias expansion revisited `alias`.
1888    AliasCycle { alias: CodeUnit },
1889    /// The explicit arguments do not bind to the declared template parameters.
1890    ArgumentBinding,
1891    /// Bound arguments do not substitute into the alias target's arguments.
1892    Substitution,
1893    /// No visible primary template declaration could be selected and
1894    /// reconciled for the specialization family.
1895    PrimarySelection,
1896    /// More than one applicable specialization remains and none is strictly
1897    /// more specialized than every other candidate.
1898    AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1899}
1900
1901/// The ambiguity candidates, deduplicated to one representative per visible
1902/// symbol so a diagnostic lists each contender once.
1903fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1904    let mut distinct: Vec<CodeUnit> = Vec::new();
1905    for unit in units {
1906        if !distinct
1907            .iter()
1908            .any(|existing| same_visible_symbol(existing, unit))
1909        {
1910            distinct.push(unit.clone());
1911        }
1912    }
1913    distinct
1914}
1915
1916/// Enumerate macro lexical references while constructing visibility only if
1917/// the bounded source walk finds a definition or identifier call candidate.
1918pub fn macro_lexical_references_with_visibility<'visibility, 'source: 'visibility, Factory>(
1919    visibility: Factory,
1920    file: &ProjectFile,
1921    root: Node<'_>,
1922    source: &str,
1923    max_references: usize,
1924    cancelled: impl FnMut() -> bool,
1925) -> MacroLexicalReferences
1926where
1927    Factory: FnOnce() -> &'visibility VisibilityIndex<'source>,
1928{
1929    crate::graph::macro_lexical::all_references(
1930        visibility,
1931        file,
1932        root,
1933        source,
1934        max_references,
1935        cancelled,
1936    )
1937}
1938
1939impl<'a> VisibilityIndex<'a> {
1940    pub fn data(&self) -> Arc<VisibilityIndexData> {
1941        Arc::clone(&self.data)
1942    }
1943
1944    pub fn from_data(
1945        cpp: &'a dyn CppSource,
1946        token: QueryToken<'a>,
1947        data: Arc<VisibilityIndexData>,
1948    ) -> Self {
1949        Self { cpp, token, data }
1950    }
1951
1952    pub fn cpp(&self) -> &'a dyn CppSource {
1953        self.cpp
1954    }
1955
1956    /// The request-scope proof this index was built with (issue #2414 step 3).
1957    pub fn token(&self) -> QueryToken<'a> {
1958        self.token
1959    }
1960
1961    /// Build the source-backed macro lexical index once per file in this
1962    /// visibility query. The entry cap keeps the query from retaining a
1963    /// workspace-sized collection when a batch visits many translation units.
1964    pub(crate) fn macro_lexical_index(
1965        &self,
1966        file: &ProjectFile,
1967        root: Node<'_>,
1968        source: &str,
1969    ) -> Arc<crate::graph::macro_lexical::MacroLexicalIndex> {
1970        if let Some(index) = self
1971            .macro_lexical_indexes
1972            .lock()
1973            .expect("macro lexical index cache poisoned")
1974            .get(file)
1975            .cloned()
1976        {
1977            debug_assert_eq!(
1978                index.source_len,
1979                source.len(),
1980                "macro lexical index for {file:?} was built from a different source"
1981            );
1982            return index;
1983        }
1984        let index = Arc::new(crate::graph::macro_lexical::build_index(
1985            self, file, root, source,
1986        ));
1987        let mut cache = self
1988            .macro_lexical_indexes
1989            .lock()
1990            .expect("macro lexical index cache poisoned");
1991        if let Some(existing) = cache.get(file).cloned() {
1992            return existing;
1993        }
1994        if cache.len() < MACRO_LEXICAL_INDEX_CACHE_CAP {
1995            cache.insert(file.clone(), Arc::clone(&index));
1996        }
1997        index
1998    }
1999
2000    /// The complete signature rows for one callable, shared by every
2001    /// candidate fact that the visibility query asks for. C++ reconciled
2002    /// members make this read substantially more expensive than an ordinary
2003    /// metadata lookup, so keep the answer on this per-query index and cap it
2004    /// explicitly.
2005    fn callable_metadata(&self, unit: &CodeUnit) -> Arc<Vec<SignatureMetadata>> {
2006        if let Some(metadata) = self
2007            .callable_metadata
2008            .lock()
2009            .expect("C++ callable metadata cache poisoned")
2010            .get(unit)
2011            .cloned()
2012        {
2013            return metadata;
2014        }
2015        let metadata = Arc::new(self.cpp.stored_signature_metadata(unit));
2016        let mut cache = self
2017            .callable_metadata
2018            .lock()
2019            .expect("C++ callable metadata cache poisoned");
2020        if cache.len() < CALLABLE_METADATA_CACHE_CAP {
2021            cache.insert(unit.clone(), Arc::clone(&metadata));
2022        }
2023        metadata
2024    }
2025
2026    /// Callable linkage backed by the query-local metadata memo.
2027    pub fn callable_linkage(&self, unit: &CodeUnit) -> Option<CallableLinkage> {
2028        crate::identity::cpp_callable_linkage_from_metadata(
2029            self.callable_metadata(unit).iter().cloned(),
2030        )
2031    }
2032
2033    /// Callable arity backed by the same query-local metadata memo.
2034    pub fn callable_arity(&self, unit: &CodeUnit) -> Option<CallableArity> {
2035        brokk_bifrost_core::analyzer::model::first_callable_arity(
2036            self.callable_metadata(unit).iter().cloned(),
2037        )
2038    }
2039
2040    /// Callable parameter types backed by the same stored-metadata memo.
2041    /// Parameter spellings are source-owned facts and do not depend on the
2042    /// modifier joins that full `signature_metadata` performs for an
2043    /// out-of-line member.
2044    pub fn callable_parameter_types(&self, unit: &CodeUnit) -> Option<Vec<String>> {
2045        brokk_bifrost_core::analyzer::model::first_callable_parameter_types(
2046            self.callable_metadata(unit).iter().cloned(),
2047        )
2048    }
2049
2050    /// Whether `file` has a structured unresolved include that is active before
2051    /// the reference at `before_byte`.
2052    ///
2053    /// Include facts are collected from the prepared tree once per visibility
2054    /// query. The reference position is still evaluated for each call because
2055    /// preprocessor visibility depends on the reference's own conditional
2056    /// context.
2057    pub fn has_unresolved_include_visible_before(
2058        &self,
2059        file: &ProjectFile,
2060        before_byte: usize,
2061    ) -> bool {
2062        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
2063            return false;
2064        };
2065        let facts = self.structured_include_facts(file, prepared.as_ref());
2066        has_unresolved_include_visible_before_in_prepared(
2067            file,
2068            prepared.as_ref(),
2069            self.cpp.include_target_index(),
2070            &facts,
2071            before_byte,
2072        )
2073    }
2074
2075    fn structured_include_facts(
2076        &self,
2077        file: &ProjectFile,
2078        prepared: &PreparedSyntaxTree,
2079    ) -> Arc<[StructuredIncludeFact]> {
2080        let cell = self
2081            .structured_include_fact_cells
2082            .lock()
2083            .expect("C++ structured include-fact cache poisoned")
2084            .entry(file.clone())
2085            .or_default()
2086            .clone();
2087        Arc::clone(cell.get_or_init(|| collect_structured_include_facts(prepared)))
2088    }
2089
2090    /// A [`VisibilityIndex`] over a caller-supplied visible-declaration map,
2091    /// bypassing the include-closure walk [`Self::build`] performs.
2092    ///
2093    /// The resolver's own unit tests drive the type-resolution paths against a
2094    /// hand-written visibility table; they live in `brokk-bifrost-analysis`
2095    /// because they need a real `CppAnalyzer`, so the struct literal they used
2096    /// to write inline is here instead of thirty-three public fields.
2097    #[cfg(any(test, feature = "test-support"))]
2098    pub fn from_visible_files_for_test(
2099        cpp: &'a dyn CppSource,
2100        token: QueryToken<'a>,
2101        visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
2102    ) -> Self {
2103        let visible_source_files_by_root = visible_by_file
2104            .iter()
2105            .map(|(file, visible)| {
2106                (
2107                    file.clone(),
2108                    visible
2109                        .iter()
2110                        .map(|unit| unit.source().clone())
2111                        .chain(std::iter::once(file.clone()))
2112                        .collect(),
2113                )
2114            })
2115            .collect();
2116        let mut global_field_internal_linkage = HashMap::default();
2117        Self {
2118            cpp,
2119            token,
2120            data: Arc::new(VisibilityIndexData {
2121                visible_by_identifier: build_visible_identifier_index(
2122                    &CppGraphSource::from_source(cpp, token),
2123                    &visible_by_file,
2124                    &visible_source_files_by_root,
2125                    &mut global_field_internal_linkage,
2126                ),
2127                global_field_internal_linkage,
2128                visible_by_file,
2129                visible_source_files_by_root,
2130                alias_cells: Mutex::new(HashMap::default()),
2131                visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2132                parser_alias_target_matches: RwLock::new(HashMap::default()),
2133                ordinary_type_import_cells: Mutex::new(HashMap::default()),
2134                project_using_index: OnceLock::new(),
2135                namespace_alias_sites: Mutex::new(HashMap::default()),
2136                callable_reference_specs: Mutex::new(HashMap::default()),
2137                structured_include_fact_cells: Mutex::new(HashMap::default()),
2138                include_activation_cells: Mutex::new(HashMap::default()),
2139                compile_proven_guard_cells: Mutex::new(HashMap::default()),
2140                include_path_admission_cells: Mutex::new(HashMap::default()),
2141                conditional_include_projection_cells: Mutex::new(HashMap::default()),
2142                conditional_include_projection_index_build_count: AtomicUsize::new(0),
2143                conditional_include_projection_state_count: AtomicUsize::new(0),
2144                conditional_include_target_state_count: AtomicUsize::new(0),
2145                include_activation_build_count: AtomicUsize::new(0),
2146                using_donor_activation_count: AtomicUsize::new(0),
2147                using_namespace_lookup_count: AtomicUsize::new(0),
2148                using_name_candidate_inspection_count: AtomicUsize::new(0),
2149                callable_reference_spec_build_count: AtomicUsize::new(0),
2150                alias_source_parse_counts: Mutex::new(HashMap::default()),
2151                visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2152                parser_alias_fallback_calls: AtomicUsize::new(0),
2153                parser_alias_fallback_files: AtomicUsize::new(0),
2154                parser_alias_source_parses: AtomicUsize::new(0),
2155                parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2156                field_type_facts: Mutex::new(HashMap::default()),
2157                structured_alias_targets: Mutex::new(HashMap::default()),
2158                callable_comparables: Mutex::new(HashMap::default()),
2159                class_definition_agreements: Mutex::new(HashMap::default()),
2160                callable_metadata: Mutex::new(HashMap::default()),
2161                comparable_name_declarations: Mutex::new(HashMap::default()),
2162                indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2163                indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2164                precise_parent_cache: Mutex::new(HashMap::default()),
2165                c_tag_kind_cache: Mutex::new(HashMap::default()),
2166                c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2167                macro_event_cells: Mutex::new(HashMap::default()),
2168                macro_event_name_sets: Mutex::new(HashMap::default()),
2169                macro_include_protection_cells: Mutex::new(HashMap::default()),
2170                macro_environment_checkpoints: Mutex::new(HashMap::default()),
2171                macro_replacements: Mutex::new(HashMap::default()),
2172                macro_local_binding_templates: Mutex::new(HashMap::default()),
2173                macro_lexical_templates: Mutex::new(HashMap::default()),
2174                macro_lexical_indexes: Mutex::new(HashMap::default()),
2175                macro_replacement_bodies: Mutex::new(HashMap::default()),
2176                macro_type_parameters: Mutex::new(HashMap::default()),
2177                callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2178                macro_replacement_parse_count: AtomicUsize::new(0),
2179                macro_event_application_count: AtomicUsize::new(0),
2180                macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2181                macro_environment_copy_count: AtomicUsize::new(0),
2182                macro_environment_request_count: AtomicUsize::new(0),
2183                cpp_template_metadata: HashMap::default(),
2184                cpp_template_families: HashMap::default(),
2185                qualified_candidate_inspections: AtomicUsize::new(0),
2186                target_preserving_type_resolution_count: AtomicUsize::new(0),
2187                visibility_identifier_lookup_count: 0,
2188                visibility_identifier_batch_count: 0,
2189            }),
2190        }
2191    }
2192
2193    /// The index's own C++ source, in the dispatching-analyzer shape.
2194    ///
2195    /// Four resolution paths reach the workspace through the C++ analyzer they
2196    /// already hold rather than through the analyzer the query was issued
2197    /// against; before the move they passed `&CppAnalyzer` straight into a
2198    /// `&dyn IAnalyzer` parameter. See [`CppGraphSource::from_source`].
2199    fn cpp_source(&self) -> CppGraphSource<'a> {
2200        CppGraphSource::from_source(self.cpp, self.token)
2201    }
2202
2203    pub fn build(
2204        cpp: &'a dyn CppSource,
2205        token: QueryToken<'a>,
2206        analyzer: &CppGraphSource<'_>,
2207        roots: &HashSet<ProjectFile>,
2208    ) -> Self {
2209        Self::build_with_cancellation(cpp, token, analyzer, roots, None)
2210    }
2211
2212    pub fn build_with_cancellation(
2213        cpp: &'a dyn CppSource,
2214        token: QueryToken<'a>,
2215        analyzer: &CppGraphSource<'_>,
2216        roots: &HashSet<ProjectFile>,
2217        cancellation: Option<&CancellationToken>,
2218    ) -> Self {
2219        let visibility_started = Instant::now();
2220        let include_targets = cpp.include_target_index();
2221        let includes_started = Instant::now();
2222        let mut include_graph = IncludeGraph::default();
2223        for root in roots {
2224            include_graph.extend_with(root, cancellation, &mut |file| {
2225                cpp.canonical_include_paths(token, file)
2226                    .unwrap_or_default()
2227                    .into_iter()
2228                    .flat_map(|include| {
2229                        resolve_include_targets_with_index(file, &include, include_targets)
2230                    })
2231                    .collect()
2232            });
2233        }
2234        let include_elapsed = includes_started.elapsed();
2235        let include_file_count = include_graph.files().count();
2236        let visible_source_files_by_root = roots
2237            .iter()
2238            .map(|root| {
2239                (
2240                    root.clone(),
2241                    include_graph.reachable_files(root, cancellation),
2242                )
2243            })
2244            .collect::<HashMap<_, _>>();
2245        let mut visibility_stats = BoundedVisibilityStats::default();
2246        let mut visible_by_file = build_bounded_visible_declarations(
2247            cpp,
2248            token,
2249            analyzer,
2250            roots,
2251            &visible_source_files_by_root,
2252            cancellation,
2253            &mut visibility_stats,
2254        );
2255        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
2256            eprintln!(
2257                "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={}",
2258                visibility_started.elapsed().as_millis(),
2259                include_elapsed.as_millis(),
2260                include_file_count,
2261                visibility_stats.rounds,
2262                visibility_stats.root_names,
2263                visibility_stats.identifier_lookups,
2264                visibility_stats.identifier_batches,
2265                visibility_stats.candidate_units,
2266                visibility_stats.candidate_sources,
2267                visibility_stats.declaration_reads,
2268                visibility_stats.declaration_units,
2269                visibility_stats.selected_units,
2270                visibility_stats.dependency_source_names,
2271                visibility_stats.dependency_names,
2272                visibility_stats.lookup_elapsed.as_millis(),
2273                visibility_stats.declaration_elapsed.as_millis(),
2274                visibility_stats.dependency_source_elapsed.as_millis(),
2275            );
2276        }
2277        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
2278        let finalize_started = Instant::now();
2279        if report_stats {
2280            eprintln!(
2281                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=started roots={} visible_units={}",
2282                visible_by_file.len(),
2283                visible_by_file.values().map(HashSet::len).sum::<usize>(),
2284            );
2285        }
2286        let owner_started = Instant::now();
2287        if report_stats {
2288            eprintln!("BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=started");
2289        }
2290        let owner_stats = extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
2291        if report_stats {
2292            eprintln!(
2293                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=completed unseen_owners={} definition_lookups={} admitted={} elapsed_ms={}",
2294                owner_stats.unseen_owners,
2295                owner_stats.definition_lookups,
2296                owner_stats.admitted,
2297                owner_started.elapsed().as_millis(),
2298            );
2299        }
2300        let mut global_field_internal_linkage = HashMap::default();
2301        let identifier_started = Instant::now();
2302        if report_stats {
2303            eprintln!(
2304                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=started"
2305            );
2306        }
2307        let visible_by_identifier = build_visible_identifier_index(
2308            analyzer,
2309            &visible_by_file,
2310            &visible_source_files_by_root,
2311            &mut global_field_internal_linkage,
2312        );
2313        if report_stats {
2314            eprintln!(
2315                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=completed roots={} names={} candidates={} elapsed_ms={}",
2316                visible_by_identifier.len(),
2317                visible_by_identifier
2318                    .values()
2319                    .map(HashMap::len)
2320                    .sum::<usize>(),
2321                visible_by_identifier
2322                    .values()
2323                    .flat_map(HashMap::values)
2324                    .map(Vec::len)
2325                    .sum::<usize>(),
2326                identifier_started.elapsed().as_millis(),
2327            );
2328        }
2329        let mut cpp_template_metadata = HashMap::default();
2330        let metadata_started = Instant::now();
2331        let mut template_classes = 0usize;
2332        if report_stats {
2333            eprintln!(
2334                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=started"
2335            );
2336        }
2337        for unit in visible_by_file
2338            .values()
2339            .flatten()
2340            .filter(|unit| unit.is_class())
2341        {
2342            template_classes += 1;
2343            if cpp_template_metadata.contains_key(unit) {
2344                continue;
2345            }
2346            if let Some(metadata) = cpp.template_metadata(unit) {
2347                cpp_template_metadata.insert(unit.clone(), metadata);
2348            }
2349        }
2350        if report_stats {
2351            eprintln!(
2352                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=completed classes={} metadata={} elapsed_ms={}",
2353                template_classes,
2354                cpp_template_metadata.len(),
2355                metadata_started.elapsed().as_millis(),
2356            );
2357        }
2358        let families_started = Instant::now();
2359        if report_stats {
2360            eprintln!(
2361                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=started"
2362            );
2363        }
2364        let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
2365        for (unit, metadata) in &cpp_template_metadata {
2366            cpp_template_families
2367                .entry(metadata.primary_fq_name.clone())
2368                .or_default()
2369                .push(unit.clone());
2370        }
2371        // `cpp_template_metadata` is hash-keyed on `CodeUnit`, so the push
2372        // order above is a function of those hashes. Two mirrored headers can
2373        // declare one specialization; `select_template_specialization` treats
2374        // them as interchangeable and returns the family's first entry, so an
2375        // unsorted family made the reported declaration depend on the
2376        // workspace's absolute path and on unrelated files (#1836). Order the
2377        // family exactly as `build_visible_identifier_index` orders its
2378        // per-identifier candidate lists.
2379        for family in cpp_template_families.values_mut() {
2380            sort_lookup_units(family);
2381        }
2382        if report_stats {
2383            eprintln!(
2384                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=completed families={} members={} elapsed_ms={}",
2385                cpp_template_families.len(),
2386                cpp_template_families.values().map(Vec::len).sum::<usize>(),
2387                families_started.elapsed().as_millis(),
2388            );
2389            eprintln!(
2390                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=completed roots={} visible_units={} elapsed_ms={} total_ms={}",
2391                visible_by_file.len(),
2392                visible_by_file.values().map(HashSet::len).sum::<usize>(),
2393                finalize_started.elapsed().as_millis(),
2394                visibility_started.elapsed().as_millis(),
2395            );
2396        }
2397        Self {
2398            cpp,
2399            token,
2400            data: Arc::new(VisibilityIndexData {
2401                visible_by_file,
2402                visible_by_identifier,
2403                global_field_internal_linkage,
2404                visible_source_files_by_root,
2405                alias_cells: Mutex::new(HashMap::default()),
2406                visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2407                parser_alias_target_matches: RwLock::new(HashMap::default()),
2408                ordinary_type_import_cells: Mutex::new(HashMap::default()),
2409                project_using_index: OnceLock::new(),
2410                namespace_alias_sites: Mutex::new(HashMap::default()),
2411                callable_reference_specs: Mutex::new(HashMap::default()),
2412                structured_include_fact_cells: Mutex::new(HashMap::default()),
2413                include_activation_cells: Mutex::new(HashMap::default()),
2414                compile_proven_guard_cells: Mutex::new(HashMap::default()),
2415                include_path_admission_cells: Mutex::new(HashMap::default()),
2416                conditional_include_projection_cells: Mutex::new(HashMap::default()),
2417                #[cfg(any(test, feature = "test-support"))]
2418                conditional_include_projection_index_build_count: AtomicUsize::new(0),
2419                #[cfg(any(test, feature = "test-support"))]
2420                conditional_include_projection_state_count: AtomicUsize::new(0),
2421                #[cfg(any(test, feature = "test-support"))]
2422                conditional_include_target_state_count: AtomicUsize::new(0),
2423                #[cfg(any(test, feature = "test-support"))]
2424                include_activation_build_count: AtomicUsize::new(0),
2425                #[cfg(any(test, feature = "test-support"))]
2426                using_donor_activation_count: AtomicUsize::new(0),
2427                #[cfg(any(test, feature = "test-support"))]
2428                using_namespace_lookup_count: AtomicUsize::new(0),
2429                #[cfg(any(test, feature = "test-support"))]
2430                using_name_candidate_inspection_count: AtomicUsize::new(0),
2431                #[cfg(any(test, feature = "test-support"))]
2432                callable_reference_spec_build_count: AtomicUsize::new(0),
2433                #[cfg(any(test, feature = "test-support"))]
2434                alias_source_parse_counts: Mutex::new(HashMap::default()),
2435                #[cfg(any(test, feature = "test-support"))]
2436                visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2437                parser_alias_fallback_calls: AtomicUsize::new(0),
2438                parser_alias_fallback_files: AtomicUsize::new(0),
2439                parser_alias_source_parses: AtomicUsize::new(0),
2440                parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2441                field_type_facts: Mutex::new(HashMap::default()),
2442                structured_alias_targets: Mutex::new(HashMap::default()),
2443                callable_comparables: Mutex::new(HashMap::default()),
2444                class_definition_agreements: Mutex::new(HashMap::default()),
2445                callable_metadata: Mutex::new(HashMap::default()),
2446                comparable_name_declarations: Mutex::new(HashMap::default()),
2447                indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2448                indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2449                precise_parent_cache: Mutex::new(HashMap::default()),
2450                c_tag_kind_cache: Mutex::new(HashMap::default()),
2451                c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2452                macro_event_cells: Mutex::new(HashMap::default()),
2453                macro_event_name_sets: Mutex::new(HashMap::default()),
2454                macro_include_protection_cells: Mutex::new(HashMap::default()),
2455                macro_environment_checkpoints: Mutex::new(HashMap::default()),
2456                macro_replacements: Mutex::new(HashMap::default()),
2457                macro_local_binding_templates: Mutex::new(HashMap::default()),
2458                macro_lexical_templates: Mutex::new(HashMap::default()),
2459                macro_lexical_indexes: Mutex::new(HashMap::default()),
2460                macro_replacement_bodies: Mutex::new(HashMap::default()),
2461                macro_type_parameters: Mutex::new(HashMap::default()),
2462                callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2463                #[cfg(any(test, feature = "test-support"))]
2464                macro_replacement_parse_count: AtomicUsize::new(0),
2465                #[cfg(any(test, feature = "test-support"))]
2466                macro_event_application_count: AtomicUsize::new(0),
2467                #[cfg(any(test, feature = "test-support"))]
2468                macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2469                #[cfg(any(test, feature = "test-support"))]
2470                macro_environment_copy_count: AtomicUsize::new(0),
2471                #[cfg(any(test, feature = "test-support"))]
2472                macro_environment_request_count: AtomicUsize::new(0),
2473                cpp_template_metadata,
2474                cpp_template_families,
2475                #[cfg(any(test, feature = "test-support"))]
2476                qualified_candidate_inspections: AtomicUsize::new(0),
2477                #[cfg(any(test, feature = "test-support"))]
2478                target_preserving_type_resolution_count: AtomicUsize::new(0),
2479                #[cfg(any(test, feature = "test-support"))]
2480                visibility_identifier_lookup_count: visibility_stats.identifier_lookups,
2481                #[cfg(any(test, feature = "test-support"))]
2482                visibility_identifier_batch_count: visibility_stats.identifier_batches,
2483            }),
2484        }
2485    }
2486
2487    pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2488        if file == target.source() {
2489            return true;
2490        }
2491        if self.global_field_has_internal_linkage(target) {
2492            return self
2493                .visible_source_files_by_root
2494                .get(file)
2495                .is_some_and(|sources| sources.contains(target.source()));
2496        }
2497        self.visible_by_file
2498            .get(file)
2499            .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
2500    }
2501
2502    fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
2503        self.global_field_internal_linkage
2504            .get(unit)
2505            .copied()
2506            .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
2507    }
2508
2509    pub fn call_arity_evidence(
2510        &self,
2511        file: &ProjectFile,
2512        call: Node<'_>,
2513        source: &str,
2514    ) -> CallArityEvidence {
2515        self.call_arity_evidence_at(file, call, source, call.start_byte())
2516    }
2517
2518    /// Argument-count evidence for a call whose macro environment is not the
2519    /// one at its own byte offset.
2520    ///
2521    /// A call recovered from a macro replacement lives in a sentinel parse of
2522    /// its own, so its node offsets say nothing about which macros are active.
2523    /// `environment_byte` names the position in `file` whose macro environment
2524    /// governs the call: the macro definition site for a replacement body.
2525    pub fn call_arity_evidence_at(
2526        &self,
2527        file: &ProjectFile,
2528        call: Node<'_>,
2529        source: &str,
2530        environment_byte: usize,
2531    ) -> CallArityEvidence {
2532        let Some(arguments) = call
2533            .child_by_field_name("arguments")
2534            .or_else(|| call.child_by_field_name("parameters"))
2535            .or_else(|| call.child_by_field_name("value"))
2536            .or_else(|| first_named_child_of_kind(call, "argument_list"))
2537            .or_else(|| first_named_child_of_kind(call, "initializer_list"))
2538        else {
2539            return CallArityEvidence::Exact(0);
2540        };
2541        let recovered_c_keyword_arguments =
2542            recovered_c_keyword_argument_count(file, call, arguments, source);
2543        let c_semantics = reference_uses_c_semantics(self.cpp, file);
2544        let arguments = argument_children(arguments)
2545            .flat_map(|argument| {
2546                recovered_c_new_expression_arguments(argument, c_semantics)
2547                    .map(Vec::from)
2548                    .unwrap_or_else(|| vec![argument])
2549            })
2550            .collect::<Vec<_>>();
2551        if arguments
2552            .iter()
2553            .all(|argument| !argument_shape_may_change_arity(*argument))
2554        {
2555            return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
2556        }
2557        let environment = self.macro_environment(file, environment_byte);
2558        let mut stack = Vec::new();
2559        let mut total = recovered_c_keyword_arguments;
2560        for argument in arguments {
2561            if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
2562                return CallArityEvidence::Unknown;
2563            }
2564            let CallArityEvidence::Exact(spread) =
2565                self.argument_arity_evidence(argument, source, &environment, &mut stack)
2566            else {
2567                return CallArityEvidence::Unknown;
2568            };
2569            total += spread;
2570        }
2571        CallArityEvidence::Exact(total)
2572    }
2573
2574    fn argument_arity_evidence(
2575        &self,
2576        argument: Node<'_>,
2577        source: &str,
2578        environment: &MacroEnvironment,
2579        stack: &mut Vec<(ProjectFile, usize)>,
2580    ) -> CallArityEvidence {
2581        let (name, invocation_arguments, function_like) = match argument.kind() {
2582            "identifier" => (node_text(argument, source), None, false),
2583            "call_expression" => {
2584                let Some(function) = argument.child_by_field_name("function") else {
2585                    return CallArityEvidence::Exact(1);
2586                };
2587                if function.kind() != "identifier" {
2588                    return CallArityEvidence::Exact(1);
2589                }
2590                let Some(arguments) = argument.child_by_field_name("arguments") else {
2591                    return CallArityEvidence::Exact(1);
2592                };
2593                (node_text(function, source), Some(arguments), true)
2594            }
2595            _ => return CallArityEvidence::Exact(1),
2596        };
2597        let Some(binding) = environment.binding(name) else {
2598            return if environment.unknown_names {
2599                CallArityEvidence::Unknown
2600            } else {
2601                CallArityEvidence::Exact(1)
2602            };
2603        };
2604        if !binding.is_exact() {
2605            return CallArityEvidence::Unknown;
2606        }
2607        match (&binding.definition, invocation_arguments, function_like) {
2608            (MacroDefinition::Object { replacement }, None, false) => self
2609                .replacement_arity_evidence(
2610                    replacement,
2611                    &[],
2612                    &[],
2613                    source,
2614                    environment,
2615                    stack,
2616                    binding,
2617                ),
2618            (
2619                MacroDefinition::Function {
2620                    parameters,
2621                    replacement,
2622                },
2623                Some(arguments),
2624                true,
2625            ) => {
2626                let actuals = argument_children(arguments).collect::<Vec<_>>();
2627                if actuals.len() != parameters.len() {
2628                    CallArityEvidence::Unknown
2629                } else {
2630                    self.replacement_arity_evidence(
2631                        replacement,
2632                        parameters,
2633                        &actuals,
2634                        source,
2635                        environment,
2636                        stack,
2637                        binding,
2638                    )
2639                }
2640            }
2641            (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
2642            _ => CallArityEvidence::Unknown,
2643        }
2644    }
2645
2646    #[allow(clippy::too_many_arguments)]
2647    fn replacement_arity_evidence(
2648        &self,
2649        replacement: &str,
2650        parameters: &[String],
2651        actuals: &[Node<'_>],
2652        actual_source: &str,
2653        environment: &MacroEnvironment,
2654        stack: &mut Vec<(ProjectFile, usize)>,
2655        binding: &MacroBinding,
2656    ) -> CallArityEvidence {
2657        let identity = (binding.source.clone(), binding.declaration_byte);
2658        if stack.contains(&identity) || replacement.trim().is_empty() {
2659            return CallArityEvidence::Unknown;
2660        }
2661        stack.push(identity);
2662        let parsed = self.parsed_macro_replacement(binding, replacement);
2663        let evidence = (|| {
2664            let ParsedMacroReplacement::Parsed {
2665                source: sentinel,
2666                tree,
2667            } = parsed.as_ref()
2668            else {
2669                return None;
2670            };
2671            let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
2672            let arguments = call.child_by_field_name("arguments")?;
2673            let mut total = 0usize;
2674            for argument in argument_children(arguments) {
2675                if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
2676                    return None;
2677                }
2678                if argument.kind() == "identifier"
2679                    && let Some(parameter_index) = parameters
2680                        .iter()
2681                        .position(|parameter| parameter == node_text(argument, sentinel))
2682                {
2683                    if !macro_expansion_shape_is_safe(
2684                        actuals[parameter_index],
2685                        actual_source,
2686                        &[],
2687                        environment,
2688                    ) {
2689                        return None;
2690                    }
2691                    let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
2692                        actuals[parameter_index],
2693                        actual_source,
2694                        environment,
2695                        stack,
2696                    ) else {
2697                        return None;
2698                    };
2699                    total += spread;
2700                    continue;
2701                }
2702                let CallArityEvidence::Exact(spread) =
2703                    self.argument_arity_evidence(argument, sentinel, environment, stack)
2704                else {
2705                    return None;
2706                };
2707                total += spread;
2708            }
2709            Some(CallArityEvidence::Exact(total))
2710        })()
2711        .unwrap_or(CallArityEvidence::Unknown);
2712        stack.pop();
2713        evidence
2714    }
2715
2716    fn parsed_macro_replacement(
2717        &self,
2718        binding: &MacroBinding,
2719        replacement: &str,
2720    ) -> Arc<ParsedMacroReplacement> {
2721        let key = (binding.source.clone(), binding.declaration_byte);
2722        let mut cache = self
2723            .macro_replacements
2724            .lock()
2725            .expect("C++ macro replacement cache poisoned");
2726        if let Some(parsed) = cache.get(&key) {
2727            return Arc::clone(parsed);
2728        }
2729        #[cfg(any(test, feature = "test-support"))]
2730        self.macro_replacement_parse_count
2731            .fetch_add(1, Ordering::Relaxed);
2732        let parsed = parse_sentinel(
2733            "void __bifrost_macro_arity() { __bifrost_macro_call(",
2734            SentinelInput::replacement(replacement),
2735            "); }",
2736        );
2737        let parsed = if parsed.tree.root_node().has_error() {
2738            ParsedMacroReplacement::Unsupported
2739        } else {
2740            ParsedMacroReplacement::Parsed {
2741                source: parsed.source,
2742                tree: parsed.tree,
2743            }
2744        };
2745        let parsed = Arc::new(parsed);
2746        cache.insert(key, Arc::clone(&parsed));
2747        parsed
2748    }
2749
2750    /// Recover a typed local declared by an active C function-like macro.
2751    ///
2752    /// This is intentionally narrower than macro expansion. The replacement
2753    /// must parse as one declaration, and the invocation must bind every
2754    /// formal parameter to one structured argument. That is sufficient for
2755    /// declaration macros such as `THIS(StorageAzure)`. An unavailable include
2756    /// can make the binding provisional without erasing its last known
2757    /// definition; an explicit conflicting definition still replaces it with
2758    /// Unsupported. Malformed and statement-producing macros also fail closed.
2759    pub fn function_macro_local_binding<'tree>(
2760        &self,
2761        file: &ProjectFile,
2762        statement: Node<'tree>,
2763        source: &str,
2764    ) -> Option<MacroLocalBinding<'tree>> {
2765        if !is_c_source_file(file) {
2766            return None;
2767        }
2768        if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
2769            return Some(binding);
2770        }
2771        let call = match statement.kind() {
2772            "expression_statement" => {
2773                single_named_child_matching(statement, |child| !child.is_extra())?
2774            }
2775            _ => statement,
2776        };
2777        let (function, actuals) = function_macro_invocation_parts(call)?;
2778        let environment = self.macro_environment(file, call.start_byte());
2779        let function_name = node_text(function, source);
2780        let binding = environment.binding(function_name)?;
2781        let MacroDefinition::Function {
2782            parameters,
2783            replacement,
2784        } = &binding.definition
2785        else {
2786            return None;
2787        };
2788        if actuals.len() != parameters.len() {
2789            return None;
2790        }
2791        let template = self.macro_local_binding_template(binding, parameters, replacement)?;
2792        let (type_name, type_node) = match &template.declared_type {
2793            MacroLocalBindingTypeTemplate::Parameter(index) => {
2794                let actual = *actuals.get(*index)?;
2795                if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
2796                    return None;
2797                }
2798                let type_node = macro_type_argument_node(actual, source).unwrap_or(actual);
2799                (
2800                    node_text(actual, source).trim().to_string(),
2801                    Some(type_node),
2802                )
2803            }
2804            MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
2805        };
2806        if type_name.is_empty() {
2807            return None;
2808        }
2809        Some(MacroLocalBinding {
2810            name: template.name.clone(),
2811            type_name,
2812            type_node,
2813            pointer_depth: template.pointer_depth,
2814            proven_unit: None,
2815        })
2816    }
2817
2818    /// Recover the typed receiver established by a C container macro
2819    /// assignment, such as `value = container_of(ptr, struct item, link)`.
2820    ///
2821    /// The macro definition's parsed replacement identifies the one formal
2822    /// parameter used in type position.  The invocation supplies the actual
2823    /// type node, which is then resolved through the ordinary visibility
2824    /// index.  Calls with no unique type-position parameter, an unresolved
2825    /// type, or an uncertain macro environment remain unproven.
2826    pub fn function_macro_container_binding<'tree>(
2827        &self,
2828        analyzer: &CppGraphSource<'_>,
2829        file: &ProjectFile,
2830        assignment: Node<'tree>,
2831        source: &str,
2832    ) -> Option<MacroLocalBinding<'tree>> {
2833        if !is_c_source_file(file) || assignment.kind() != "assignment_expression" {
2834            return None;
2835        }
2836        let name_node = assignment.child_by_field_name("left")?;
2837        if name_node.kind() != "identifier" {
2838            return None;
2839        }
2840        let call = assignment.child_by_field_name("right")?;
2841        if call.kind() != "call_expression" {
2842            return None;
2843        }
2844        let function = call.child_by_field_name("function")?;
2845        if function.kind() != "identifier" {
2846            return None;
2847        }
2848        let arguments = call.child_by_field_name("arguments")?;
2849        let actuals = argument_children(arguments).collect::<Vec<_>>();
2850        let function_name = node_text(function, source);
2851        let environment = self.macro_environment(file, call.start_byte());
2852        let binding = environment.binding(function_name)?;
2853        if !binding.is_exact() {
2854            return None;
2855        }
2856        let MacroDefinition::Function {
2857            parameters,
2858            replacement,
2859        } = &binding.definition
2860        else {
2861            return None;
2862        };
2863        if actuals.len() != parameters.len() {
2864            return None;
2865        }
2866        let body = self.parsed_macro_replacement_body(
2867            &(binding.source.clone(), binding.declaration_byte),
2868            parameters,
2869            replacement,
2870        )?;
2871        let type_parameter = macro_replacement_type_parameter(&body, parameters)?;
2872        let type_argument = *actuals.get(type_parameter)?;
2873        let type_node = macro_type_argument_node(type_argument, source)?;
2874        let type_name = node_text(type_node, source).trim().to_string();
2875        if type_name.is_empty() {
2876            return None;
2877        }
2878        let explicit_tag = match node_text(type_argument, source) {
2879            "struct" => Some(CppCTagKind::Struct),
2880            "union" => Some(CppCTagKind::Union),
2881            _ => None,
2882        };
2883        let resolved_type = if let Some(tag) = explicit_tag {
2884            let candidates = self
2885                .visible_identifier_candidates(file, &type_name)
2886                .filter(|candidate| self.cached_c_tag_kind(analyzer, candidate) == Some(tag))
2887                .collect::<Vec<_>>();
2888            self.resolve_type_candidates(
2889                analyzer,
2890                file,
2891                &candidates,
2892                TypeCandidateResolution::Canonical,
2893            )
2894            .ok()
2895        } else {
2896            self.resolve_type_node_result(file, type_node, source)
2897                .ok()
2898                .flatten()
2899        };
2900        let proven_unit = resolved_type?;
2901        Some(MacroLocalBinding {
2902            name: node_text(name_node, source).to_string(),
2903            type_name,
2904            type_node: Some(type_node),
2905            pointer_depth: 1,
2906            proven_unit: Some(proven_unit),
2907        })
2908    }
2909
2910    /// Recover the invocation-specific type of a replacement local visible
2911    /// at the selected source token.
2912    pub fn macro_local_binding_at<'tree>(
2913        &self,
2914        file: &ProjectFile,
2915        root: Node<'tree>,
2916        source: &str,
2917        start_byte: usize,
2918        end_byte: usize,
2919    ) -> Option<MacroLocalBinding<'tree>> {
2920        crate::graph::macro_lexical::typed_binding(self, file, root, source, start_byte, end_byte)
2921    }
2922
2923    /// Resolve the source-backed lexical macro declaration for one source
2924    /// range. The range may be a point range (`start..start + 1`) inside an
2925    /// identifier, which is the form used by goto-definition queries.
2926    pub fn macro_lexical_binding(
2927        &self,
2928        file: &ProjectFile,
2929        root: Node<'_>,
2930        source: &str,
2931        start_byte: usize,
2932        end_byte: usize,
2933    ) -> Option<MacroLexicalBinding> {
2934        crate::graph::macro_lexical::binding(self, file, root, source, start_byte, end_byte)
2935    }
2936
2937    /// Enumerate source ranges whose lexical meaning comes from a
2938    /// function-like macro formal or replacement local. Definition spellings
2939    /// themselves are omitted; callers needing goto-definition on a spelling
2940    /// use [`Self::macro_lexical_binding`].
2941    pub fn macro_lexical_references(
2942        &self,
2943        file: &ProjectFile,
2944        root: Node<'_>,
2945        source: &str,
2946        max_references: usize,
2947        cancelled: impl FnMut() -> bool,
2948    ) -> MacroLexicalReferences {
2949        crate::graph::macro_lexical::all_references(
2950            || self,
2951            file,
2952            root,
2953            source,
2954            max_references,
2955            cancelled,
2956        )
2957    }
2958
2959    /// Return the source identity of the exact active function-like macro
2960    /// binding. The definition AST is hydrated by the lexical helper from
2961    /// this `(ProjectFile, declaration byte)` pair, so no parser node escapes
2962    /// the prepared source borrow.
2963    pub(crate) fn function_macro_binding_at(
2964        &self,
2965        file: &ProjectFile,
2966        name: &str,
2967        before_byte: usize,
2968    ) -> Option<(ProjectFile, usize)> {
2969        let environment = self.macro_environment(file, before_byte);
2970        let binding = environment.binding(name)?;
2971        if binding.is_exact()
2972            && matches!(
2973                binding.definition,
2974                MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2975            )
2976        {
2977            return Some((binding.source.clone(), binding.declaration_byte));
2978        }
2979        // A definition under an unknown condition is still certain at a use
2980        // within that same branch. The checkpoint retains the event that made
2981        // the binding uncertain. Require that exact defining event, so an
2982        // intervening undef, include, or conditional redefinition cannot be
2983        // bypassed by a lexical search for an older macro.
2984        if binding.source != *file {
2985            return None;
2986        }
2987        let prepared = self.cpp.prepared_syntax(self.token, file)?;
2988        let root = prepared.tree().root_node();
2989        let source = prepared.source();
2990        let reference = root.descendant_for_byte_range(
2991            before_byte,
2992            before_byte.saturating_add(1).min(source.len()),
2993        )?;
2994        let reference_conditions = owning_preprocessor_conditionals(root, reference, source);
2995        let cell = self.macro_event_cell(file);
2996        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2997        let event = events
2998            .iter()
2999            .find(|event| event.byte() == binding.declaration_byte)?;
3000        let MacroEvent::Define {
3001            name: defined_name,
3002            binding: definition,
3003            conditionals,
3004            ..
3005        } = event
3006        else {
3007            return None;
3008        };
3009        if defined_name != name
3010            || conditionals.is_empty()
3011            || !conditionals
3012                .iter()
3013                .all(|condition| reference_conditions.contains(condition))
3014            || !matches!(
3015                definition.definition,
3016                MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
3017            )
3018        {
3019            return None;
3020        }
3021        Some((definition.source.clone(), definition.declaration_byte))
3022    }
3023
3024    /// Return the type argument at a C function-like macro invocation.
3025    ///
3026    /// Exact local macro definitions identify type formals from their parsed
3027    /// replacement. An unavailable definition is deliberately not inferred:
3028    /// C has no AST distinction between an external macro call and an
3029    /// ordinary call to an unindexed function.
3030    pub fn function_macro_type_argument<'tree>(
3031        &self,
3032        file: &ProjectFile,
3033        node: Node<'tree>,
3034        source: &str,
3035    ) -> Option<Node<'tree>> {
3036        if !is_c_source_file(file) {
3037            return None;
3038        }
3039        let (call, argument_index, argument) = c_function_macro_argument(node)?;
3040        let function = call.child_by_field_name("function")?;
3041        let function_name = node_text(function, source);
3042        if function_name.is_empty() {
3043            return None;
3044        }
3045        if !self.file_defines_macro_name(file, function_name)
3046            && !self
3047                .visible_identifier_candidates(file, function_name)
3048                .any(|candidate| candidate.is_macro())
3049        {
3050            return None;
3051        }
3052        let environment = self.macro_environment(file, call.start_byte());
3053        let binding = environment.binding(function_name)?;
3054        if !binding.is_exact() {
3055            return None;
3056        }
3057        let (parameters, replacement, variadic) = match &binding.definition {
3058            MacroDefinition::Function {
3059                parameters,
3060                replacement,
3061            } => (parameters, replacement, false),
3062            MacroDefinition::VariadicFunction {
3063                parameters,
3064                replacement,
3065            } => (parameters, replacement, true),
3066            MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
3067        };
3068        let actuals = call
3069            .child_by_field_name("arguments")
3070            .map(argument_children)
3071            .into_iter()
3072            .flatten()
3073            .collect::<Vec<_>>();
3074        let arity_matches = if variadic {
3075            actuals.len() >= parameters.len()
3076        } else {
3077            actuals.len() == parameters.len()
3078        };
3079        let type_parameters = self.macro_type_parameter_indices(
3080            &(binding.source.clone(), binding.declaration_byte),
3081            parameters,
3082            replacement,
3083        )?;
3084        if !arity_matches || !type_parameters.contains(&argument_index) {
3085            return None;
3086        }
3087        let type_node = macro_type_argument_node(argument, source)?;
3088        if self.names_a_macro_at(file, node_text(type_node, source), type_node.start_byte()) {
3089            return None;
3090        }
3091        Some(type_node)
3092    }
3093
3094    fn macro_local_binding_template(
3095        &self,
3096        binding: &MacroBinding,
3097        parameters: &[String],
3098        replacement: &str,
3099    ) -> Option<Arc<MacroLocalBindingTemplate>> {
3100        let key = (binding.source.clone(), binding.declaration_byte);
3101        if let Some(template) = self
3102            .macro_local_binding_templates
3103            .lock()
3104            .expect("C++ macro local-binding cache poisoned")
3105            .get(&key)
3106        {
3107            return template.clone();
3108        }
3109        let template = (|| {
3110            let body = self.parsed_macro_replacement_body(&key, parameters, replacement)?;
3111            let sentinel = body.source.as_str();
3112            let statements = body.statements()?;
3113            let declaration = single_named_child_matching(statements, |child| !child.is_extra())?;
3114            if declaration.kind() != "declaration" {
3115                return None;
3116            }
3117            let type_node = declaration
3118                .child_by_field_name("type")
3119                .or_else(|| first_type_child(declaration))?;
3120            let declarator = declaration.child_by_field_name("declarator").or_else(|| {
3121                let mut cursor = declaration.walk();
3122                declaration.named_children(&mut cursor).find_map(|child| {
3123                    if child.kind() == "init_declarator" {
3124                        child.child_by_field_name("declarator")
3125                    } else {
3126                        is_declarator_node(child).then_some(child)
3127                    }
3128                })
3129            })?;
3130            let name = extract_variable_name(declarator, sentinel)?;
3131            let pointer_depth =
3132                declared_name_binding(declaration, type_node, &name, sentinel)?.pointer_depth;
3133            let type_text = node_text(type_node, sentinel).trim();
3134            let declared_type = parameters
3135                .iter()
3136                .position(|parameter| parameter == type_text)
3137                .map(MacroLocalBindingTypeTemplate::Parameter)
3138                .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
3139            Some(Arc::new(MacroLocalBindingTemplate {
3140                name,
3141                declared_type,
3142                pointer_depth,
3143            }))
3144        })();
3145        self.macro_local_binding_templates
3146            .lock()
3147            .expect("C++ macro local-binding cache poisoned")
3148            .insert(key, template.clone());
3149        template
3150    }
3151
3152    /// The parsed replacement body of the function-like macro `definition`
3153    /// defines, or `None` when the replacement cannot be recovered exactly.
3154    ///
3155    /// `definition` is the defining `preproc_function_def` node in `file`, so
3156    /// the result describes that definition rather than whichever same-named
3157    /// macro a later reference resolves to.
3158    pub fn function_macro_replacement_body(
3159        &self,
3160        file: &ProjectFile,
3161        definition: Node<'_>,
3162        source: &str,
3163    ) -> Option<Arc<ParsedReplacementBody>> {
3164        debug_assert_eq!(definition.kind(), "preproc_function_def");
3165        let (parameters, replacement) = match Self::decode_macro_definition(definition, source) {
3166            MacroDefinition::Function {
3167                parameters,
3168                replacement,
3169            }
3170            | MacroDefinition::VariadicFunction {
3171                parameters,
3172                replacement,
3173            } => (parameters, replacement),
3174            MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
3175        };
3176        self.parsed_macro_replacement_body(
3177            &(file.clone(), definition.start_byte()),
3178            &parameters,
3179            &replacement,
3180        )
3181    }
3182
3183    /// Parse one function-like macro replacement inside the shared sentinel.
3184    ///
3185    /// The parse fails closed, and the failure is cached, whenever the
3186    /// sentinel tree carries an error or the replacement uses preprocessor
3187    /// syntax that has no C++ meaning. Token pasting and stringizing produce
3188    /// `ERROR` nodes; `__VA_ARGS__` parses as an ordinary identifier and is
3189    /// therefore rejected from the parsed tree instead of the source text.
3190    fn parsed_macro_replacement_body(
3191        &self,
3192        key: &(ProjectFile, usize),
3193        parameters: &[String],
3194        replacement: &str,
3195    ) -> Option<Arc<ParsedReplacementBody>> {
3196        if let Some(body) = self
3197            .macro_replacement_bodies
3198            .lock()
3199            .expect("C++ macro replacement body cache poisoned")
3200            .get(key)
3201        {
3202            return body.clone();
3203        }
3204        let body = (|| {
3205            if replacement.trim().is_empty() {
3206                return None;
3207            }
3208            let (source, tree, original_offsets) =
3209                Self::parse_macro_replacement_body(replacement, parameters)?;
3210            let body = ParsedReplacementBody {
3211                source,
3212                tree,
3213                body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3214                parameters: parameters.to_vec(),
3215                original_offsets,
3216            };
3217            body.statements()?;
3218            if body.expands_variadic_arguments() {
3219                return None;
3220            }
3221            Some(Arc::new(body))
3222        })();
3223        self.macro_replacement_bodies
3224            .lock()
3225            .expect("C++ macro replacement body cache poisoned")
3226            .insert(key.clone(), body.clone());
3227        body
3228    }
3229
3230    /// Parse a replacement while preserving a map from the parse buffer back
3231    /// to the original logical-line bytes. A block formal can occur where C
3232    /// requires a statement terminator (`if (condition) block`); the caller's
3233    /// actual block supplies that terminator only after macro substitution.
3234    /// Tree-sitter exposes the formal as an identifier inside recovery, so a
3235    /// semicolon is inserted at that AST-derived boundary for the fallback
3236    /// parse. No identifier text is scanned to find the insertion point.
3237    fn parse_macro_replacement_body(
3238        replacement: &str,
3239        parameters: &[String],
3240    ) -> Option<(String, Tree, MacroOriginMap)> {
3241        let parsed = parse_sentinel(
3242            MACRO_BODY_SENTINEL_PREFIX,
3243            SentinelInput::replacement(replacement),
3244            MACRO_BODY_SENTINEL_SUFFIX,
3245        );
3246        let normalized_end = parsed.source.len() - MACRO_BODY_SENTINEL_SUFFIX.len() - 1;
3247        let normalized = &parsed.source[MACRO_BODY_SENTINEL_PREFIX.len()..normalized_end];
3248        let normalized_offsets = &parsed.original_offsets;
3249        let source = parsed.source.as_str();
3250        let tree = &parsed.tree;
3251        if !tree.root_node().has_error() {
3252            return Some((
3253                parsed.source,
3254                parsed.tree,
3255                parsed.original_offsets.into_boxed_slice(),
3256            ));
3257        }
3258
3259        let body_offset = MACRO_BODY_SENTINEL_PREFIX.len();
3260        let mut insertion_points = Vec::new();
3261        let mut stack = vec![tree.root_node()];
3262        while let Some(node) = stack.pop() {
3263            if matches!(
3264                node.kind(),
3265                "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
3266            ) && parameters
3267                .iter()
3268                .any(|parameter| parameter.as_str() == node_text(node, source))
3269                && Self::macro_formal_needs_statement_separator(node)
3270            {
3271                let point = node.end_byte().saturating_sub(body_offset);
3272                if point <= normalized.len() && !insertion_points.contains(&point) {
3273                    insertion_points.push(point);
3274                }
3275            }
3276            push_named_children_reversed(node, &mut stack);
3277        }
3278        if insertion_points.is_empty() {
3279            return None;
3280        }
3281        insertion_points.sort_unstable();
3282        let mut recovered = Vec::with_capacity(normalized.len() + insertion_points.len());
3283        let mut original_offsets =
3284            Vec::with_capacity(normalized.len() + insertion_points.len() + 1);
3285        let mut next_insertion = 0;
3286        for (index, byte) in normalized.bytes().enumerate() {
3287            recovered.push(byte);
3288            original_offsets.push(normalized_offsets[index].clone());
3289            while insertion_points.get(next_insertion).copied() == Some(index + 1) {
3290                recovered.push(b';');
3291                let boundary = normalized_offsets[index].end;
3292                original_offsets.push(boundary..boundary);
3293                next_insertion += 1;
3294            }
3295        }
3296        original_offsets.push(replacement.len()..replacement.len());
3297        let recovered = String::from_utf8(recovered).expect("source text remains UTF-8");
3298        let parsed = parse_sentinel(
3299            MACRO_BODY_SENTINEL_PREFIX,
3300            SentinelInput {
3301                source: recovered,
3302                original_offsets,
3303            },
3304            MACRO_BODY_SENTINEL_SUFFIX,
3305        );
3306        let (source, tree, original_offsets) =
3307            (parsed.source, parsed.tree, parsed.original_offsets);
3308        if tree.root_node().has_error() {
3309            return None;
3310        }
3311        Some((source, tree, original_offsets.into_boxed_slice()))
3312    }
3313
3314    /// A formal is statement-shaped when the recovered tree places it directly
3315    /// at a statement boundary. This is the grammar recovery produced for
3316    /// replacement forms such as `if (condition) block`; an identifier inside
3317    /// an expression or call argument must not receive an artificial `;`.
3318    fn macro_formal_needs_statement_separator(node: Node<'_>) -> bool {
3319        let mut current = node;
3320        let mut crossed_recovery = false;
3321        while let Some(parent) = current.parent() {
3322            if parent.is_error() {
3323                crossed_recovery = true;
3324                current = parent;
3325                continue;
3326            }
3327            if matches!(
3328                parent.kind(),
3329                "call_expression"
3330                    | "argument_list"
3331                    | "field_expression"
3332                    | "binary_expression"
3333                    | "unary_expression"
3334                    | "assignment_expression"
3335                    | "conditional_expression"
3336                    | "parenthesized_expression"
3337                    | "subscript_expression"
3338            ) {
3339                return false;
3340            }
3341            if parent.kind() == "expression_statement" {
3342                return single_named_child_matching(parent, |child| !child.is_extra()).is_some();
3343            }
3344            if parent.kind() == "compound_statement" && current == node {
3345                return true;
3346            }
3347            if matches!(
3348                parent.kind(),
3349                "compound_statement" | "if_statement" | "while_statement" | "do_statement"
3350            ) {
3351                return crossed_recovery
3352                    || parent.child_by_field_name("consequence") == Some(current);
3353            }
3354            if matches!(parent.kind(), "declaration" | "init_declarator") {
3355                return false;
3356            }
3357            current = parent;
3358        }
3359        false
3360    }
3361
3362    /// Identify fixed macro formals that retain an unambiguous type role even
3363    /// when an unrelated part of the replacement needs parser recovery.
3364    ///
3365    /// Variadic production macros commonly contain token pasting elsewhere in
3366    /// the body. The strict shared replacement parser must keep rejecting that
3367    /// body for expansion modeling, but a type declaration such as `TY col`
3368    /// remains usable when the node itself retains a structured type role.
3369    fn macro_type_parameter_indices(
3370        &self,
3371        key: &(ProjectFile, usize),
3372        parameters: &[String],
3373        replacement: &str,
3374    ) -> Option<Arc<[usize]>> {
3375        if let Some(indices) = self
3376            .macro_type_parameters
3377            .lock()
3378            .expect("C++ macro type-parameter cache poisoned")
3379            .get(key)
3380        {
3381            return indices.clone();
3382        }
3383        #[cfg(any(test, feature = "test-support"))]
3384        self.macro_replacement_parse_count
3385            .fetch_add(1, Ordering::Relaxed);
3386        let indices = (|| {
3387            if replacement.trim().is_empty() {
3388                return None;
3389            }
3390            // Keep this parser permissive. Type-role extraction predates the
3391            // replacement-body model and deliberately survives unrelated
3392            // token-pasting or recovery elsewhere in a production macro.
3393            let parsed = parse_sentinel(
3394                MACRO_BODY_SENTINEL_PREFIX,
3395                SentinelInput::replacement(replacement),
3396                MACRO_BODY_SENTINEL_SUFFIX,
3397            );
3398            let (source, tree, original_offsets) =
3399                (parsed.source, parsed.tree, parsed.original_offsets);
3400            let body = ParsedReplacementBody {
3401                source,
3402                tree,
3403                body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3404                parameters: parameters.to_vec(),
3405                original_offsets: original_offsets.into_boxed_slice(),
3406            };
3407            macro_replacement_type_parameters(&body, parameters).map(Arc::from)
3408        })();
3409        self.macro_type_parameters
3410            .lock()
3411            .expect("C++ macro type-parameter cache poisoned")
3412            .insert(key.clone(), indices.clone());
3413        indices
3414    }
3415
3416    fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
3417        let replacement = if node.kind() == "preproc_function_def" {
3418            function_macro_replacement_span(node, source)
3419                .and_then(|span| source.get(span))
3420                .map(str::to_owned)
3421                .or_else(|| {
3422                    node.child_by_field_name("value")
3423                        .map(|value| node_text(value, source).to_string())
3424                })
3425                .unwrap_or_default()
3426        } else {
3427            node.child_by_field_name("value")
3428                .map(|value| node_text(value, source).to_string())
3429                .unwrap_or_default()
3430        };
3431        if node.kind() == "preproc_def" {
3432            return MacroDefinition::Object { replacement };
3433        }
3434        let Some(parameters) = node.child_by_field_name("parameters") else {
3435            return MacroDefinition::Unsupported;
3436        };
3437        let variadic = (0..parameters.child_count()).any(|index| {
3438            parameters
3439                .child(index)
3440                .is_some_and(|child| child.kind() == "...")
3441        });
3442        let parameters = (0..parameters.named_child_count())
3443            .filter_map(|index| parameters.named_child(index))
3444            .map(|parameter| node_text(parameter, source).to_string())
3445            .collect::<Vec<_>>();
3446        if variadic {
3447            MacroDefinition::VariadicFunction {
3448                parameters,
3449                replacement,
3450            }
3451        } else {
3452            MacroDefinition::Function {
3453                parameters,
3454                replacement,
3455            }
3456        }
3457    }
3458
3459    pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
3460        clone_or_default_file_cell(
3461            &self.macro_event_cells,
3462            file,
3463            "C++ macro event cache poisoned",
3464        )
3465    }
3466
3467    fn file_defines_macro_name(&self, file: &ProjectFile, name: &str) -> bool {
3468        if let Some(names) = self
3469            .macro_event_name_sets
3470            .lock()
3471            .expect("C++ macro event-name cache poisoned")
3472            .get(file)
3473            .cloned()
3474        {
3475            return names.contains(name);
3476        }
3477        let cell = self.macro_event_cell(file);
3478        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3479        let names = Arc::new(
3480            events
3481                .iter()
3482                .filter_map(|event| match event {
3483                    MacroEvent::Define { name, .. } => Some(name.clone()),
3484                    MacroEvent::Undef { .. }
3485                    | MacroEvent::Include { .. }
3486                    | MacroEvent::Invalidate { .. } => None,
3487                })
3488                .collect(),
3489        );
3490        self.macro_event_name_sets
3491            .lock()
3492            .expect("C++ macro event-name cache poisoned")
3493            .insert(file.clone(), Arc::clone(&names));
3494        names.contains(name)
3495    }
3496
3497    fn macro_environment_checkpoint_cell(
3498        &self,
3499        file: &ProjectFile,
3500    ) -> MacroEnvironmentCheckpointCell {
3501        clone_or_default_file_cell(
3502            &self.macro_environment_checkpoints,
3503            file,
3504            "C++ macro environment checkpoint cache poisoned",
3505        )
3506    }
3507
3508    /// The environment of `file`'s macro events applied up to `before_byte`.
3509    pub fn macro_environment(
3510        &self,
3511        file: &ProjectFile,
3512        before_byte: usize,
3513    ) -> Arc<MacroEnvironment> {
3514        #[cfg(any(test, feature = "test-support"))]
3515        self.macro_environment_request_count
3516            .fetch_add(1, Ordering::Relaxed);
3517        let cell = self.macro_event_cell(file);
3518        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3519        let frontier = events.partition_point(|event| event.byte() < before_byte);
3520        let checkpoint_cell = self.macro_environment_checkpoint_cell(file);
3521        let checkpoints =
3522            checkpoint_cell.get_or_init(|| self.build_macro_environment_checkpoints(file, events));
3523        let checkpoint = checkpoints.at_or_before(frontier);
3524        if checkpoint.frontier == frontier {
3525            return Arc::clone(&checkpoint.environment);
3526        }
3527        #[cfg(any(test, feature = "test-support"))]
3528        self.macro_environment_copy_count
3529            .fetch_add(1, Ordering::Relaxed);
3530        let mut environment = checkpoint.environment.as_ref().clone();
3531        let mut include_stack = HashSet::from_iter([file.clone()]);
3532        for event in &events[checkpoint.frontier..frontier] {
3533            self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3534        }
3535        Arc::new(environment)
3536    }
3537
3538    /// Apply `file`'s events once, keeping the environment at the prefixes
3539    /// [`MacroEnvironmentCheckpoints`] describes.
3540    fn build_macro_environment_checkpoints(
3541        &self,
3542        file: &ProjectFile,
3543        events: &[MacroEvent],
3544    ) -> MacroEnvironmentCheckpoints {
3545        #[cfg(any(test, feature = "test-support"))]
3546        self.macro_environment_checkpoint_build_count
3547            .fetch_add(1, Ordering::Relaxed);
3548        // The TU's build-proven defines hold from the first byte (#2011): they
3549        // are facts of the whole compile, so they seed the frontier-zero
3550        // checkpoint. A later explicit #undef event still overrides them
3551        // through `known_undefined_names`.
3552        let mut environment = MacroEnvironment {
3553            build_proven_defines: self
3554                .compile_proven_guards(file)
3555                .iter()
3556                .filter_map(|guard| match guard {
3557                    PreprocessorGuard::Defined(name) => Some(name.clone()),
3558                    _ => None,
3559                })
3560                .collect(),
3561            ..MacroEnvironment::default()
3562        };
3563        let mut checkpoints = vec![MacroEnvironmentCheckpoint {
3564            frontier: 0,
3565            environment: Arc::new(environment.clone()),
3566        }];
3567        // Keep roughly one fixed stride's worth of checkpoints, while taking
3568        // a checkpoint at every event for event sets no larger than the fixed
3569        // stride. Generated C tables commonly have thousands of uses between
3570        // one #define and a trailing #undef; with a fixed stride those
3571        // identical frontier requests would all copy and replay the same
3572        // prefix.
3573        let checkpoint_stride = events
3574            .len()
3575            .div_ceil(MACRO_ENVIRONMENT_CHECKPOINT_STRIDE)
3576            .clamp(1, MACRO_ENVIRONMENT_CHECKPOINT_STRIDE);
3577        let mut include_stack = HashSet::from_iter([file.clone()]);
3578        for (index, event) in events.iter().enumerate() {
3579            self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3580            let frontier = index + 1;
3581            if frontier % checkpoint_stride == 0 || matches!(event, MacroEvent::Include { .. }) {
3582                checkpoints.push(MacroEnvironmentCheckpoint {
3583                    frontier,
3584                    environment: Arc::new(environment.clone()),
3585                });
3586            }
3587        }
3588        MacroEnvironmentCheckpoints { checkpoints }
3589    }
3590
3591    /// Whether `name` is bound as a macro at `before_byte` in `file`,
3592    /// including a binding this environment cannot pin to one replacement
3593    /// (a conditional `#define`, or a function-like macro).
3594    ///
3595    /// [`Self::object_macro_replacement_at`] collapses every such binding to
3596    /// `None`, which is indistinguishable from "not a macro at all". A caller
3597    /// that must not read a macro token as an ordinary type name needs the two
3598    /// apart: an unexpandable macro is an unknown, a plain identifier is not.
3599    pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
3600        self.macro_environment(file, before_byte)
3601            .binding(name)
3602            .is_some()
3603    }
3604
3605    pub fn macro_name_may_be_bound_at(
3606        &self,
3607        file: &ProjectFile,
3608        name: &str,
3609        before_byte: usize,
3610    ) -> bool {
3611        self.macro_environment(file, before_byte).may_bind(name)
3612    }
3613
3614    /// Whether the active macro binding at this reference is the requested
3615    /// indexed definition. Name equality alone is not enough because two
3616    /// headers can define the same macro for different translation units.
3617    pub fn macro_binding_matches_target_at(
3618        &self,
3619        analyzer: &CppGraphSource<'_>,
3620        file: &ProjectFile,
3621        name: &str,
3622        before_byte: usize,
3623        target: &CodeUnit,
3624    ) -> bool {
3625        let ranges = analyzer.ranges(target);
3626        let declaration_bytes = self.macro_declaration_bytes(target, &ranges);
3627        self.macro_binding_matches_target_declaration_at(
3628            file,
3629            name,
3630            before_byte,
3631            target.source(),
3632            &declaration_bytes,
3633        )
3634    }
3635
3636    /// Resolve declaration ranges once for inverse scans that test many call
3637    /// sites against one macro target. Walking a large generated syntax tree
3638    /// back to the same `#define` for every call site is otherwise quadratic
3639    /// in the number of top-level declarations.
3640    pub(crate) fn macro_declaration_bytes(
3641        &self,
3642        target: &CodeUnit,
3643        ranges: &[Range],
3644    ) -> Vec<usize> {
3645        let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
3646            return Vec::new();
3647        };
3648        ranges
3649            .iter()
3650            .filter_map(|range| {
3651                let mut node = node_for_exact_range(prepared.tree().root_node(), range)?;
3652                while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
3653                    node = node.parent()?;
3654                }
3655                Some(node.start_byte())
3656            })
3657            .collect()
3658    }
3659
3660    pub(crate) fn macro_binding_matches_target_declaration_at(
3661        &self,
3662        file: &ProjectFile,
3663        name: &str,
3664        before_byte: usize,
3665        target_source: &ProjectFile,
3666        target_declaration_bytes: &[usize],
3667    ) -> bool {
3668        let environment = self.macro_environment(file, before_byte);
3669        let Some(binding) = environment.binding(name) else {
3670            return false;
3671        };
3672        if binding.definition == MacroDefinition::Unsupported {
3673            return false;
3674        }
3675        // A normal header guard makes the replacement text conditional, but
3676        // it does not erase the definition site's source and byte identity.
3677        // Keep that identity even when expansion details are not exact.
3678        if binding.source != *target_source {
3679            return false;
3680        }
3681        target_declaration_bytes.contains(&binding.declaration_byte)
3682    }
3683
3684    /// Resolve an ordinary expression-position macro token at its exact byte.
3685    ///
3686    /// Calls and preprocessor-condition tokens have separate resolution
3687    /// surfaces. Declaration names, macro parameters, and labels are not
3688    /// references. Keeping that role policy here makes forward and both
3689    /// inverse graph builders consume the same activation verdict (#2093).
3690    pub fn resolve_ordinary_macro_reference(
3691        &self,
3692        analyzer: &CppGraphSource<'_>,
3693        file: &ProjectFile,
3694        node: Node<'_>,
3695        source: &str,
3696    ) -> OrdinaryMacroReferenceResolution {
3697        if !is_ordinary_macro_reference_node(node) {
3698            return OrdinaryMacroReferenceResolution::Missing;
3699        }
3700        let name = node_text(node, source);
3701        if name.is_empty() {
3702            return OrdinaryMacroReferenceResolution::Missing;
3703        }
3704        let visible = self
3705            .visible_identifier_candidates(file, name)
3706            .filter(|candidate| candidate.is_macro())
3707            .cloned()
3708            .collect::<Vec<_>>();
3709        let mut exact = Vec::new();
3710        for candidate in &visible {
3711            if self.macro_binding_matches_target_at(
3712                analyzer,
3713                file,
3714                name,
3715                node.start_byte(),
3716                candidate,
3717            ) && !exact
3718                .iter()
3719                .any(|existing| same_visible_symbol(existing, candidate))
3720            {
3721                exact.push(candidate.clone());
3722            }
3723        }
3724        match exact.len() {
3725            1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
3726            2.. => OrdinaryMacroReferenceResolution::Ambiguous,
3727            0 if !visible.is_empty()
3728                && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
3729            {
3730                OrdinaryMacroReferenceResolution::Ambiguous
3731            }
3732            0 => OrdinaryMacroReferenceResolution::Missing,
3733        }
3734    }
3735
3736    /// Collect reference-capable C tokens beneath tree-sitter recovery nodes.
3737    ///
3738    /// The ordinary census deliberately skips every `ERROR` subtree. This
3739    /// separate, precision-only frontier admits only roles that retain enough
3740    /// structure for the C usage graph to interpret independently (#2089).
3741    /// Macro evidence comes from this visibility index at the exact byte; no
3742    /// source-text parsing or terminal-name fallback is used.
3743    pub fn recovered_c_reference_ranges(
3744        &self,
3745        file: &ProjectFile,
3746        root: Node<'_>,
3747        source: &str,
3748        limit: usize,
3749    ) -> RecoveredCReferenceRanges {
3750        if !is_c_source_file(file) {
3751            return RecoveredCReferenceRanges::Complete(Vec::new());
3752        }
3753        let mut ranges = Vec::new();
3754        let mut seen = HashSet::default();
3755        let mut stack = vec![(root, root.is_error())];
3756        while let Some((node, inside_error)) = stack.pop() {
3757            // A `macro_type_specifier` is the macro-declaration grammar's
3758            // structured form of the same evidence an `ERROR` carried: it is
3759            // admitted only where the parse would otherwise fail, so the type
3760            // it wraps keeps its structured references.
3761            let inside_error =
3762                inside_error || node.is_error() || node.kind() == "macro_type_specifier";
3763            if node.kind() == "preproc_arg" {
3764                // Tree-sitter keeps an object-like replacement opaque. The
3765                // inverse extractor uses the same parsed replacement helper;
3766                // retain its exact macro/type leaves for precision membership
3767                // even when the preprocessor node is outside ERROR recovery.
3768                let macro_value_kind = node.parent().and_then(|parent| {
3769                    (parent.child_by_field_name("value") == Some(node)).then_some(parent.kind())
3770                });
3771                if matches!(
3772                    macro_value_kind,
3773                    Some("preproc_def" | "preproc_function_def")
3774                ) {
3775                    let name = node_text(node, source);
3776                    if !name.is_empty()
3777                        && self.macro_name_may_be_bound_at(file, name, node.start_byte())
3778                        && !push_recovered_c_range(
3779                            &mut ranges,
3780                            &mut seen,
3781                            node.start_byte(),
3782                            node.end_byte(),
3783                            node,
3784                            limit,
3785                        )
3786                    {
3787                        return RecoveredCReferenceRanges::LimitExceeded;
3788                    }
3789                }
3790                if macro_value_kind == Some("preproc_def") {
3791                    for reference in object_macro_replacement_type_references(node, source) {
3792                        for range in reference.component_ranges {
3793                            let visible = self
3794                                .visible_identifier_candidates(file, &source[range.clone()])
3795                                .any(|candidate| candidate.is_class() || candidate.is_module());
3796                            if visible
3797                                && !push_recovered_c_range(
3798                                    &mut ranges,
3799                                    &mut seen,
3800                                    range.start,
3801                                    range.end,
3802                                    node,
3803                                    limit,
3804                                )
3805                            {
3806                                return RecoveredCReferenceRanges::LimitExceeded;
3807                            }
3808                        }
3809                    }
3810                }
3811            }
3812            if inside_error
3813                && recovered_c_reference_node(self, file, node, source)
3814                && !push_recovered_c_range(
3815                    &mut ranges,
3816                    &mut seen,
3817                    node.start_byte(),
3818                    node.end_byte(),
3819                    node,
3820                    limit,
3821                )
3822            {
3823                return RecoveredCReferenceRanges::LimitExceeded;
3824            }
3825            let mut cursor = node.walk();
3826            for child in node.named_children(&mut cursor) {
3827                stack.push((child, inside_error));
3828            }
3829        }
3830        ranges.sort_unstable();
3831        RecoveredCReferenceRanges::Complete(ranges)
3832    }
3833
3834    /// Whether this target is an indexed macro visible from this file.
3835    ///
3836    /// An unresolved conditional can make more than one same-name macro a
3837    /// possible active binding. Each possible target can keep the site as an
3838    /// unproven hit. A macro in an unrelated translation unit stays excluded.
3839    pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
3840        self.visible_identifier_candidates(file, target.identifier())
3841            .filter(|candidate| candidate.is_macro())
3842            .any(|candidate| {
3843                candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
3844            })
3845    }
3846
3847    pub fn object_macro_replacement_at(
3848        &self,
3849        file: &ProjectFile,
3850        name: &str,
3851        before_byte: usize,
3852    ) -> Option<String> {
3853        let environment = self.macro_environment(file, before_byte);
3854        let binding = environment.binding(name)?;
3855        if !binding.exact {
3856            return None;
3857        }
3858        match &binding.definition {
3859            MacroDefinition::Object { replacement } => Some(replacement.clone()),
3860            MacroDefinition::Function { .. }
3861            | MacroDefinition::VariadicFunction { .. }
3862            | MacroDefinition::Unsupported => None,
3863        }
3864    }
3865
3866    fn apply_macro_events(
3867        &self,
3868        file: &ProjectFile,
3869        environment: &mut MacroEnvironment,
3870        include_stack: &mut HashSet<ProjectFile>,
3871    ) {
3872        if !include_stack.insert(file.clone()) {
3873            return;
3874        }
3875        if self.cpp.prepared_syntax(self.token, file).is_none() {
3876            environment.mark_unknown_names(file, 0);
3877            include_stack.remove(file);
3878            return;
3879        }
3880        match self.macro_include_protection(file) {
3881            MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
3882                Some(binding) if binding.is_exact() => {
3883                    include_stack.remove(file);
3884                    return;
3885                }
3886                Some(_) | None if environment.unknown_names => {
3887                    let mut ambiguous_seen = HashSet::default();
3888                    self.mark_macro_events_ambiguous(
3889                        file,
3890                        environment,
3891                        &mut ambiguous_seen,
3892                        file,
3893                        0,
3894                    );
3895                    include_stack.remove(file);
3896                    return;
3897                }
3898                Some(_) => {
3899                    let mut ambiguous_seen = HashSet::default();
3900                    self.mark_macro_events_ambiguous(
3901                        file,
3902                        environment,
3903                        &mut ambiguous_seen,
3904                        file,
3905                        0,
3906                    );
3907                    include_stack.remove(file);
3908                    return;
3909                }
3910                None => {}
3911            },
3912            MacroIncludeProtection::PragmaOnce => {
3913                if !environment.applied_pragma_once_files.insert(file.clone()) {
3914                    include_stack.remove(file);
3915                    return;
3916                }
3917                if environment.maybe_applied_pragma_once_files.remove(file) {
3918                    // A prior conditional include may already have consumed the pragma-once
3919                    // header. This unconditional include guarantees it is consumed now, but
3920                    // cannot prove whether its events occur before or after intervening local
3921                    // macro changes, so preserve the union as ambiguous.
3922                    let mut ambiguous_seen = HashSet::default();
3923                    environment.applied_pragma_once_files.remove(file);
3924                    self.mark_macro_events_ambiguous(
3925                        file,
3926                        environment,
3927                        &mut ambiguous_seen,
3928                        file,
3929                        0,
3930                    );
3931                    environment.maybe_applied_pragma_once_files.remove(file);
3932                    environment.applied_pragma_once_files.insert(file.clone());
3933                    include_stack.remove(file);
3934                    return;
3935                }
3936            }
3937            MacroIncludeProtection::None => {}
3938        }
3939        let cell = self.macro_event_cell(file);
3940        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3941        for event in events {
3942            self.apply_macro_event(file, event, environment, include_stack);
3943        }
3944        include_stack.remove(file);
3945    }
3946
3947    fn apply_macro_event(
3948        &self,
3949        file: &ProjectFile,
3950        event: &MacroEvent,
3951        environment: &mut MacroEnvironment,
3952        include_stack: &mut HashSet<ProjectFile>,
3953    ) {
3954        #[cfg(any(test, feature = "test-support"))]
3955        self.macro_event_application_count
3956            .fetch_add(1, Ordering::Relaxed);
3957        match event {
3958            MacroEvent::Define {
3959                name,
3960                binding,
3961                conditionals,
3962                byte,
3963            } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3964                Some(true) => environment.insert(name.clone(), binding.clone()),
3965                Some(false) => {}
3966                None => Self::merge_conditional_macro_definition(
3967                    environment,
3968                    name,
3969                    binding,
3970                    file,
3971                    *byte,
3972                ),
3973            },
3974            MacroEvent::Undef {
3975                name,
3976                conditionals,
3977                byte,
3978            } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3979                Some(true) => environment.remove(name),
3980                Some(false) => {}
3981                None => {
3982                    if environment.binding(name).is_some() {
3983                        environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
3984                    }
3985                }
3986            },
3987            MacroEvent::Include {
3988                targets,
3989                conditionals,
3990                byte,
3991            } => {
3992                let condition =
3993                    self.macro_event_condition_value(file, *byte, environment, conditionals);
3994                if condition == Some(false) {
3995                    return;
3996                }
3997                if targets.is_empty() {
3998                    environment.mark_unknown_names(file, *byte);
3999                    return;
4000                }
4001                if condition.is_none() || targets.len() > 1 {
4002                    let mut ambiguous_seen = HashSet::default();
4003                    for target in targets {
4004                        self.mark_macro_events_ambiguous(
4005                            target,
4006                            environment,
4007                            &mut ambiguous_seen,
4008                            file,
4009                            *byte,
4010                        );
4011                    }
4012                } else if let Some(target) = targets.first() {
4013                    self.apply_macro_events(target, environment, include_stack);
4014                }
4015            }
4016            MacroEvent::Invalidate { byte } => {
4017                for binding in environment.bindings.values_mut() {
4018                    *binding = MacroBinding::uncertain_from(binding, file, *byte);
4019                }
4020            }
4021        }
4022    }
4023
4024    /// Evaluate the structured conditional path that owns one macro event.
4025    ///
4026    /// `Some(true)` and `Some(false)` are proofs from exact macro bindings at
4027    /// this source byte. `None` preserves the old conditional merge when a
4028    /// build/configuration input or an unsupported expression is involved.
4029    ///
4030    /// `conditionals` is the event's own [`OwningPreprocessorConditionals`]:
4031    /// which ancestors structurally own the event was decided when the events
4032    /// were collected, so all this walk does is find those nodes again and ask
4033    /// the environment what their conditions are worth here.
4034    fn macro_event_condition_value(
4035        &self,
4036        file: &ProjectFile,
4037        event_byte: usize,
4038        environment: &MacroEnvironment,
4039        conditionals: &OwningPreprocessorConditionals,
4040    ) -> Option<bool> {
4041        if conditionals.is_empty() {
4042            return Some(true);
4043        }
4044        let prepared = self.cpp.prepared_syntax(self.token, file)?;
4045        let source = prepared.source();
4046        let root = prepared.tree().root_node();
4047        let descendant = root.descendant_for_byte_range(
4048            event_byte,
4049            event_byte.saturating_add(1).min(source.len()),
4050        )?;
4051        let mut unknown = false;
4052        let mut current = descendant.parent();
4053        while let Some(conditional) = current {
4054            if matches!(
4055                conditional.kind(),
4056                "preproc_if" | "preproc_ifdef" | "preproc_elif"
4057            ) && conditionals.contains(&conditional.start_byte())
4058            {
4059                let mut value = match conditional.kind() {
4060                    "preproc_ifdef" => {
4061                        let name = conditional.child_by_field_name("name")?;
4062                        let defined =
4063                            self.macro_name_defined_value(environment, node_text(name, source));
4064                        match conditional.child(0)?.kind() {
4065                            "#ifdef" => defined,
4066                            "#ifndef" => defined.map(|defined| !defined),
4067                            _ => None,
4068                        }
4069                    }
4070                    "preproc_if" | "preproc_elif" => conditional
4071                        .child_by_field_name("condition")
4072                        .and_then(|condition| {
4073                            self.preprocessor_integer_value(
4074                                condition,
4075                                source,
4076                                environment,
4077                                &mut Vec::new(),
4078                                0,
4079                            )
4080                        })
4081                        .map(|value| value != 0),
4082                    _ => unreachable!(),
4083                };
4084                match preprocessor_branch_of_descendant(
4085                    conditional,
4086                    descendant,
4087                    &mut PreprocessorContextCache::default(),
4088                ) {
4089                    PreprocessorBranch::Consequence => {}
4090                    PreprocessorBranch::Structured(_) | PreprocessorBranch::DisplacedElse => {
4091                        value = value.map(|value| !value);
4092                    }
4093                    PreprocessorBranch::Unreadable => value = None,
4094                }
4095                match value {
4096                    Some(true) => {}
4097                    Some(false) => return Some(false),
4098                    None => unknown = true,
4099                }
4100            }
4101            current = conditional.parent();
4102        }
4103        (!unknown).then_some(true)
4104    }
4105
4106    fn macro_name_defined_value(&self, environment: &MacroEnvironment, name: &str) -> Option<bool> {
4107        if environment.known_undefined_names.contains(name) {
4108            return Some(false);
4109        }
4110        if let Some(binding) = environment.binding(name) {
4111            return binding.is_exact().then_some(true);
4112        }
4113        environment
4114            .build_proven_defines
4115            .contains(name)
4116            .then_some(true)
4117    }
4118
4119    fn preprocessor_integer_value(
4120        &self,
4121        expression: Node<'_>,
4122        source: &str,
4123        environment: &MacroEnvironment,
4124        expansion_stack: &mut Vec<(ProjectFile, usize)>,
4125        depth: usize,
4126    ) -> Option<i128> {
4127        // Macro replacement graphs can cycle. This explicit bound makes the
4128        // otherwise recursive AST evaluation stack-safe for hostile input.
4129        if depth >= 64 {
4130            return None;
4131        }
4132        match expression.kind() {
4133            "number_literal" => parse_cpp_integer_literal(node_text(expression, source)),
4134            "identifier" | "type_identifier" => {
4135                let binding = environment.binding(node_text(expression, source))?;
4136                if !binding.is_exact() {
4137                    return None;
4138                }
4139                let MacroDefinition::Object { replacement } = &binding.definition else {
4140                    return None;
4141                };
4142                let identity = (binding.source.clone(), binding.declaration_byte);
4143                if expansion_stack.contains(&identity) {
4144                    return None;
4145                }
4146                expansion_stack.push(identity);
4147                let parsed = self.parsed_macro_replacement(binding, replacement);
4148                let value = match parsed.as_ref() {
4149                    ParsedMacroReplacement::Parsed {
4150                        source: replacement_source,
4151                        tree,
4152                    } => first_descendant_of_kind(tree.root_node(), "call_expression")
4153                        .and_then(|call| call.child_by_field_name("arguments"))
4154                        .and_then(|arguments| argument_children(arguments).next())
4155                        .and_then(|argument| {
4156                            self.preprocessor_integer_value(
4157                                argument,
4158                                replacement_source,
4159                                environment,
4160                                expansion_stack,
4161                                depth + 1,
4162                            )
4163                        }),
4164                    ParsedMacroReplacement::Unsupported => None,
4165                };
4166                expansion_stack.pop();
4167                value
4168            }
4169            "preproc_defined" => {
4170                let mut cursor = expression.walk();
4171                let name = expression
4172                    .named_children(&mut cursor)
4173                    .find(|child| child.kind() == "identifier")?;
4174                self.macro_name_defined_value(environment, node_text(name, source))
4175                    .map(i128::from)
4176            }
4177            "parenthesized_expression" => expression.named_child(0).and_then(|child| {
4178                self.preprocessor_integer_value(
4179                    child,
4180                    source,
4181                    environment,
4182                    expansion_stack,
4183                    depth + 1,
4184                )
4185            }),
4186            "unary_expression" => {
4187                let operator = expression.child_by_field_name("operator")?.kind();
4188                let argument = expression.child_by_field_name("argument")?;
4189                let value = self.preprocessor_integer_value(
4190                    argument,
4191                    source,
4192                    environment,
4193                    expansion_stack,
4194                    depth + 1,
4195                )?;
4196                match operator {
4197                    "+" => Some(value),
4198                    "-" => value.checked_neg(),
4199                    "!" => Some(i128::from(value == 0)),
4200                    "~" => Some(!value),
4201                    _ => None,
4202                }
4203            }
4204            "binary_expression" => {
4205                let left = self.preprocessor_integer_value(
4206                    expression.child_by_field_name("left")?,
4207                    source,
4208                    environment,
4209                    expansion_stack,
4210                    depth + 1,
4211                )?;
4212                let right = self.preprocessor_integer_value(
4213                    expression.child_by_field_name("right")?,
4214                    source,
4215                    environment,
4216                    expansion_stack,
4217                    depth + 1,
4218                )?;
4219                match expression.child_by_field_name("operator")?.kind() {
4220                    "+" => left.checked_add(right),
4221                    "-" => left.checked_sub(right),
4222                    "*" => left.checked_mul(right),
4223                    "/" => left.checked_div(right),
4224                    "%" => left.checked_rem(right),
4225                    "<<" => u32::try_from(right)
4226                        .ok()
4227                        .and_then(|shift| left.checked_shl(shift)),
4228                    ">>" => u32::try_from(right)
4229                        .ok()
4230                        .and_then(|shift| left.checked_shr(shift)),
4231                    "<" => Some(i128::from(left < right)),
4232                    "<=" => Some(i128::from(left <= right)),
4233                    ">" => Some(i128::from(left > right)),
4234                    ">=" => Some(i128::from(left >= right)),
4235                    "==" => Some(i128::from(left == right)),
4236                    "!=" => Some(i128::from(left != right)),
4237                    "&" => Some(left & right),
4238                    "|" => Some(left | right),
4239                    "^" => Some(left ^ right),
4240                    "&&" => Some(i128::from(left != 0 && right != 0)),
4241                    "||" => Some(i128::from(left != 0 || right != 0)),
4242                    _ => None,
4243                }
4244            }
4245            _ => None,
4246        }
4247    }
4248
4249    fn mark_macro_events_ambiguous(
4250        &self,
4251        file: &ProjectFile,
4252        environment: &mut MacroEnvironment,
4253        include_stack: &mut HashSet<ProjectFile>,
4254        conditional_file: &ProjectFile,
4255        conditional_byte: usize,
4256    ) {
4257        if !include_stack.insert(file.clone()) {
4258            return;
4259        }
4260        if self.cpp.prepared_syntax(self.token, file).is_none() {
4261            environment.mark_unknown_names(conditional_file, conditional_byte);
4262            return;
4263        }
4264        match self.macro_include_protection(file) {
4265            MacroIncludeProtection::MacroGuard(guard) => {
4266                if environment
4267                    .binding(&guard)
4268                    .is_some_and(MacroBinding::is_exact)
4269                {
4270                    return;
4271                }
4272            }
4273            MacroIncludeProtection::PragmaOnce => {
4274                if environment.applied_pragma_once_files.contains(file) {
4275                    return;
4276                }
4277                environment
4278                    .maybe_applied_pragma_once_files
4279                    .insert(file.clone());
4280            }
4281            MacroIncludeProtection::None => {}
4282        }
4283        let cell = self.macro_event_cell(file);
4284        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
4285        for event in events {
4286            #[cfg(any(test, feature = "test-support"))]
4287            self.macro_event_application_count
4288                .fetch_add(1, Ordering::Relaxed);
4289            match event {
4290                MacroEvent::Define { name, binding, .. } => {
4291                    Self::merge_conditional_macro_definition(
4292                        environment,
4293                        name,
4294                        binding,
4295                        conditional_file,
4296                        conditional_byte,
4297                    );
4298                }
4299                MacroEvent::Undef { name, .. } => {
4300                    if environment.binding(name).is_some() {
4301                        environment.insert(
4302                            name.clone(),
4303                            MacroBinding::ambiguous(conditional_file, conditional_byte),
4304                        );
4305                    } else {
4306                        environment.remove_known_undefined(name);
4307                    }
4308                }
4309                MacroEvent::Include { targets, .. } => {
4310                    if targets.is_empty() {
4311                        environment.mark_unknown_names(conditional_file, conditional_byte);
4312                        continue;
4313                    }
4314                    for target in targets {
4315                        self.mark_macro_events_ambiguous(
4316                            target,
4317                            environment,
4318                            include_stack,
4319                            conditional_file,
4320                            conditional_byte,
4321                        );
4322                    }
4323                }
4324                MacroEvent::Invalidate { .. } => {
4325                    for binding in environment.bindings.values_mut() {
4326                        *binding = MacroBinding::uncertain_from(
4327                            binding,
4328                            conditional_file,
4329                            conditional_byte,
4330                        );
4331                    }
4332                }
4333            }
4334        }
4335    }
4336
4337    fn merge_conditional_macro_definition(
4338        environment: &mut MacroEnvironment,
4339        name: &str,
4340        possible_binding: &MacroBinding,
4341        conditional_file: &ProjectFile,
4342        conditional_byte: usize,
4343    ) {
4344        // A conditional include can revisit an already-active guarded header.
4345        // If the possible branch defines the exact same macro, both outcomes
4346        // leave the binding unchanged; degrading it to Unknown would discard
4347        // proof because of an unrelated unresolved macro name (#2092).
4348        if environment.binding(name).is_some_and(|current| {
4349            current.definition != MacroDefinition::Unsupported
4350                && current.definition == possible_binding.definition
4351        }) {
4352            return;
4353        }
4354        environment.insert(
4355            name.to_string(),
4356            MacroBinding::ambiguous(conditional_file, conditional_byte),
4357        );
4358    }
4359
4360    pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
4361        let cell = self
4362            .macro_include_protection_cells
4363            .lock()
4364            .expect("C++ include protection cache poisoned")
4365            .entry(file.clone())
4366            .or_default()
4367            .clone();
4368        cell.get_or_init(|| {
4369            self.cpp.prepared_syntax(self.token, file).map_or(
4370                MacroIncludeProtection::None,
4371                |prepared| {
4372                    top_level_macro_include_protection(
4373                        prepared.tree().root_node(),
4374                        prepared.source(),
4375                    )
4376                },
4377            )
4378        })
4379        .clone()
4380    }
4381
4382    fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
4383        collect_macro_events(self.cpp, self.token, file)
4384    }
4385
4386    pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
4387        self.ordinary_type_import_cells
4388            .lock()
4389            .expect("C++ ordinary type import cache poisoned")
4390            .entry(file.clone())
4391            .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
4392            .clone()
4393    }
4394
4395    pub fn project_using_index(
4396        &self,
4397        build: impl FnOnce() -> ProjectUsingIndex,
4398    ) -> &ProjectUsingIndex {
4399        self.project_using_index.get_or_init(build)
4400    }
4401
4402    pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
4403        let mut files = self
4404            .visible_source_files_by_root
4405            .values()
4406            .flatten()
4407            .cloned()
4408            .collect::<HashSet<_>>()
4409            .into_iter()
4410            .collect::<Vec<_>>();
4411        files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
4412        files
4413    }
4414
4415    /// The namespace aliases written in `root`'s visible source files, by name.
4416    pub(super) fn namespace_alias_cell(
4417        &self,
4418        root: &ProjectFile,
4419    ) -> super::namespace_alias::NamespaceAliasCell {
4420        self.namespace_alias_sites
4421            .lock()
4422            .expect("C++ namespace alias cache poisoned")
4423            .entry(root.clone())
4424            .or_default()
4425            .clone()
4426    }
4427
4428    /// `root` and every source file its include closure makes visible.
4429    pub(super) fn visible_source_files(&self, root: &ProjectFile) -> Vec<ProjectFile> {
4430        self.visible_source_files_by_root.get(root).map_or_else(
4431            || vec![root.clone()],
4432            |files| {
4433                files
4434                    .iter()
4435                    .chain([root])
4436                    .cloned()
4437                    .collect::<HashSet<_>>()
4438                    .into_iter()
4439                    .collect()
4440            },
4441        )
4442    }
4443
4444    pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
4445        self.visible_source_files_by_root
4446            .get(root)
4447            .is_some_and(|files| files.contains(source))
4448    }
4449
4450    fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
4451        let cached = self
4452            .visible_parser_alias_name_sets
4453            .read()
4454            .expect("visible parser alias-name cache poisoned")
4455            .get(file)
4456            .cloned();
4457        let cell = if let Some(cached) = cached {
4458            cached
4459        } else {
4460            let mut cells = self
4461                .visible_parser_alias_name_sets
4462                .write()
4463                .expect("visible parser alias-name cache poisoned");
4464            Arc::clone(
4465                cells
4466                    .entry(file.clone())
4467                    .or_insert_with(|| Arc::new(OnceLock::new())),
4468            )
4469        };
4470        if let Some(names) = cell.get() {
4471            return names.contains(name);
4472        }
4473        let mut names = HashSet::default();
4474        let visible_files = self
4475            .visible_source_files_by_root
4476            .get(file)
4477            .cloned()
4478            .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
4479        for visible_file in visible_files {
4480            let Some(aliases) = self.canonical_alias_cell(self.cpp, &visible_file) else {
4481                return false;
4482            };
4483            for alias in aliases.get().expect("published canonical aliases").iter() {
4484                names.insert(alias.name.clone());
4485            }
4486        }
4487        #[cfg(any(test, feature = "test-support"))]
4488        self.visible_parser_alias_name_set_build_count
4489            .fetch_add(1, Ordering::Relaxed);
4490        cell.get_or_init(|| names).contains(name)
4491    }
4492
4493    pub fn parser_alias_name_may_resolve_to_target(
4494        &self,
4495        file: &ProjectFile,
4496        alias_name: &str,
4497        target: &CodeUnit,
4498    ) -> bool {
4499        let started = std::time::Instant::now();
4500        self.parser_alias_fallback_calls
4501            .fetch_add(1, Ordering::Relaxed);
4502        let key = (
4503            file.clone(),
4504            alias_name.to_string(),
4505            logical_symbol_key(target),
4506        );
4507        let cached = self
4508            .parser_alias_target_matches
4509            .read()
4510            .expect("parser alias target-match cache poisoned")
4511            .get(&key)
4512            .cloned();
4513        let cell = if let Some(cached) = cached {
4514            cached
4515        } else {
4516            let mut cells = self
4517                .parser_alias_target_matches
4518                .write()
4519                .expect("parser alias target-match cache poisoned");
4520            Arc::clone(
4521                cells
4522                    .entry(key)
4523                    .or_insert_with(|| Arc::new(OnceLock::new())),
4524            )
4525        };
4526        let matched = *cell.get_or_init(|| {
4527            any_visible_source_file_matches(
4528                &self.visible_source_files_by_root,
4529                file,
4530                |visible_file| {
4531                    self.parser_alias_fallback_files
4532                        .fetch_add(1, Ordering::Relaxed);
4533                    self.file_alias_matches(self.cpp, visible_file, alias_name, target)
4534                },
4535            )
4536        });
4537        self.parser_alias_fallback_elapsed_micros.fetch_add(
4538            started.elapsed().as_micros().min(usize::MAX as u128) as usize,
4539            Ordering::Relaxed,
4540        );
4541        matched
4542    }
4543
4544    fn callable_arities_for_target(
4545        &self,
4546        analyzer: &CppGraphSource<'_>,
4547        cpp: &dyn CppSource,
4548        file: &ProjectFile,
4549        prepared: &PreparedSyntaxTree,
4550        spec: &TargetSpec,
4551    ) -> Vec<ActivatedCallableArity> {
4552        let Some(signature) = spec.target.signature() else {
4553            return Vec::new();
4554        };
4555        let Some(candidates) = self
4556            .visible_by_identifier
4557            .get(file)
4558            .and_then(|by_name| by_name.get(&spec.member_name))
4559        else {
4560            return Vec::new();
4561        };
4562        let differing_candidates = candidates
4563            .iter()
4564            .filter(|candidate| {
4565                candidate.is_function()
4566                    && candidate.fq_name() == spec.target.fq_name()
4567                    && candidate.signature() == Some(signature)
4568            })
4569            .filter_map(|candidate| {
4570                analyzer
4571                    .signature_metadata(candidate)
4572                    .into_iter()
4573                    .find_map(|metadata| metadata.callable_arity())
4574                    .filter(|arity| Some(*arity) != spec.callable_arity)
4575                    .map(|arity| (candidate, arity))
4576            })
4577            .collect::<Vec<_>>();
4578        if differing_candidates.is_empty() {
4579            return Vec::new();
4580        }
4581        let mut arities = Vec::with_capacity(differing_candidates.len());
4582        // The activation ranges here describe the whole file rather than one
4583        // reference, so there is no reference guard environment to consult.
4584        let reference = CallableReferenceContext {
4585            file,
4586            position: None,
4587        };
4588        for (candidate, candidate_arity) in differing_candidates {
4589            let declaration_activation =
4590                callable_declaration_activation_in_file(analyzer, candidate, &reference);
4591            let Some(declaration_activation) = declaration_activation else {
4592                continue;
4593            };
4594            let activation_byte = if candidate.source() == file {
4595                Some(declaration_activation)
4596            } else {
4597                self.include_activation_for_source(cpp, file, prepared, candidate.source())
4598            };
4599            if let Some(activation_byte) = activation_byte {
4600                arities.push(ActivatedCallableArity {
4601                    activation_byte,
4602                    arity: candidate_arity,
4603                });
4604            }
4605        }
4606        arities
4607    }
4608
4609    fn callable_parameter_macro_arity(
4610        &self,
4611        target: &CodeUnit,
4612        parameter_types: Option<&[String]>,
4613    ) -> Option<CallableArity> {
4614        let parameter_types = parameter_types?;
4615        let [macro_name] = parameter_types else {
4616            return None;
4617        };
4618        if macro_name.is_empty()
4619            || !macro_name
4620                .chars()
4621                .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
4622        {
4623            return None;
4624        }
4625        let cache_key = (target.source().clone(), macro_name.clone());
4626        if let Some(cached) = self
4627            .callable_parameter_macro_arities
4628            .lock()
4629            .expect("C++ callable parameter-macro arity cache poisoned")
4630            .get(&cache_key)
4631            .copied()
4632        {
4633            return cached;
4634        }
4635        let mut visible_files = HashSet::default();
4636        collect_include_closure(
4637            &self.cpp_source(),
4638            self.cpp.include_target_index(),
4639            target.source(),
4640            &mut visible_files,
4641            None,
4642        );
4643        let mut arities = Vec::new();
4644        for visible_file in visible_files {
4645            let cell = self.macro_event_cell(&visible_file);
4646            for event in
4647                cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
4648            {
4649                let MacroEvent::Define { name, binding, .. } = event else {
4650                    continue;
4651                };
4652                if name != macro_name {
4653                    continue;
4654                }
4655                let MacroDefinition::Object { replacement } = &binding.definition else {
4656                    continue;
4657                };
4658                let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
4659                    continue;
4660                };
4661                if !arities.contains(&arity) {
4662                    arities.push(arity);
4663                }
4664            }
4665        }
4666        let resolved = (|| {
4667            let required = arities
4668                .iter()
4669                .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
4670                .min()?;
4671            let total = arities.iter().map(|arity| arity.total()).max()?;
4672            let repeated = arities
4673                .iter()
4674                .any(|arity| arity.accepts(arity.total().saturating_add(1)));
4675            // Preprocessor conditions can leave more than one object-like parameter
4676            // bundle active in the target header's include closure. Preserve their
4677            // conservative callable envelope instead of choosing whichever definition
4678            // happened to be visited first.
4679            Some(CallableArity::new(required, total, repeated))
4680        })();
4681        self.callable_parameter_macro_arities
4682            .lock()
4683            .expect("C++ callable parameter-macro arity cache poisoned")
4684            .insert(cache_key, resolved);
4685        resolved
4686    }
4687
4688    pub fn include_activation_for_source(
4689        &self,
4690        cpp: &dyn CppSource,
4691        file: &ProjectFile,
4692        prepared: &PreparedSyntaxTree,
4693        donor_source: &ProjectFile,
4694    ) -> Option<usize> {
4695        let key = (file.clone(), donor_source.clone());
4696        if let Some(cached) = self
4697            .include_activation_cells
4698            .lock()
4699            .expect("C++ include activation cache poisoned")
4700            .get(&key)
4701            .copied()
4702        {
4703            return cached;
4704        }
4705        #[cfg(any(test, feature = "test-support"))]
4706        self.include_activation_build_count
4707            .fetch_add(1, Ordering::Relaxed);
4708        let language_guard_stable = OnceCell::new();
4709        let activation =
4710            find_include_activation(cpp, self.token, file, prepared, donor_source, &|| {
4711                *language_guard_stable.get_or_init(|| self.include_language_guard_is_stable(file))
4712            });
4713        let mut cells = self
4714            .include_activation_cells
4715            .lock()
4716            .expect("C++ include activation cache poisoned");
4717        *cells.entry(key).or_insert(activation)
4718    }
4719
4720    pub fn conditional_include_projections_for_source(
4721        &self,
4722        file: &ProjectFile,
4723        prepared: &PreparedSyntaxTree,
4724        donor_source: &ProjectFile,
4725    ) -> Arc<[ConditionalIncludeProjection]> {
4726        static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
4727        let cell = self
4728            .conditional_include_projection_cells
4729            .lock()
4730            .expect("C++ conditional include projection cache poisoned")
4731            .entry(file.clone())
4732            .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
4733            .clone();
4734        let index = cell.get_or_build_pool_independent(|| {
4735            #[cfg(any(test, feature = "test-support"))]
4736            self.conditional_include_projection_index_build_count
4737                .fetch_add(1, Ordering::Relaxed);
4738            let language_guard_stable = OnceCell::new();
4739            find_conditional_include_projection_index(
4740                self.cpp,
4741                self.token,
4742                file,
4743                prepared,
4744                &|| {
4745                    *language_guard_stable
4746                        .get_or_init(|| self.include_language_guard_is_stable(file))
4747                },
4748                &|| {
4749                    #[cfg(any(test, feature = "test-support"))]
4750                    self.conditional_include_projection_state_count
4751                        .fetch_add(1, Ordering::Relaxed);
4752                },
4753            )
4754        });
4755        index
4756            .get(donor_source)
4757            .cloned()
4758            .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
4759    }
4760
4761    fn include_language_guard_is_stable(&self, file: &ProjectFile) -> bool {
4762        let mut macro_events = |file: &ProjectFile| {
4763            let cell = self.macro_event_cell(file);
4764            cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice())
4765                .clone()
4766        };
4767        include_language_guard_is_stable_with(
4768            self.cpp,
4769            self.token,
4770            file,
4771            &mut macro_events,
4772            &mut |file: &ProjectFile, prepared: &PreparedSyntaxTree| {
4773                self.structured_include_facts(file, prepared)
4774            },
4775        )
4776    }
4777
4778    #[cfg(any(test, feature = "test-support"))]
4779    pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
4780        (
4781            self.conditional_include_projection_index_build_count
4782                .load(Ordering::Relaxed),
4783            self.conditional_include_projection_state_count
4784                .load(Ordering::Relaxed),
4785        )
4786    }
4787
4788    #[cfg(any(test, feature = "test-support"))]
4789    pub fn conditional_include_target_state_count_for_test(&self) -> usize {
4790        self.conditional_include_target_state_count
4791            .load(Ordering::Relaxed)
4792    }
4793
4794    #[cfg(any(test, feature = "test-support"))]
4795    pub fn include_activation_build_count_for_test(&self) -> usize {
4796        self.include_activation_build_count.load(Ordering::Relaxed)
4797    }
4798
4799    #[cfg(any(test, feature = "test-support"))]
4800    pub fn note_using_donor_activation_for_test(&self) {
4801        self.using_donor_activation_count
4802            .fetch_add(1, Ordering::Relaxed);
4803    }
4804
4805    #[cfg(not(any(test, feature = "test-support")))]
4806    pub fn note_using_donor_activation_for_test(&self) {}
4807
4808    #[cfg(any(test, feature = "test-support"))]
4809    pub fn note_using_namespace_lookup_for_test(&self) {
4810        self.using_namespace_lookup_count
4811            .fetch_add(1, Ordering::Relaxed);
4812    }
4813
4814    #[cfg(not(any(test, feature = "test-support")))]
4815    pub fn note_using_namespace_lookup_for_test(&self) {}
4816
4817    #[cfg(any(test, feature = "test-support"))]
4818    pub fn note_using_name_candidate_inspection_for_test(&self) {
4819        self.using_name_candidate_inspection_count
4820            .fetch_add(1, Ordering::Relaxed);
4821    }
4822
4823    #[cfg(not(any(test, feature = "test-support")))]
4824    pub fn note_using_name_candidate_inspection_for_test(&self) {}
4825
4826    #[cfg(any(test, feature = "test-support"))]
4827    pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
4828        (
4829            self.using_donor_activation_count.load(Ordering::Relaxed),
4830            self.using_namespace_lookup_count.load(Ordering::Relaxed),
4831            self.callable_reference_spec_build_count
4832                .load(Ordering::Relaxed),
4833            self.using_name_candidate_inspection_count
4834                .load(Ordering::Relaxed),
4835        )
4836    }
4837
4838    pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
4839        file == target.source()
4840            || self
4841                .visible_by_file
4842                .get(file)
4843                .is_some_and(|visible| visible.contains(target))
4844    }
4845
4846    /// Whether some declaration of `declaration`'s logical symbol is visible at
4847    /// `reference_byte` in `file`.
4848    ///
4849    /// The question is asked of the *logical* symbol, not of the physical unit:
4850    /// an out-of-line body in a `.cpp` nobody includes is never itself visible,
4851    /// and it does not have to be - what makes the call legal is the header
4852    /// declaration that the reference file does include. Reading that relation
4853    /// through `same_logical_callable` rather than through signature strings is
4854    /// the same #2010 correction the gates make, and it matters here because
4855    /// the body and the declaration are exactly the pair that spells one
4856    /// parameter type two ways.
4857    pub fn declaration_visible_at(
4858        &self,
4859        analyzer: &CppGraphSource<'_>,
4860        file: &ProjectFile,
4861        declaration: &CodeUnit,
4862        reference_byte: usize,
4863    ) -> bool {
4864        let reference_guards = OnceCell::new();
4865        // A prototype must itself be active. Another same-signature prototype
4866        // cannot select its include branch or compilation dialect for it.
4867        if declaration.is_callable() && !callable_target_has_definition_body(analyzer, declaration)
4868        {
4869            return self.physical_declaration_visible_at(
4870                analyzer,
4871                file,
4872                declaration,
4873                reference_byte,
4874                &reference_guards,
4875            );
4876        }
4877        self.visible_identifier_candidates(file, declaration.identifier())
4878            .filter(|candidate| {
4879                // Across dialects, a shared spelling needs the include evidence
4880                // that can connect a C declaration to an extern-C definition.
4881                // Within one dialect retain unresolved link-unit candidates;
4882                // their uncertainty is reported by the navigation consumer.
4883                (if declaration.is_callable()
4884                    && reference_uses_c_semantics(self.cpp, file)
4885                        != reference_uses_c_semantics(self.cpp, declaration.source())
4886                {
4887                    crate::identity::cpp_callable_definitions_share_identity_evidence_with_visibility(
4888                        analyzer,
4889                        self,
4890                        candidate,
4891                        declaration,
4892                        |left, right| {
4893                            crate::identity::cpp_header_body_files_are_related(
4894                                self.cpp, self.token, left, right,
4895                            )
4896                        },
4897                    )
4898                } else {
4899                    self.same_logical_callable(analyzer, candidate, declaration)
4900                })
4901                    || flattened_macro_namespace_declaration_matches(
4902                        analyzer,
4903                        self.cpp,
4904                        file,
4905                        candidate,
4906                        declaration,
4907                        reference_byte,
4908                    )
4909            })
4910            .any(|candidate| {
4911                self.physical_declaration_visible_at(
4912                    analyzer,
4913                    file,
4914                    candidate,
4915                    reference_byte,
4916                    &reference_guards,
4917                )
4918            })
4919    }
4920
4921    /// Candidate guard admission, without claiming that the guards hold.
4922    /// Structured inverse paths use this when visibility proof fails, so an
4923    /// unrelated guard preserves uncertainty while a contradiction is excluded.
4924    pub(crate) fn declaration_guards_compatible_at_reference(
4925        &self,
4926        analyzer: &CppGraphSource<'_>,
4927        file: &ProjectFile,
4928        declaration: &CodeUnit,
4929        reference: Node<'_>,
4930    ) -> bool {
4931        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4932            return false;
4933        };
4934        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4935        declaration_guard_requirements(analyzer, self.cpp, declaration)
4936            .iter()
4937            .any(|(_, required)| {
4938                guards_compatible_at_reference(required, reference_guards.as_ref())
4939            })
4940    }
4941
4942    /// Whether a physical declaration is visible at an exact structured
4943    /// reference node. Inverse C field references need the reference's own
4944    /// preprocessor environment before using the callable activation path:
4945    /// callable activation can establish that a field declaration is
4946    /// nameable, but it must not admit the opposite branch of that field's
4947    /// conditional family.
4948    pub fn declaration_visible_at_reference(
4949        &self,
4950        analyzer: &CppGraphSource<'_>,
4951        file: &ProjectFile,
4952        declaration: &CodeUnit,
4953        reference: Node<'_>,
4954    ) -> bool {
4955        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4956            return false;
4957        };
4958        if !self.declaration_guards_compatible_at_reference(analyzer, file, declaration, reference)
4959        {
4960            return false;
4961        }
4962        if declaration.source() == file
4963            && !analyzer.reference_uses_c_semantics(file)
4964            && (declaration.is_field() || declaration.is_callable())
4965            && type_owner_of(analyzer, declaration).is_some_and(|owner| {
4966                self.indexed_enclosing_owner_scope(analyzer, file, reference)
4967                    .is_some_and(|scope| scope == canonical_cpp_scope_components(&owner))
4968            })
4969        {
4970            // C++ complete-class context makes members visible throughout the
4971            // class. The guard check above still excludes a declaration from
4972            // an incompatible preprocessor branch.
4973            return true;
4974        }
4975        if declaration.is_field() && declaration.source() == file {
4976            let reference_byte = reference.start_byte();
4977            let reference_function =
4978                real_function_definition_ancestor(reference, prepared.source());
4979            let guards = OnceCell::new();
4980            let field_reference = CallableReferenceContext {
4981                file,
4982                position: Some(CallableReferencePosition {
4983                    prepared: prepared.as_ref(),
4984                    byte: reference_byte,
4985                    guards: &guards,
4986                }),
4987            };
4988            let mut has_local_declaration = false;
4989            let mut local_declaration_visible = false;
4990            for declaration in callable_declaration_nodes(analyzer, prepared.as_ref(), declaration)
4991            {
4992                let Some(declaration_function) =
4993                    real_function_definition_ancestor(declaration, prepared.source())
4994                else {
4995                    continue;
4996                };
4997                has_local_declaration = true;
4998                if reference_function.is_some_and(|reference_function| {
4999                    reference_function.start_byte() == declaration_function.start_byte()
5000                        && reference_function.end_byte() == declaration_function.end_byte()
5001                }) && callable_preprocessor_context_is_visible_for_reference(
5002                    declaration,
5003                    prepared.source(),
5004                    &field_reference,
5005                ) && declaration.end_byte() < reference_byte
5006                {
5007                    local_declaration_visible = true;
5008                    break;
5009                }
5010            }
5011            if has_local_declaration {
5012                return local_declaration_visible;
5013            }
5014        }
5015        let guards = OnceCell::new();
5016        self.physical_declaration_visible_at(
5017            analyzer,
5018            file,
5019            declaration,
5020            reference.start_byte(),
5021            &guards,
5022        )
5023    }
5024
5025    /// C forward navigation may bind a call to a later same-file definition.
5026    /// There is no earlier source declaration to activate in that legacy C
5027    /// shape, but the call's preprocessor environment must still imply the
5028    /// definition's requirements. Ordinary C++ and inverse visibility retain
5029    /// the declaration-order rule in [`Self::declaration_visible_at`].
5030    pub fn declaration_visible_for_c_forward_call(
5031        &self,
5032        analyzer: &CppGraphSource<'_>,
5033        file: &ProjectFile,
5034        declaration: &CodeUnit,
5035        reference_byte: usize,
5036    ) -> bool {
5037        if self.declaration_visible_at(analyzer, file, declaration, reference_byte) {
5038            return true;
5039        }
5040        if declaration.source() != file {
5041            return false;
5042        }
5043        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5044            return false;
5045        };
5046        let reference_guards = prepared
5047            .tree()
5048            .root_node()
5049            .descendant_for_byte_range(reference_byte, reference_byte)
5050            .and_then(|node| preprocessor_guard_environment(node, prepared.source()));
5051        declaration_guard_requirements(analyzer, self.cpp, declaration)
5052            .into_iter()
5053            .any(|(_, required)| {
5054                guard_requirements_hold_at_reference(&required, reference_guards.as_ref())
5055            })
5056    }
5057
5058    pub fn callable_arity_at_reference(
5059        &self,
5060        analyzer: &CppGraphSource<'_>,
5061        file: &ProjectFile,
5062        candidate: &CodeUnit,
5063        reference_byte: usize,
5064    ) -> Option<CallableArity> {
5065        let key = (file.clone(), logical_symbol_key(candidate));
5066        let cell = self
5067            .callable_reference_specs
5068            .lock()
5069            .expect("C++ callable reference-spec cache poisoned")
5070            .entry(key)
5071            .or_default()
5072            .clone();
5073        let spec = cell.get_or_init(|| {
5074            let prepared = self.cpp.prepared_syntax(self.token, file)?;
5075            let spec = TargetSpec::from_target(analyzer, candidate)?;
5076            let spec = spec
5077                .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
5078                .into_owned();
5079            #[cfg(any(test, feature = "test-support"))]
5080            self.callable_reference_spec_build_count
5081                .fetch_add(1, Ordering::Relaxed);
5082            Some(spec)
5083        });
5084        spec.as_ref()?.callable_arity_at(reference_byte)
5085    }
5086
5087    fn physical_declaration_visible_at(
5088        &self,
5089        analyzer: &CppGraphSource<'_>,
5090        file: &ProjectFile,
5091        declaration: &CodeUnit,
5092        reference_byte: usize,
5093        reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
5094    ) -> bool {
5095        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5096            return false;
5097        };
5098        let reference = CallableReferenceContext {
5099            file,
5100            position: Some(CallableReferencePosition {
5101                prepared: prepared.as_ref(),
5102                byte: reference_byte,
5103                guards: reference_guards,
5104            }),
5105        };
5106        if declaration.source() == file {
5107            return callable_declaration_activation_in_file(analyzer, declaration, &reference)
5108                .or_else(|| {
5109                    self.exhaustive_guard_family_activation(analyzer, declaration, &reference)
5110                })
5111                .is_some_and(|activation| activation < reference_byte);
5112        }
5113        if self
5114            .exhaustive_guard_family_activation(analyzer, declaration, &reference)
5115            .is_some()
5116        {
5117            // Structured coverage proves this logical declaration exists in
5118            // every arm. The include path still needs its own proof.
5119            return self.foreign_declaration_reachable_at_reference(
5120                file,
5121                prepared.as_ref(),
5122                declaration.source(),
5123                &HashSet::default(),
5124                reference.guards(),
5125                reference_byte,
5126            );
5127        }
5128        if !self.foreign_callable_declaration_is_active(analyzer, declaration, &reference) {
5129            return false;
5130        }
5131        declaration_guard_requirements(analyzer, self.cpp, declaration)
5132            .into_iter()
5133            .any(|(_, declaration_guards)| {
5134                self.foreign_declaration_reachable_at_reference(
5135                    file,
5136                    prepared.as_ref(),
5137                    declaration.source(),
5138                    &declaration_guards,
5139                    reference.guards(),
5140                    reference_byte,
5141                )
5142            })
5143    }
5144
5145    /// Whether the donor's callable facts admit this candidate. Build coverage
5146    /// selects required macro facts; without it, compatibility permits discovery.
5147    /// `physical_declaration_visible_at` separately requires implication and a
5148    /// proven include path before publishing an exact answer (#6902).
5149    fn foreign_callable_declaration_is_active(
5150        &self,
5151        analyzer: &CppGraphSource<'_>,
5152        declaration: &CodeUnit,
5153        reference: &CallableReferenceContext<'_>,
5154    ) -> bool {
5155        let build_decides = !self.compile_context_is_absent(reference.file);
5156        let proven = self.compile_proven_guards(reference.file);
5157        let augmented;
5158        let active = match reference.guards() {
5159            Some(active) if !proven.is_empty() => {
5160                augmented = active.union(&proven).cloned().collect();
5161                Some(&augmented)
5162            }
5163            other => other,
5164        };
5165        analyzer
5166            .declaration_source_occurrences(declaration)
5167            .into_iter()
5168            .flatten()
5169            .any(|occurrence| {
5170                if occurrence.fact.callable_activation.is_none() {
5171                    return false;
5172                }
5173                let Some(mut required) = occurrence
5174                    .fact
5175                    .callable_guards
5176                    .as_ref()
5177                    .and_then(cpp_guard_set_to_runtime)
5178                else {
5179                    return false;
5180                };
5181                if required.iter().any(|guard| match guard {
5182                    PreprocessorGuard::Defined(name) if name == "__cplusplus" => reference.is_c(),
5183                    PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
5184                        !reference.is_c()
5185                    }
5186                    _ => false,
5187                }) {
5188                    return false;
5189                }
5190                required.retain(|guard| {
5191                    !matches!(guard,
5192                    PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
5193                        if name == "__cplusplus")
5194                });
5195                required.is_empty()
5196                    || if build_decides {
5197                        guard_requirements_hold_at_reference(&required, active)
5198                    } else {
5199                        guards_compatible_at_reference(&required, reference.guards())
5200                    }
5201            })
5202    }
5203
5204    pub fn external_type_candidate_visible_at(
5205        &self,
5206        file: &ProjectFile,
5207        candidate: &CodeUnit,
5208        reference_byte: usize,
5209    ) -> bool {
5210        if candidate.source() == file {
5211            return true;
5212        }
5213        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5214            return false;
5215        };
5216        self.visible_identifier_candidates(file, candidate.identifier())
5217            .filter(|peer| same_logical_symbol(candidate, peer))
5218            .any(|peer| {
5219                peer.source() == file
5220                    || self
5221                        .include_activation_for_source(
5222                            self.cpp,
5223                            file,
5224                            prepared.as_ref(),
5225                            peer.source(),
5226                        )
5227                        .is_some_and(|activation| activation <= reference_byte)
5228            })
5229    }
5230
5231    pub fn external_type_declaration_visible_at(
5232        &self,
5233        file: &ProjectFile,
5234        candidate: &CodeUnit,
5235        reference_byte: usize,
5236    ) -> bool {
5237        if candidate.source() == file {
5238            return true;
5239        }
5240        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5241            return false;
5242        };
5243        self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
5244            .is_some_and(|activation| activation <= reference_byte)
5245    }
5246
5247    /// The preprocessor facts the build proves for a reference sited in
5248    /// `file` (#2011).
5249    ///
5250    /// Every `-D` that survives its command's `-D`/`-U` ordering is a positive
5251    /// `Defined` fact, and a fact holds only when every compile configuration
5252    /// that governs the file agrees on it (intersection). The facts are
5253    /// strictly additive to the reference's active guard set: they can prove a
5254    /// required guard, but the guard check itself is never weakened and no
5255    /// implication is ever inferred from source text.
5256    ///
5257    /// A file with its own database entry answers from that entry alone
5258    /// (phase 1). A header takes its context from the translation units whose
5259    /// include closure reaches it, intersected across all of them (phase 2):
5260    /// the header is compiled once per including TU, so a fact holds for a
5261    /// header-sited reference only when every one of those compilations
5262    /// proves it. A reaching TU the database does not cover proves nothing,
5263    /// which empties the intersection. A file nothing covers or reaches has
5264    /// no facts and every check runs on source structure alone.
5265    pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
5266        if let Some(cached) = self
5267            .compile_proven_guard_cells
5268            .lock()
5269            .expect("C++ compile-proven guard cache poisoned")
5270            .get(file)
5271        {
5272            return Arc::clone(cached);
5273        }
5274        let names = match context_fact_names(self.cpp.compile_contexts_for(file)) {
5275            Some(names) => names,
5276            None => {
5277                let mut translation_units = self.cpp.reaching_translation_units(file).into_iter();
5278                let seed = translation_units.next().and_then(|translation_unit| {
5279                    context_fact_names(self.cpp.compile_contexts_for(&translation_unit))
5280                });
5281                match seed {
5282                    None => HashSet::default(),
5283                    Some(mut names) => {
5284                        for translation_unit in translation_units {
5285                            let Some(reached) = context_fact_names(
5286                                self.cpp.compile_contexts_for(&translation_unit),
5287                            ) else {
5288                                names.clear();
5289                                break;
5290                            };
5291                            names.retain(|name| reached.contains(name));
5292                            if names.is_empty() {
5293                                break;
5294                            }
5295                        }
5296                        names
5297                    }
5298                }
5299            }
5300        };
5301        let proven = Arc::new(
5302            names
5303                .into_iter()
5304                .map(PreprocessorGuard::Defined)
5305                .collect::<HashSet<_>>(),
5306        );
5307        self.compile_proven_guard_cells
5308            .lock()
5309            .expect("C++ compile-proven guard cache poisoned")
5310            .insert(file.clone(), Arc::clone(&proven));
5311        proven
5312    }
5313
5314    /// The rule a conditional `#include` path's guards face on the way to a
5315    /// reference in `file`.
5316    ///
5317    /// This is the include-edge analogue of the choice
5318    /// [`Self::foreign_callable_declaration_is_active`] makes for a foreign
5319    /// declaration's own guards (#2988), and it is decided by the same thing:
5320    /// what settles the reference's configuration. Memoized per file because
5321    /// the answer depends on nothing else, and the callers ask it once per
5322    /// candidate.
5323    fn include_path_admission(&self, file: &ProjectFile) -> IncludePathAdmission {
5324        if let Some(cached) = self
5325            .include_path_admission_cells
5326            .lock()
5327            .expect("C++ include-path admission cache poisoned")
5328            .get(file)
5329            .copied()
5330        {
5331            return cached;
5332        }
5333        let admission = if self.compile_context_is_absent(file) {
5334            IncludePathAdmission::Compatible
5335        } else {
5336            IncludePathAdmission::Proven
5337        };
5338        self.include_path_admission_cells
5339            .lock()
5340            .expect("C++ include-path admission cache poisoned")
5341            .insert(file.clone(), admission);
5342        admission
5343    }
5344
5345    /// Whether no compile data covers the compilations of `file`: it has no
5346    /// database entry of its own, and either nothing reaches it or some
5347    /// translation unit that reaches it has no entry. This is the state a
5348    /// regenerated `compile_commands.json` could decide; data that is present
5349    /// for every governing compilation but does not prove a guard is a
5350    /// decided conservative miss, not this state.
5351    fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
5352        if !self.cpp.compile_contexts_for(file).is_empty() {
5353            return false;
5354        }
5355        let translation_units = self.cpp.reaching_translation_units(file);
5356        translation_units.is_empty()
5357            || translation_units
5358                .iter()
5359                .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
5360    }
5361
5362    /// Whether a lookup miss for `identifier` in `file` is explainable by
5363    /// missing compile context (#2011): some same-name declaration is
5364    /// reachable through a conditional include whose required guards neither
5365    /// contradict the reference's active guards nor follow from them, and the
5366    /// translation unit has no compile-commands entry that could decide the
5367    /// question. Callers surface this as an explicit "requires compile
5368    /// context" incompleteness instead of an indistinguishable miss.
5369    ///
5370    /// A structurally disproven declaration (contradicting guards) and a TU
5371    /// whose compile context exists but does not prove the guard both answer
5372    /// `false`: those misses are decided, not incomplete.
5373    pub fn miss_requires_compile_context(
5374        &self,
5375        file: &ProjectFile,
5376        identifier: &str,
5377        reference: Node<'_>,
5378    ) -> bool {
5379        if !self.compile_context_is_absent(file) {
5380            return false;
5381        }
5382        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5383            return false;
5384        };
5385        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5386        let reference_byte = reference.start_byte();
5387        let mut sources = self
5388            .visible_identifier_candidates(file, identifier)
5389            .map(CodeUnit::source)
5390            .filter(|source| *source != file)
5391            .collect::<Vec<_>>();
5392        sources.sort();
5393        sources.dedup();
5394        sources.into_iter().any(|declaration_source| {
5395            self.conditional_include_projections_for_source(
5396                file,
5397                prepared.as_ref(),
5398                declaration_source,
5399            )
5400            .iter()
5401            .any(|projection| {
5402                projection.activation_byte <= reference_byte
5403                    && !guard_requirements_hold_at_reference(
5404                        &projection.required_guards,
5405                        reference_guards.as_ref(),
5406                    )
5407                    && guards_compatible_at_reference(
5408                        &projection.required_guards,
5409                        reference_guards.as_ref(),
5410                    )
5411            })
5412        })
5413    }
5414
5415    /// The guards that hold at a reference in `file`: its own source guards,
5416    /// the defines the build proves (#2011), and the translation unit's stable
5417    /// `__cplusplus` state. A reference whose own environment is unknown stays
5418    /// unknown -- the facts extend an environment, they never invent one. Every
5419    /// proof and contradiction check against a reference uses this set, so a C
5420    /// translation unit never takes an `#ifdef __cplusplus` arm (#7072).
5421    fn effective_reference_guards(
5422        &self,
5423        file: &ProjectFile,
5424        reference_guards: Option<&HashSet<PreprocessorGuard>>,
5425    ) -> Option<HashSet<PreprocessorGuard>> {
5426        let mut effective = reference_guards.cloned()?;
5427        effective.extend(self.compile_proven_guards(file).iter().cloned());
5428        if self.include_language_guard_is_stable(file) {
5429            effective.insert(if reference_uses_c_semantics(self.cpp, file) {
5430                PreprocessorGuard::Undefined("__cplusplus".to_string())
5431            } else {
5432                PreprocessorGuard::Defined("__cplusplus".to_string())
5433            });
5434        }
5435        Some(effective)
5436    }
5437
5438    /// Decide whether a declaration that lives in another file reaches a
5439    /// reference in `file`.
5440    ///
5441    /// Candidate discovery admits non-contradicting guards. Proven visibility
5442    /// additionally requires the declaration's guards to follow from the
5443    /// effective reference context; independent opaque conditions imply nothing
5444    /// about one another (#6902).
5445    fn foreign_declaration_reachable_at_reference(
5446        &self,
5447        file: &ProjectFile,
5448        prepared: &PreparedSyntaxTree,
5449        declaration_source: &ProjectFile,
5450        declaration_guards: &HashSet<PreprocessorGuard>,
5451        reference_guards: Option<&HashSet<PreprocessorGuard>>,
5452        reference_byte: usize,
5453    ) -> bool {
5454        let effective = self.effective_reference_guards(file, reference_guards);
5455        let reference_guards = effective.as_ref();
5456        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5457            eprintln!(
5458                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=foreign_guard_compatibility declaration_source={} declaration_guards={declaration_guards:?} reference_guards={reference_guards:?}",
5459                declaration_source.rel_path().display(),
5460            );
5461        }
5462        if !guard_requirements_hold_at_reference(declaration_guards, reference_guards) {
5463            return false;
5464        }
5465        if self
5466            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5467            .is_some_and(|activation| activation <= reference_byte)
5468        {
5469            return true;
5470        }
5471        let projections =
5472            self.conditional_include_projections_for_source(file, prepared, declaration_source);
5473        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5474            eprintln!(
5475                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=filtered_projection source={} declaration_guards={} proven_guards={} projections={}",
5476                declaration_source.rel_path().display(),
5477                declaration_guards.len(),
5478                self.compile_proven_guards(file).len(),
5479                projections.len(),
5480            );
5481        }
5482        let admission = IncludePathAdmission::Proven;
5483        projections.iter().any(|projection| {
5484            projection.activation_byte <= reference_byte
5485                && admission.admits(
5486                    &projection.required_guards,
5487                    &projection.partial_guards,
5488                    reference_guards,
5489                )
5490                && self.preprocessor_guards_stable_between(
5491                    file,
5492                    projection.activation_byte,
5493                    reference_byte,
5494                    &projection.required_guards,
5495                )
5496        })
5497    }
5498
5499    fn foreign_declaration_may_be_reachable_from_raw_guards(
5500        &self,
5501        file: &ProjectFile,
5502        prepared: &PreparedSyntaxTree,
5503        declaration_source: &ProjectFile,
5504        declaration_guards: &HashSet<PreprocessorGuard>,
5505        reference_guards: Option<&HashSet<PreprocessorGuard>>,
5506        reference_byte: usize,
5507    ) -> bool {
5508        let proven = self.compile_proven_guards(file);
5509        let augmented;
5510        let reference_guards = match reference_guards {
5511            Some(active) if !proven.is_empty() => {
5512                augmented = active.union(&proven).cloned().collect();
5513                Some(&augmented)
5514            }
5515            other => other,
5516        };
5517        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5518            return false;
5519        }
5520        if self
5521            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5522            .is_some_and(|activation| activation <= reference_byte)
5523        {
5524            return true;
5525        }
5526        let language_guard_stable = OnceCell::new();
5527        let reachable = find_conditional_include_projection_for_source(
5528            self.cpp,
5529            self.token,
5530            file,
5531            prepared,
5532            declaration_source,
5533            self.include_path_admission(file),
5534            reference_guards,
5535            reference_byte,
5536            &|| *language_guard_stable.get_or_init(|| self.include_language_guard_is_stable(file)),
5537            &|| {
5538                #[cfg(any(test, feature = "test-support"))]
5539                self.conditional_include_target_state_count
5540                    .fetch_add(1, Ordering::Relaxed);
5541            },
5542        );
5543        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5544            eprintln!(
5545                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_projection source={} declaration_guards={} proven_guards={} raw_guards={} reachable={reachable}",
5546                declaration_source.rel_path().display(),
5547                declaration_guards.len(),
5548                proven.len(),
5549                reference_guards.map_or(0, HashSet::len),
5550            );
5551        }
5552        reachable
5553    }
5554
5555    fn foreign_declaration_reachable_from_compile_proven_guards(
5556        &self,
5557        file: &ProjectFile,
5558        prepared: &PreparedSyntaxTree,
5559        declaration_source: &ProjectFile,
5560        declaration_guards: &HashSet<PreprocessorGuard>,
5561        reference_byte: usize,
5562    ) -> bool {
5563        let proven = self.compile_proven_guards(file);
5564        if proven.is_empty()
5565            || !guard_requirements_hold_at_reference(declaration_guards, Some(proven.as_ref()))
5566        {
5567            return false;
5568        }
5569        let projections =
5570            self.conditional_include_projections_for_source(file, prepared, declaration_source);
5571        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5572            eprintln!(
5573                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=compile_proven_projection source={} declaration_guards={} proven_guards={} projections={}",
5574                declaration_source.rel_path().display(),
5575                declaration_guards.len(),
5576                proven.len(),
5577                projections.len(),
5578            );
5579        }
5580        projections.iter().any(|projection| {
5581            projection.activation_byte <= reference_byte
5582                    && guard_requirements_hold_at_reference(
5583                        &projection.required_guards,
5584                        Some(proven.as_ref()),
5585                    )
5586                    // Build facts hold at translation-unit entry. A source
5587                    // `#undef` or an earlier include may invalidate one before
5588                    // this conditional include is reached; mutations after the
5589                    // include cannot revoke declarations it already supplied.
5590                    && self.preprocessor_guards_stable_between(
5591                        file,
5592                        0,
5593                        projection.activation_byte,
5594                        &projection.required_guards,
5595                    )
5596        })
5597    }
5598
5599    /// Whether one declaration site of `site_source` introduces its name before
5600    /// `reference_byte` of `file` without consulting the reference's own guard
5601    /// environment: an unguarded site earlier in the file or reached through
5602    /// an unconditional include, or a foreign site the build's proven defines
5603    /// reach.
5604    fn declaration_site_visible_without_reference_environment(
5605        &self,
5606        file: &ProjectFile,
5607        prepared: &PreparedSyntaxTree,
5608        site_source: &ProjectFile,
5609        site_byte: usize,
5610        site_guards: &HashSet<PreprocessorGuard>,
5611        reference_byte: usize,
5612    ) -> bool {
5613        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
5614        if site_source == file {
5615            let visible = site_byte < reference_byte && site_guards.is_empty();
5616            if report_stats {
5617                eprintln!(
5618                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=true visible={visible}",
5619                    site_source.rel_path().display(),
5620                    site_guards.len(),
5621                );
5622            }
5623            return visible;
5624        }
5625        let direct = site_guards.is_empty()
5626            && self
5627                .include_activation_for_source(self.cpp, file, prepared, site_source)
5628                .is_some_and(|activation| activation <= reference_byte);
5629        let compile_proven = !direct
5630            && self.foreign_declaration_reachable_from_compile_proven_guards(
5631                file,
5632                prepared,
5633                site_source,
5634                site_guards,
5635                reference_byte,
5636            );
5637        if report_stats {
5638            eprintln!(
5639                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=false direct={direct} compile_proven={compile_proven}",
5640                site_source.rel_path().display(),
5641                site_guards.len(),
5642            );
5643        }
5644        direct || compile_proven
5645    }
5646
5647    /// Whether one declaration site of `site_source` is in effect at
5648    /// `reference_byte` of `file` under the reference's guard environment:
5649    /// `raw_reference_guards` as written, and `reference_guards_at_site` after
5650    /// the macro events before the reference.
5651    #[allow(clippy::too_many_arguments)]
5652    fn declaration_site_visible_at_reference<'g>(
5653        &self,
5654        file: &ProjectFile,
5655        prepared: &PreparedSyntaxTree,
5656        site_source: &ProjectFile,
5657        site: &DeclarationGuardSite,
5658        reference_byte: usize,
5659        raw_reference_guards: Option<&HashSet<PreprocessorGuard>>,
5660        reference_guards_at_site: impl Fn() -> Option<&'g HashSet<PreprocessorGuard>>,
5661    ) -> bool {
5662        if site_source == file {
5663            if site.byte >= reference_byte {
5664                return false;
5665            }
5666            // The reference's own conditional context must either prove the
5667            // declaration's requirements, or follow a completed family that
5668            // encloses the declaration (#3297); either way it must not
5669            // contradict them.
5670            let active = |guards: Option<&HashSet<PreprocessorGuard>>| {
5671                guard_requirements_hold_at_reference(&site.guards, guards)
5672                    || (site.completed_family_before_reference
5673                        && guards_compatible_at_reference(&site.guards, guards))
5674            };
5675            if !active(raw_reference_guards) {
5676                return false;
5677            }
5678            return active(reference_guards_at_site())
5679                && self.preprocessor_guards_stable_between(
5680                    file,
5681                    site.byte,
5682                    reference_byte,
5683                    &site.guards,
5684                );
5685        }
5686        let raw_feasible = self.foreign_declaration_may_be_reachable_from_raw_guards(
5687            file,
5688            prepared,
5689            site_source,
5690            &site.guards,
5691            raw_reference_guards,
5692            reference_byte,
5693        );
5694        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5695            eprintln!(
5696                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_feasibility source={} declaration_guards={} feasible={raw_feasible}",
5697                site_source.rel_path().display(),
5698                site.guards.len(),
5699            );
5700        }
5701        raw_feasible
5702            && self.foreign_declaration_reachable_at_reference(
5703                file,
5704                prepared,
5705                site_source,
5706                &site.guards,
5707                reference_guards_at_site(),
5708                reference_byte,
5709            )
5710    }
5711
5712    /// Whether a scope-level binding written at `site_byte` of `site_source`
5713    /// under `site_guards` is in effect at `reference`, by the same site rule
5714    /// [`Self::external_type_candidate_visible_in_context`] applies to each
5715    /// declaration range.
5716    pub fn binding_site_visible_at_reference(
5717        &self,
5718        file: &ProjectFile,
5719        site_source: &ProjectFile,
5720        site_byte: usize,
5721        site_guards: &HashSet<PreprocessorGuard>,
5722        reference: Node<'_>,
5723    ) -> bool {
5724        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5725            return false;
5726        };
5727        if self.declaration_site_visible_without_reference_environment(
5728            file,
5729            prepared.as_ref(),
5730            site_source,
5731            site_byte,
5732            site_guards,
5733            reference.start_byte(),
5734        ) {
5735            return true;
5736        }
5737        let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
5738        let reference_guards = OnceCell::new();
5739        self.declaration_site_visible_at_reference(
5740            file,
5741            prepared.as_ref(),
5742            site_source,
5743            &DeclarationGuardSite {
5744                byte: site_byte,
5745                guards: site_guards.clone(),
5746                completed_family_before_reference: false,
5747            },
5748            reference.start_byte(),
5749            raw_reference_guards.as_ref(),
5750            || {
5751                reference_guards
5752                    .get_or_init(|| {
5753                        let environment = self.macro_environment(file, reference.start_byte());
5754                        raw_reference_guards
5755                            .clone()
5756                            .filter(|guards| environment.guard_requirements_may_hold(guards))
5757                    })
5758                    .as_ref()
5759            },
5760        )
5761    }
5762
5763    pub fn external_type_candidate_visible_in_context(
5764        &self,
5765        analyzer: &CppGraphSource<'_>,
5766        file: &ProjectFile,
5767        candidate: &CodeUnit,
5768        reference: Node<'_>,
5769    ) -> bool {
5770        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
5771        if report_stats {
5772            eprintln!(
5773                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=started fqn={} candidate_source={} reference_file={} reference_byte={}",
5774                candidate.fq_name(),
5775                candidate.source().rel_path().display(),
5776                file.rel_path().display(),
5777                reference.start_byte(),
5778            );
5779        }
5780        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5781            return false;
5782        };
5783        let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
5784        let reference_guards = OnceCell::new();
5785        let reference_guards_at_site = || {
5786            reference_guards.get_or_init(|| {
5787                let started = Instant::now();
5788                if report_stats {
5789                    eprintln!(
5790                        "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=started file={} reference_byte={} raw_guards={}",
5791                        file.rel_path().display(),
5792                        reference.start_byte(),
5793                        raw_reference_guards.as_ref().map_or(0, HashSet::len),
5794                    );
5795                }
5796                let macro_environment = self.macro_environment(file, reference.start_byte());
5797                let filtered = raw_reference_guards
5798                    .clone()
5799                    .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
5800                if report_stats {
5801                    eprintln!(
5802                        "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=completed retained={} elapsed_ms={}",
5803                        filtered.is_some(),
5804                        started.elapsed().as_millis(),
5805                    );
5806                }
5807                filtered
5808            })
5809        };
5810
5811        let peers = self
5812            .visible_identifier_candidates(file, candidate.identifier())
5813            .filter(|peer| same_logical_symbol(candidate, peer))
5814            .collect::<Vec<_>>();
5815        if report_stats {
5816            let peer_sources = peers
5817                .iter()
5818                .map(|peer| peer.source().rel_path().display().to_string())
5819                .collect::<Vec<_>>();
5820            eprintln!(
5821                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=peers fqn={} sources={peer_sources:?}",
5822                candidate.fq_name(),
5823            );
5824        }
5825        let directly_visible_without_reference_environment = peers.iter().any(|peer| {
5826            declaration_guard_requirements(analyzer, self.cpp, peer)
5827                .into_iter()
5828                .any(|(declaration_byte, declaration_guards)| {
5829                    self.declaration_site_visible_without_reference_environment(
5830                        file,
5831                        prepared.as_ref(),
5832                        peer.source(),
5833                        declaration_byte,
5834                        &declaration_guards,
5835                        reference.start_byte(),
5836                    )
5837                })
5838        });
5839        if directly_visible_without_reference_environment {
5840            if report_stats {
5841                eprintln!(
5842                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=direct_or_compile_proven fqn={}",
5843                    candidate.fq_name(),
5844                );
5845            }
5846            return true;
5847        }
5848        let directly_visible = peers.iter().any(|peer| {
5849            declaration_guard_sites(analyzer, self.cpp, peer, reference.start_byte())
5850                .into_iter()
5851                .any(|site| {
5852                    self.declaration_site_visible_at_reference(
5853                        file,
5854                        prepared.as_ref(),
5855                        peer.source(),
5856                        &site,
5857                        reference.start_byte(),
5858                        raw_reference_guards.as_ref(),
5859                        || reference_guards_at_site().as_ref(),
5860                    )
5861                })
5862        });
5863        if directly_visible {
5864            if report_stats {
5865                eprintln!(
5866                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=filtered_reference fqn={}",
5867                    candidate.fq_name(),
5868                );
5869            }
5870            return true;
5871        }
5872        let complementary = self
5873            .visible_identifier_candidates(file, candidate.identifier())
5874            .filter(|peer| {
5875                peer.kind() == candidate.kind()
5876                    && peer.fq_name() == candidate.fq_name()
5877                    && peer.source() == candidate.source()
5878            })
5879            .collect::<Vec<_>>();
5880        // A completed #if/#else family declares the shared source-level name
5881        // before this reference. A later macro mutation cannot revoke that
5882        // declaration. The family gate below rejects declarations split across
5883        // separate conditional blocks, where mutation can change coverage.
5884        let complementary_family =
5885            self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate);
5886        let raw_candidate_branch_compatible = complementary_family
5887            && raw_reference_guards.as_ref().is_some_and(|active| {
5888                declaration_guard_requirements(analyzer, self.cpp, candidate)
5889                    .iter()
5890                    .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5891            });
5892        if report_stats {
5893            eprintln!(
5894                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=complementary fqn={} candidates={} family={} raw_compatible={}",
5895                candidate.fq_name(),
5896                complementary.len(),
5897                complementary_family,
5898                raw_candidate_branch_compatible,
5899            );
5900        }
5901        let candidate_branch_compatible = raw_candidate_branch_compatible
5902            && reference_guards_at_site().as_ref().is_some_and(|active| {
5903                declaration_guard_requirements(analyzer, self.cpp, candidate)
5904                    .iter()
5905                    .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5906            });
5907        let complementary_visible = candidate_branch_compatible
5908            && if candidate.source() == file {
5909                declaration_guard_requirements(analyzer, self.cpp, candidate)
5910                    .iter()
5911                    .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
5912            } else {
5913                self.include_activation_for_source(
5914                    self.cpp,
5915                    file,
5916                    prepared.as_ref(),
5917                    candidate.source(),
5918                )
5919                .is_some_and(|activation| activation <= reference.start_byte())
5920            };
5921        if report_stats {
5922            eprintln!(
5923                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome={} fqn={}",
5924                if complementary_visible {
5925                    "complementary"
5926                } else {
5927                    "missing"
5928                },
5929                candidate.fq_name(),
5930            );
5931        }
5932        complementary_visible
5933    }
5934
5935    pub fn is_exhaustive_same_fqn_type_declaration_family(
5936        &self,
5937        analyzer: &CppGraphSource<'_>,
5938        file: &ProjectFile,
5939        candidate: &CodeUnit,
5940    ) -> bool {
5941        let candidates = self
5942            .visible_identifier_candidates(file, candidate.identifier())
5943            .filter(|peer| {
5944                peer.kind() == candidate.kind()
5945                    && peer.fq_name() == candidate.fq_name()
5946                    && peer.source() == candidate.source()
5947            })
5948            .collect::<Vec<_>>();
5949        self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
5950    }
5951
5952    /// Prove a nested type alias used as a dependent member-pointer owner when
5953    /// its owning class has mutually-exclusive declarations.  A common C++11
5954    /// compatibility shape provides the owning class in one preprocessor
5955    /// branch and aliases it to a standard-library type in the other branch;
5956    /// the nested fallback alias is therefore not itself active in every
5957    /// branch even though the qualified owner API is.
5958    ///
5959    /// This is deliberately narrower than ordinary type visibility.  The
5960    /// caller has already recovered a member-pointer owner path from the CST;
5961    /// this helper additionally requires the target's structured parent to
5962    /// match that path, physical source visibility, and exact preprocessor
5963    /// guard agreement with the parent declaration.  Only then may the
5964    /// parent's direct/complementary same-FQN visibility stand in for the
5965    /// nested terminal's active-branch check.
5966    pub fn dependent_member_pointer_alias_visible_in_context(
5967        &self,
5968        analyzer: &CppGraphSource<'_>,
5969        file: &ProjectFile,
5970        candidate: &CodeUnit,
5971        owner_components: &[String],
5972        reference: Node<'_>,
5973    ) -> bool {
5974        if !analyzer
5975            .type_alias_provider()
5976            .is_some_and(|provider| provider.is_type_alias(candidate))
5977        {
5978            return false;
5979        }
5980        let Some((terminal, owner_prefix)) = owner_components.split_last() else {
5981            return false;
5982        };
5983        if terminal != candidate.identifier()
5984            || canonical_cpp_scope_components(candidate) != owner_components
5985        {
5986            return false;
5987        }
5988        let Some(expected_parent_fq_name) =
5989            brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
5990        else {
5991            return false;
5992        };
5993        let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
5994            return false;
5995        };
5996        if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
5997            || parent_anchor.source() != candidate.source()
5998            || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
5999        {
6000            return false;
6001        }
6002
6003        // The ordinary path already handles unguarded aliases (and preserves
6004        // same-file declaration ordering).  This fallback is only for a
6005        // physically visible declaration whose guard is the owning branch's
6006        // guard, so reject a same-file declaration that appears after the
6007        // reference before considering guard compatibility.
6008        if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
6009            || candidate.source() == file
6010                && !analyzer
6011                    .ranges(candidate)
6012                    .iter()
6013                    .any(|range| range.start_byte < reference.start_byte())
6014        {
6015            return false;
6016        }
6017
6018        let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
6019        if candidate_guards.is_empty() {
6020            return false;
6021        }
6022        let same_guard_sets =
6023            |left: &[(usize, HashSet<PreprocessorGuard>)],
6024             right: &[(usize, HashSet<PreprocessorGuard>)]| {
6025                left.iter().all(|(_, left_guards)| {
6026                    right
6027                        .iter()
6028                        .any(|(_, right_guards)| left_guards == right_guards)
6029                })
6030            };
6031        let parent_candidates = self
6032            .visible_identifier_candidates(file, parent_anchor.identifier())
6033            .filter(|peer| {
6034                peer.kind() == parent_anchor.kind()
6035                    && peer.fq_name() == expected_parent_fq_name.as_str()
6036                    && peer.source() == parent_anchor.source()
6037                    && canonical_cpp_scope_components(peer) == owner_prefix
6038            })
6039            .filter_map(|peer| {
6040                let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
6041                (candidate_guards.len() == parent_guards.len()
6042                    && same_guard_sets(&candidate_guards, &parent_guards)
6043                    && same_guard_sets(&parent_guards, &candidate_guards))
6044                .then(|| (peer.clone(), parent_guards))
6045            })
6046            .collect::<Vec<_>>();
6047        let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
6048            return false;
6049        };
6050
6051        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6052            return false;
6053        };
6054        let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
6055        else {
6056            return false;
6057        };
6058        // An external header selects its declaration branch before the
6059        // reference file is parsed. Require compatible reference guards, but
6060        // do not test the header's guard expression for stability in the
6061        // reference file. Same-file aliases still require that stability.
6062        if !candidate_guards.iter().any(|(_, target_guards)| {
6063            guards_compatible_at_reference(target_guards, Some(&reference_guards))
6064                && (candidate.source() != file
6065                    || self.preprocessor_guards_stable_between(
6066                        file,
6067                        0,
6068                        reference.start_byte(),
6069                        target_guards,
6070                    ))
6071        }) {
6072            return false;
6073        }
6074
6075        self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
6076    }
6077
6078    /// Check a type candidate's preprocessor/import context without imposing
6079    /// ordinary declaration-before-reference ordering for same-file peers.
6080    ///
6081    /// C++ class scope makes member names visible throughout the complete
6082    /// class, including a trailing return type that appears before the member
6083    /// alias declaration in source order. Callers must first prove that the
6084    /// reference is inside the candidate's indexed class owner; this helper
6085    /// only relaxes the byte-order predicate while retaining guard and include
6086    /// activation checks.
6087    pub fn external_type_candidate_guard_compatible_in_context(
6088        &self,
6089        analyzer: &CppGraphSource<'_>,
6090        file: &ProjectFile,
6091        candidate: &CodeUnit,
6092        reference: Node<'_>,
6093    ) -> bool {
6094        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6095            return false;
6096        };
6097        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
6098
6099        self.visible_identifier_candidates(file, candidate.identifier())
6100            .filter(|peer| same_logical_symbol(candidate, peer))
6101            .any(|peer| {
6102                declaration_guard_requirements(analyzer, self.cpp, peer)
6103                    .into_iter()
6104                    .any(|(declaration_byte, declaration_guards)| {
6105                        if peer.source() == file {
6106                            let (start, end) = if declaration_byte <= reference.start_byte() {
6107                                (declaration_byte, reference.start_byte())
6108                            } else {
6109                                (reference.start_byte(), declaration_byte)
6110                            };
6111                            return guard_requirements_hold_at_reference(
6112                                &declaration_guards,
6113                                reference_guards.as_ref(),
6114                            ) && self.preprocessor_guards_stable_between(
6115                                file,
6116                                start,
6117                                end,
6118                                &declaration_guards,
6119                            );
6120                        }
6121                        self.foreign_declaration_reachable_at_reference(
6122                            file,
6123                            prepared.as_ref(),
6124                            peer.source(),
6125                            &declaration_guards,
6126                            reference_guards.as_ref(),
6127                            reference.start_byte(),
6128                        )
6129                    })
6130            })
6131    }
6132
6133    /// Whether a same-file callable declaration is nameable from `reference`
6134    /// after deliberately relaxing declaration-before-reference ordering.
6135    ///
6136    /// Ordinary lookup still requires an earlier declaration. Definition
6137    /// navigation for incomplete C translation units may recover a later
6138    /// definition, but only when it is at file scope and its preprocessor
6139    /// requirements hold at the call (#2404).
6140    pub fn same_file_callable_guard_compatible_ignoring_order(
6141        &self,
6142        analyzer: &CppGraphSource<'_>,
6143        file: &ProjectFile,
6144        candidate: &CodeUnit,
6145        reference: Node<'_>,
6146    ) -> bool {
6147        if candidate.source() != file || !candidate.is_callable() {
6148            return false;
6149        }
6150        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6151            return false;
6152        };
6153        let guards = OnceCell::new();
6154        let context = CallableReferenceContext {
6155            file,
6156            position: Some(CallableReferencePosition {
6157                prepared: prepared.as_ref(),
6158                byte: reference.start_byte(),
6159                guards: &guards,
6160            }),
6161        };
6162        analyzer
6163            .declaration_source_occurrences(candidate)
6164            .into_iter()
6165            .flatten()
6166            .filter_map(|occurrence| {
6167                let guards = cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
6168                Some(callable_guard_requirements_are_visible(
6169                    &guards,
6170                    occurrence.fact.callable_guard_completion_byte,
6171                    &context,
6172                ))
6173            })
6174            .any(|visible| visible)
6175    }
6176
6177    pub fn type_candidate_may_be_visible_before_reference(
6178        &self,
6179        analyzer: &CppGraphSource<'_>,
6180        file: &ProjectFile,
6181        candidate: &CodeUnit,
6182        reference_byte: usize,
6183    ) -> bool {
6184        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6185            return false;
6186        };
6187        let root = prepared.tree().root_node();
6188        let end_byte = reference_byte
6189            .saturating_add(1)
6190            .min(prepared.source().len());
6191        let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
6192            return false;
6193        };
6194        self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
6195    }
6196
6197    pub fn preprocessor_guards_stable_between(
6198        &self,
6199        file: &ProjectFile,
6200        start_byte: usize,
6201        end_byte: usize,
6202        guards: &HashSet<PreprocessorGuard>,
6203    ) -> bool {
6204        if guards.is_empty() || start_byte >= end_byte {
6205            return true;
6206        }
6207        let cell = self.macro_event_cell(file);
6208        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
6209        let mut visited = HashSet::from_iter([file.clone()]);
6210        !events.iter().any(|event| {
6211            event.byte() >= start_byte
6212                && event.byte() < end_byte
6213                && self.macro_event_may_mutate_guards(event, guards, &mut visited)
6214        })
6215    }
6216
6217    fn macro_event_may_mutate_guards(
6218        &self,
6219        event: &MacroEvent,
6220        guards: &HashSet<PreprocessorGuard>,
6221        visited: &mut HashSet<ProjectFile>,
6222    ) -> bool {
6223        match event {
6224            MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
6225                guards.iter().any(|guard| guard.may_depend_on_macro(name))
6226            }
6227            MacroEvent::Include { targets, .. } => {
6228                targets.is_empty()
6229                    || targets
6230                        .iter()
6231                        .any(|target| self.source_may_mutate_guards(target, guards, visited))
6232            }
6233            MacroEvent::Invalidate { .. } => true,
6234        }
6235    }
6236
6237    fn source_may_mutate_guards(
6238        &self,
6239        file: &ProjectFile,
6240        guards: &HashSet<PreprocessorGuard>,
6241        visited: &mut HashSet<ProjectFile>,
6242    ) -> bool {
6243        if !visited.insert(file.clone()) {
6244            return false;
6245        }
6246        let cell = self.macro_event_cell(file);
6247        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
6248        events
6249            .iter()
6250            .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
6251    }
6252
6253    pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
6254        let normalized = normalize_reference_name(raw_name)?;
6255        self.type_candidates(file, &normalized)
6256            .into_iter()
6257            .next()
6258            .cloned()
6259    }
6260
6261    /// Mirror forward navigation's visible-name fallback for a bare parameter
6262    /// type after lexical owner and inheritance lookup is exhausted.
6263    ///
6264    /// Generated or otherwise unindexed base classes can hide the alias that
6265    /// makes a parameter type valid C++. Accept the fallback only when every
6266    /// include-visible class or alias with that spelling canonicalizes to one
6267    /// logical type. A shadowing local type resolves lexically before this
6268    /// path, while distinct visible types keep the result ambiguous.
6269    pub fn unique_visible_parameter_type_fallback(
6270        &self,
6271        analyzer: &CppGraphSource<'_>,
6272        file: &ProjectFile,
6273        node: Node<'_>,
6274        source: &str,
6275    ) -> Option<CodeUnit> {
6276        if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
6277            return None;
6278        }
6279        let name = node_text(node, source);
6280        let candidates = self
6281            .visible_identifier_candidates(file, name)
6282            .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
6283            .filter(|candidate| {
6284                self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
6285            })
6286            .collect::<Vec<_>>();
6287        self.unique_canonical_type_candidate(analyzer, file, &candidates)
6288    }
6289
6290    pub fn resolve_type_node_result(
6291        &self,
6292        file: &ProjectFile,
6293        node: Node<'_>,
6294        source: &str,
6295    ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
6296        let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
6297            return Ok(None);
6298        };
6299        let Some(arguments) = cpp_template_reference_arguments(node, source) else {
6300            return Ok(Some(primary));
6301        };
6302        self.resolve_template_arguments(file, primary, &arguments)
6303            .map(Some)
6304    }
6305
6306    pub fn resolve_type_node_primary(
6307        &self,
6308        file: &ProjectFile,
6309        node: Node<'_>,
6310        source: &str,
6311    ) -> Option<CodeUnit> {
6312        let components = cpp_type_name_components(node, source)?;
6313        self.resolve_type(file, &components.join("::"))
6314    }
6315
6316    pub fn resolve_template_arguments(
6317        &self,
6318        file: &ProjectFile,
6319        primary: CodeUnit,
6320        arguments: &[CppTemplateExpression],
6321    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6322        self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
6323    }
6324
6325    fn resolve_template_arguments_inner(
6326        &self,
6327        file: &ProjectFile,
6328        primary: CodeUnit,
6329        arguments: &[CppTemplateExpression],
6330        seen_aliases: &mut HashSet<CodeUnit>,
6331    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6332        if let Some(metadata) = self.cpp_template_metadata.get(&primary)
6333            && let Some(alias_target) = &metadata.alias_target
6334        {
6335            if !seen_aliases.insert(primary.clone()) {
6336                return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
6337            }
6338            let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
6339                .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6340            let target_name = alias_target.components.join("::");
6341            let target_primary = if alias_target.global {
6342                unique_logical_type_candidate(self.type_candidates(file, &target_name))
6343            } else {
6344                self.resolve_unique_type_for_declaration(file, &primary, &target_name)
6345            };
6346            let Some(target_primary) = target_primary else {
6347                // A dependent or external RHS cannot be canonicalized from the
6348                // indexed graph. Preserve the alias's direct identity instead
6349                // of inventing a target from its source spelling.
6350                return Ok(primary);
6351            };
6352            let Some(target_arguments) = &alias_target.arguments else {
6353                return Ok(target_primary);
6354            };
6355            let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
6356                .ok_or(CppTemplateResolutionError::Substitution)?;
6357            return self.resolve_template_arguments_inner(
6358                file,
6359                target_primary,
6360                &target_arguments,
6361                seen_aliases,
6362            );
6363        }
6364
6365        let primary_fq_name = self
6366            .cpp_template_metadata
6367            .get(&primary)
6368            .map(|metadata| metadata.primary_fq_name.clone())
6369            .unwrap_or_else(|| primary.fq_name());
6370        let has_specialization_metadata = self
6371            .cpp_template_families
6372            .get(&primary_fq_name)
6373            .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
6374        if !has_specialization_metadata {
6375            return Ok(primary);
6376        }
6377        self.select_template_specialization(file, &primary, arguments)
6378    }
6379
6380    fn select_template_specialization(
6381        &self,
6382        file: &ProjectFile,
6383        resolved: &CodeUnit,
6384        explicit_arguments: &[CppTemplateExpression],
6385    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6386        let primary_fq_name = self
6387            .cpp_template_metadata
6388            .get(resolved)
6389            .map(|metadata| metadata.primary_fq_name.clone())
6390            .unwrap_or_else(|| resolved.fq_name());
6391        let family = self
6392            .cpp_template_families
6393            .get(&primary_fq_name)
6394            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6395        let primary_candidates = family
6396            .iter()
6397            .filter_map(|unit| {
6398                let metadata = self.cpp_template_metadata.get(unit)?;
6399                (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
6400            })
6401            .collect::<Vec<_>>();
6402        let primary_unit = primary_candidates
6403            .iter()
6404            .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
6405            .or_else(|| {
6406                primary_candidates
6407                    .iter()
6408                    .map(|(unit, _)| *unit)
6409                    .min_by_key(|unit| {
6410                        (
6411                            unit.source().to_string(),
6412                            unit.signature().unwrap_or_default(),
6413                        )
6414                    })
6415            })
6416            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6417        let primary_parameters =
6418            cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
6419                .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6420        let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
6421            .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6422
6423        let mut applicable = Vec::new();
6424        for unit in family {
6425            let Some(metadata) = self.cpp_template_metadata.get(unit) else {
6426                continue;
6427            };
6428            if metadata.is_primary() || !self.is_visible(file, unit) {
6429                continue;
6430            }
6431            if !cpp_specialization_matches(metadata, &expanded) {
6432                continue;
6433            }
6434            applicable.push((unit, metadata));
6435        }
6436        if applicable.is_empty() {
6437            return Ok(primary_unit.clone());
6438        }
6439
6440        // A scalar constraint count cannot represent C++ partial ordering:
6441        // e.g. `<T*, U>` and `<T, int>` are incomparable for `<int*, int>`.
6442        // Select only a logical candidate whose structural pattern is strictly
6443        // more specialized than every other distinct applicable candidate.
6444        let winners = applicable
6445            .iter()
6446            .filter(|(candidate, candidate_metadata)| {
6447                applicable.iter().all(|(other, other_metadata)| {
6448                    same_visible_symbol(candidate, other)
6449                        || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
6450                })
6451            })
6452            .copied()
6453            .collect::<Vec<_>>();
6454        let Some((selected, _)) = winners.first() else {
6455            // Mutually incomparable applicable candidates: every one of them
6456            // is a live contender.
6457            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6458                candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
6459            });
6460        };
6461        if winners
6462            .iter()
6463            .any(|(unit, _)| !same_visible_symbol(unit, selected))
6464        {
6465            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6466                candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
6467            });
6468        }
6469        Ok((*selected).clone())
6470    }
6471
6472    pub fn resolve_type_components_lexically(
6473        &self,
6474        analyzer: &CppGraphSource<'_>,
6475        file: &ProjectFile,
6476        components: &[String],
6477        global: bool,
6478        lexical_scope: &[String],
6479    ) -> LexicalTypeResolution {
6480        self.resolve_type_components_lexically_inner(
6481            analyzer,
6482            file,
6483            components,
6484            global,
6485            lexical_scope,
6486            TypeCandidateResolution::Canonical,
6487        )
6488    }
6489
6490    pub fn resolve_type_components_lexically_for_forward(
6491        &self,
6492        analyzer: &CppGraphSource<'_>,
6493        file: &ProjectFile,
6494        components: &[String],
6495        global: bool,
6496        lexical_scope: &[String],
6497    ) -> LexicalTypeResolution {
6498        self.resolve_type_components_lexically_inner(
6499            analyzer,
6500            file,
6501            components,
6502            global,
6503            lexical_scope,
6504            TypeCandidateResolution::PreserveAlias,
6505        )
6506    }
6507
6508    pub fn resolve_type_components_lexically_for_target(
6509        &self,
6510        analyzer: &CppGraphSource<'_>,
6511        file: &ProjectFile,
6512        components: &[String],
6513        global: bool,
6514        lexical_scope: &[String],
6515        target: &CodeUnit,
6516    ) -> LexicalTypeResolution {
6517        #[cfg(any(test, feature = "test-support"))]
6518        self.target_preserving_type_resolution_count
6519            .fetch_add(1, Ordering::Relaxed);
6520        self.resolve_type_components_lexically_inner(
6521            analyzer,
6522            file,
6523            components,
6524            global,
6525            lexical_scope,
6526            TypeCandidateResolution::PreserveTarget(target),
6527        )
6528    }
6529
6530    pub fn coarse_unqualified_type_reference_may_resolve(
6531        &self,
6532        file: &ProjectFile,
6533        name: &str,
6534    ) -> bool {
6535        if name.is_empty() {
6536            return true;
6537        }
6538        self.visible_identifier_candidates(file, name)
6539            .any(|candidate| {
6540                candidate.kind() == CodeUnitType::Class
6541                    || declared_type_alias(&self.cpp_source(), candidate)
6542            })
6543            || self.visible_parser_alias_name_is_visible(file, name)
6544    }
6545
6546    #[allow(clippy::too_many_arguments)]
6547    pub fn structured_type_reference_may_resolve_to_target(
6548        &self,
6549        analyzer: &CppGraphSource<'_>,
6550        file: &ProjectFile,
6551        components: &[String],
6552        global: bool,
6553        lexical_scope: &[String],
6554        target: &CodeUnit,
6555    ) -> bool {
6556        if components.is_empty() {
6557            return true;
6558        }
6559        let Some(terminal) = components.last() else {
6560            return true;
6561        };
6562        let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
6563            .map(|qualified| qualified.join("::"))
6564            .collect::<Vec<_>>();
6565        let target_name = cpp_name_for(target);
6566        if qualified_tiers
6567            .iter()
6568            .any(|qualified| qualified == &target_name)
6569        {
6570            return true;
6571        }
6572
6573        let mut saw_shape_candidate = false;
6574        for candidate in self.visible_identifier_candidates(file, terminal) {
6575            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6576            {
6577                continue;
6578            }
6579            let candidate_name = cpp_name_for(candidate);
6580            let shape_matches = if global || components.len() > 1 {
6581                qualified_tiers
6582                    .iter()
6583                    .any(|qualified| qualified == &candidate_name)
6584            } else {
6585                true
6586            };
6587            if !shape_matches {
6588                continue;
6589            }
6590            saw_shape_candidate = true;
6591            if same_visible_symbol(candidate, target)
6592                || self.c_tag_declaration_family_matches_target(
6593                    analyzer,
6594                    file,
6595                    std::slice::from_ref(&candidate),
6596                    target,
6597                )
6598                || self.compatible_primary_template_redeclarations(candidate, target)
6599                || (declared_type_alias(analyzer, candidate)
6600                    && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
6601            {
6602                return true;
6603            }
6604        }
6605
6606        !saw_shape_candidate
6607    }
6608
6609    /// A C tag's forward declaration and complete definition are one logical
6610    /// type even when their indexed signatures and source files differ. Keep
6611    /// this identity narrow: the complete declaration must be a top-level C
6612    /// tag, every visible candidate must be its physical forward declaration,
6613    /// and the parsed tag kind must agree. Different FQNs, competing complete
6614    /// definitions, aliases, and struct/union mismatches remain ambiguous.
6615    fn cached_c_tag_kind(
6616        &self,
6617        analyzer: &CppGraphSource<'_>,
6618        candidate: &CodeUnit,
6619    ) -> Option<CppCTagKind> {
6620        if let Some(kind) = self
6621            .c_tag_kind_cache
6622            .lock()
6623            .expect("C tag kind cache poisoned")
6624            .get(candidate)
6625        {
6626            return *kind;
6627        }
6628        let kind = indexed_c_tag_kind(analyzer, candidate);
6629        self.c_tag_kind_cache
6630            .lock()
6631            .expect("C tag kind cache poisoned")
6632            .insert(candidate.clone(), kind);
6633        kind
6634    }
6635
6636    fn cached_unique_c_tag_complete_definition(
6637        &self,
6638        analyzer: &CppGraphSource<'_>,
6639        target: &CodeUnit,
6640        target_tag: CppCTagKind,
6641    ) -> Option<CodeUnit> {
6642        if let Some(definition) = self
6643            .c_tag_complete_definition_cache
6644            .lock()
6645            .expect("C tag complete-definition cache poisoned")
6646            .get(target)
6647        {
6648            return definition.clone();
6649        }
6650        let complete_definitions = analyzer
6651            .definitions(&target.fq_name())
6652            .filter(|candidate| {
6653                candidate.is_class()
6654                    && !declared_type_alias(analyzer, candidate)
6655                    && is_c_source_file(candidate.source())
6656                    && analyzer.parent_of(candidate).is_none()
6657                    && cpp_class_declaration_strength(analyzer, candidate)
6658                        == CppClassDeclarationStrength::Full
6659                    && self.cached_c_tag_kind(analyzer, candidate) == Some(target_tag)
6660            })
6661            .collect::<HashSet<_>>();
6662        let definition = (complete_definitions.len() == 1)
6663            .then(|| complete_definitions.into_iter().next())
6664            .flatten()
6665            .filter(|candidate| same_visible_symbol(candidate, target));
6666        self.c_tag_complete_definition_cache
6667            .lock()
6668            .expect("C tag complete-definition cache poisoned")
6669            .insert(target.clone(), definition.clone());
6670        definition
6671    }
6672
6673    pub fn c_tag_declaration_family_matches_target(
6674        &self,
6675        analyzer: &CppGraphSource<'_>,
6676        visible_from: &ProjectFile,
6677        candidates: &[&CodeUnit],
6678        target: &CodeUnit,
6679    ) -> bool {
6680        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
6681            let candidate_evidence = candidates
6682                .iter()
6683                .map(|candidate| {
6684                    (
6685                        candidate.fq_name(),
6686                        candidate.source().rel_path().to_path_buf(),
6687                        cpp_class_declaration_strength(analyzer, candidate),
6688                        indexed_c_tag_kind(analyzer, candidate),
6689                        self.is_physically_visible(visible_from, candidate),
6690                    )
6691                })
6692                .collect::<Vec<_>>();
6693            eprintln!(
6694                "BIFROST_CPP_C_TAG_FAMILY_STATS visible_from={} target=({}, {}, {:?}, {:?}) candidates={candidate_evidence:?}",
6695                visible_from.rel_path().display(),
6696                target.fq_name(),
6697                target.source().rel_path().display(),
6698                cpp_class_declaration_strength(analyzer, target),
6699                indexed_c_tag_kind(analyzer, target),
6700            );
6701        }
6702        if candidates.is_empty()
6703            || !target.is_class()
6704            || declared_type_alias(analyzer, target)
6705            || !is_c_source_file(target.source())
6706            || analyzer.parent_of(target).is_some()
6707            || cpp_class_declaration_strength(analyzer, target) != CppClassDeclarationStrength::Full
6708        {
6709            return false;
6710        }
6711        let Some(target_tag) = self.cached_c_tag_kind(analyzer, target) else {
6712            return false;
6713        };
6714        if self
6715            .cached_unique_c_tag_complete_definition(analyzer, target, target_tag)
6716            .is_none()
6717        {
6718            return false;
6719        }
6720        let mut saw_visible_forward = false;
6721        for candidate in candidates.iter().copied() {
6722            if candidate == target {
6723                continue;
6724            }
6725            if !candidate.is_class()
6726                || declared_type_alias(analyzer, candidate)
6727                || candidate.fq_name() != target.fq_name()
6728                || analyzer.parent_of(candidate).is_some()
6729                || cpp_class_declaration_strength(analyzer, candidate)
6730                    != CppClassDeclarationStrength::Forward
6731                || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6732                || !self.is_physically_visible(visible_from, candidate)
6733            {
6734                return false;
6735            }
6736            saw_visible_forward = true;
6737        }
6738        saw_visible_forward
6739    }
6740
6741    /// Collapse one visible complete C tag and its visible forward declarations
6742    /// before ordinary lexical resolution sees their different signatures as
6743    /// competing types. A second complete definition, a different FQN/tag
6744    /// kind, or an unknown declaration shape remains ambiguous.
6745    fn unique_c_tag_declaration_family(
6746        &self,
6747        analyzer: &CppGraphSource<'_>,
6748        visible_from: &ProjectFile,
6749        candidates: &[&CodeUnit],
6750    ) -> Option<CodeUnit> {
6751        let first = candidates.first()?;
6752        let target_fq_name = first.fq_name();
6753        let target_tag = self.cached_c_tag_kind(analyzer, first)?;
6754        let mut full = None;
6755        let mut saw_forward = false;
6756        for candidate in candidates.iter().copied() {
6757            if !candidate.is_class()
6758                || declared_type_alias(analyzer, candidate)
6759                || candidate.fq_name() != target_fq_name
6760                || analyzer.parent_of(candidate).is_some()
6761                || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6762            {
6763                return None;
6764            }
6765            match cpp_class_declaration_strength(analyzer, candidate) {
6766                CppClassDeclarationStrength::Full
6767                    if is_c_source_file(candidate.source())
6768                        && full.replace(candidate.clone()).is_none() => {}
6769                CppClassDeclarationStrength::Forward
6770                    if self.is_physically_visible(visible_from, candidate) =>
6771                {
6772                    saw_forward = true
6773                }
6774                _ => return None,
6775            }
6776        }
6777        if saw_forward { full } else { None }
6778    }
6779
6780    pub fn target_preserving_reference_namespace(
6781        &self,
6782        analyzer: &CppGraphSource<'_>,
6783        file: &ProjectFile,
6784        identifier: &str,
6785        target: &CodeUnit,
6786    ) -> Option<Vec<String>> {
6787        let mut namespace = None;
6788        for candidate in self.visible_identifier_candidates(file, identifier) {
6789            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6790            {
6791                continue;
6792            }
6793            if !(same_visible_symbol(candidate, target)
6794                || self.compatible_primary_template_redeclarations(candidate, target)
6795                || declared_type_alias(analyzer, candidate)
6796                    && self.structured_alias_primary_preserves_target(
6797                        analyzer, file, candidate, target,
6798                    ))
6799            {
6800                continue;
6801            }
6802            if namespace
6803                .as_ref()
6804                .is_some_and(|existing| existing != candidate.package_name())
6805            {
6806                return None;
6807            }
6808            namespace = Some(candidate.package_name().to_string());
6809        }
6810        let namespace = namespace?;
6811        Some(
6812            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6813                brokk_bifrost_core::analyzer::Language::Cpp,
6814                &namespace,
6815            ),
6816        )
6817    }
6818
6819    pub fn resolve_imported_type_candidate(
6820        &self,
6821        analyzer: &CppGraphSource<'_>,
6822        file: &ProjectFile,
6823        target: &CodeUnit,
6824        target_components: &[String],
6825        direct_target: Option<&CodeUnit>,
6826        preserve_alias: bool,
6827    ) -> LexicalTypeResolution {
6828        let candidates = [target];
6829        let resolution = if preserve_alias {
6830            TypeCandidateResolution::PreserveAlias
6831        } else {
6832            direct_target.map_or(
6833                TypeCandidateResolution::Canonical,
6834                TypeCandidateResolution::PreserveTarget,
6835            )
6836        };
6837        // One candidate goes in, so a failure here is never "choose one of
6838        // these": it is the alias chain leaving the index, which must answer
6839        // missing rather than ambiguous (#1828).
6840        match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6841            Ok(unit) => LexicalTypeResolution::Resolved {
6842                unit,
6843                components: target_components.to_vec(),
6844                candidates: vec![target.clone()],
6845            },
6846            Err(failure) => failure.lexical_resolution(),
6847        }
6848    }
6849
6850    fn resolve_type_components_lexically_inner(
6851        &self,
6852        analyzer: &CppGraphSource<'_>,
6853        file: &ProjectFile,
6854        components: &[String],
6855        global: bool,
6856        lexical_scope: &[String],
6857        resolution: TypeCandidateResolution<'_>,
6858    ) -> LexicalTypeResolution {
6859        if components.is_empty() {
6860            return LexicalTypeResolution::Missing;
6861        }
6862        // A C++ class injects its own name into the class scope.  The indexed
6863        // FqName for that declaration is the class path itself (for example,
6864        // `n::raw_hash_set`), not a synthetic child named
6865        // `n::raw_hash_set::raw_hash_set`.  Ordinary lexical tiers append the
6866        // requested identifier to every scope component, so they cannot
6867        // represent that injected binding when the enclosing class is the
6868        // closest scope.  Recover the binding from the structured class path
6869        // before allowing lookup to fall through to an outer same-spelled
6870        // declaration.
6871        let mut injected = self.resolve_injected_class_name(
6872            analyzer,
6873            file,
6874            components,
6875            global,
6876            lexical_scope,
6877            resolution,
6878        );
6879        for qualified in lexical_component_tiers(components, global, lexical_scope) {
6880            let prefix_len = qualified.len().saturating_sub(components.len());
6881            if injected
6882                .as_ref()
6883                .is_some_and(|(owner_len, _)| prefix_len <= *owner_len)
6884            {
6885                return injected
6886                    .take()
6887                    .expect("injected class resolution was just present")
6888                    .1;
6889            }
6890            let qualified_name = qualified.join("::");
6891            let candidates = self
6892                .type_candidates(file, &qualified_name)
6893                .into_iter()
6894                .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6895                .collect::<Vec<_>>();
6896            if candidates.is_empty() {
6897                if !global && components.len() == 1 {
6898                    match self.resolve_inherited_type_for_lexical_scope(
6899                        analyzer,
6900                        file,
6901                        &qualified[..prefix_len],
6902                        &components[0],
6903                        resolution,
6904                    ) {
6905                        LexicalTypeResolution::Missing => {}
6906                        inherited => return inherited,
6907                    }
6908                }
6909                continue;
6910            }
6911            let candidates =
6912                self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6913            let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6914                Ok(unit) => unit,
6915                Err(failure) => return failure.lexical_resolution(),
6916            };
6917            return LexicalTypeResolution::Resolved {
6918                unit,
6919                components: qualified,
6920                candidates: candidates.into_iter().cloned().collect(),
6921            };
6922        }
6923        LexicalTypeResolution::Missing
6924    }
6925
6926    fn resolve_injected_class_name(
6927        &self,
6928        analyzer: &CppGraphSource<'_>,
6929        file: &ProjectFile,
6930        components: &[String],
6931        global: bool,
6932        lexical_scope: &[String],
6933        resolution: TypeCandidateResolution<'_>,
6934    ) -> Option<(usize, LexicalTypeResolution)> {
6935        if global
6936            || components.len() != 1
6937            || file.rel_path().extension().is_some_and(|ext| ext == "c")
6938            || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
6939        {
6940            return None;
6941        }
6942        let name = components.first()?;
6943        let mut matches: Vec<&CodeUnit> = Vec::new();
6944        let mut owner_len = 0;
6945        for candidate in self.visible_identifier_candidates(file, name) {
6946            if !candidate.is_class()
6947                || declared_type_alias(analyzer, candidate)
6948                || candidate.identifier() != name
6949            {
6950                continue;
6951            }
6952            let candidate_scope = canonical_cpp_scope_components(candidate);
6953            if candidate_scope.len() > lexical_scope.len()
6954                || !lexical_scope.starts_with(&candidate_scope)
6955                || candidate_scope.last().is_none_or(|last| last != name)
6956            {
6957                continue;
6958            }
6959            if candidate_scope.len() > owner_len {
6960                owner_len = candidate_scope.len();
6961                matches.clear();
6962            }
6963            if candidate_scope.len() == owner_len
6964                && !matches
6965                    .iter()
6966                    .any(|existing| same_logical_symbol(existing, candidate))
6967            {
6968                matches.push(candidate);
6969            }
6970        }
6971        if matches.is_empty() {
6972            return None;
6973        }
6974        // The exact current class boundary is the most important injected-name
6975        // case. Ordinary lexical lookup appends the requested name and can
6976        // otherwise select a synthetic constructor-shaped child such as
6977        // `Portal::Impl::Impl` instead of the enclosing `Portal::Impl` class.
6978        // The global and single-component gates above keep qualified receiver
6979        // and static-qualifier contexts out of this recovery.
6980        let owner_components = lexical_scope[..owner_len].to_vec();
6981        let matches = self.candidates_for_type_resolution(analyzer, file, &matches, resolution);
6982        let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
6983            Ok(unit) => LexicalTypeResolution::Resolved {
6984                unit,
6985                components: owner_components,
6986                candidates: matches.into_iter().cloned().collect(),
6987            },
6988            Err(failure) => failure.lexical_resolution(),
6989        };
6990        Some((owner_len, resolution))
6991    }
6992
6993    fn resolve_inherited_type_for_lexical_scope(
6994        &self,
6995        analyzer: &CppGraphSource<'_>,
6996        file: &ProjectFile,
6997        lexical_scope: &[String],
6998        name: &str,
6999        resolution: TypeCandidateResolution<'_>,
7000    ) -> LexicalTypeResolution {
7001        let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
7002            return LexicalTypeResolution::Missing;
7003        };
7004        let lexical_owner_name = lexical_scope.join("::");
7005        if lexical_owner_name.is_empty() {
7006            return LexicalTypeResolution::Missing;
7007        }
7008        let owner_candidates = self
7009            .type_candidates(file, &lexical_owner_name)
7010            .into_iter()
7011            .filter(|candidate| {
7012                canonical_cpp_name_matches(candidate, &lexical_owner_name)
7013                    && !declared_type_alias(analyzer, candidate)
7014            })
7015            .collect::<Vec<_>>();
7016        if owner_candidates.is_empty() {
7017            return LexicalTypeResolution::Missing;
7018        }
7019        // A visible forward declaration and the physical class definition share
7020        // one FQN, but only the definition owns hierarchy facts. When lookup is
7021        // physically inside that definition, do not let an earlier header
7022        // forward declaration erase its base edges (#2240).
7023        let physical_owner_candidates = owner_candidates
7024            .iter()
7025            .copied()
7026            .filter(|candidate| candidate.source() == file)
7027            .collect::<Vec<_>>();
7028        let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
7029            owner_candidates
7030        } else {
7031            physical_owner_candidates
7032        };
7033        let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
7034            return LexicalTypeResolution::Ambiguous;
7035        };
7036
7037        let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
7038        let mut visited_owners = HashSet::default();
7039        while !frontier.is_empty() {
7040            let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
7041            let mut next_frontier = Vec::new();
7042            for owner in frontier {
7043                if !visited_owners.insert(owner.fq_name()) {
7044                    continue;
7045                }
7046                let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
7047                let candidates = self
7048                    .type_candidates(file, &qualified_name)
7049                    .into_iter()
7050                    .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
7051                    .collect::<Vec<_>>();
7052                if candidates.is_empty() {
7053                    for ancestor in hierarchy.get_direct_ancestors(&owner) {
7054                        if !next_frontier
7055                            .iter()
7056                            .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
7057                        {
7058                            next_frontier.push(ancestor);
7059                        }
7060                    }
7061                    continue;
7062                }
7063                let candidates =
7064                    self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
7065                let unit =
7066                    match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
7067                        Ok(unit) => unit,
7068                        Err(failure) => return failure.lexical_resolution(),
7069                    };
7070                level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
7071            }
7072            if let Some((unit, candidates)) = level_matches.first().cloned() {
7073                let Some(first_declaration) = candidates.first() else {
7074                    return LexicalTypeResolution::Ambiguous;
7075                };
7076                if !level_matches.iter().all(|(_, declarations)| {
7077                    declarations
7078                        .iter()
7079                        .all(|declaration| same_logical_symbol(first_declaration, declaration))
7080                }) {
7081                    return LexicalTypeResolution::Ambiguous;
7082                }
7083                let mut components = lexical_scope.to_vec();
7084                components.push(name.to_string());
7085                return LexicalTypeResolution::Resolved {
7086                    unit,
7087                    components,
7088                    candidates,
7089                };
7090            }
7091            frontier = next_frontier;
7092        }
7093        LexicalTypeResolution::Missing
7094    }
7095
7096    /// Resolve a base class through its injected class name at the nearest
7097    /// inheritance tier. Distinct same-named bases at that tier are ambiguous.
7098    ///
7099    /// A base whose canonical full definition cannot be pinned from `file` -
7100    /// a forward declaration the include closure completes with two different
7101    /// full definitions, or an alias chain that leaves the index - stops the
7102    /// walk only when that base is spelled `injected_name`. The mem-initializer
7103    /// names a base by that base's own injected class name, so a base spelled
7104    /// differently can never be the one it names, whichever definition it would
7105    /// have turned out to be; aborting the level on its account instead loses
7106    /// the sibling base that *is* named (#2543). A base that is spelled
7107    /// `injected_name` still fails closed, because choosing a deeper same-named
7108    /// ancestor over it would bind the initializer to the wrong constructor.
7109    /// The skipped base carries its own ancestors out of the walk with it: with
7110    /// no canonical unit, the repeated-base accounting below cannot tell one
7111    /// inherited path through it from two.
7112    pub fn inherited_injected_class_owner(
7113        &self,
7114        analyzer: &CppGraphSource<'_>,
7115        file: &ProjectFile,
7116        enclosing_owner: &CodeUnit,
7117        injected_name: &str,
7118    ) -> Option<CodeUnit> {
7119        let hierarchy = analyzer.type_hierarchy_provider()?;
7120        let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
7121        let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
7122        while !frontier.is_empty() {
7123            let mut level_matches = Vec::new();
7124            let mut next_frontier = Vec::new();
7125            for raw_owner in frontier {
7126                let Some(owner) = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
7127                else {
7128                    if raw_owner.identifier() == injected_name {
7129                        return None;
7130                    }
7131                    continue;
7132                };
7133                let propagated = propagated_counts.entry(owner.clone()).or_default();
7134                if *propagated == 2 {
7135                    continue;
7136                }
7137                *propagated += 1;
7138                if owner.identifier() == injected_name {
7139                    level_matches.push(owner.clone());
7140                }
7141                next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
7142            }
7143            match level_matches.as_slice() {
7144                [owner] => return Some(owner.clone()),
7145                [_, ..] => return None,
7146                [] => {}
7147            }
7148            frontier = next_frontier;
7149        }
7150        None
7151    }
7152
7153    /// The one type the candidates name under `resolution`, or why they do not
7154    /// name one. The two preserving modes only ever reject candidates that
7155    /// disagree with each other, which is ambiguity; canonicalization can also
7156    /// fail because the alias chain leaves the index (#1828).
7157    fn resolve_type_candidates(
7158        &self,
7159        analyzer: &CppGraphSource<'_>,
7160        file: &ProjectFile,
7161        candidates: &[&CodeUnit],
7162        resolution: TypeCandidateResolution<'_>,
7163    ) -> Result<CodeUnit, TypeCandidateFailure> {
7164        if !matches!(resolution, TypeCandidateResolution::PreserveTarget(_))
7165            && let Some(unit) = self.unique_c_tag_declaration_family(analyzer, file, candidates)
7166        {
7167            return Ok(unit);
7168        }
7169        match resolution {
7170            TypeCandidateResolution::Canonical => {
7171                self.canonical_type_candidate_resolution(analyzer, file, candidates)
7172            }
7173            TypeCandidateResolution::PreserveAlias => {
7174                // A generated index can retain identical alias spellings from
7175                // mutually exclusive headers. When the reference file
7176                // physically reaches exactly one of those source declarations,
7177                // include closure is the structured evidence that selects it;
7178                // treating the two source spellings as an overload set makes a
7179                // reachable alias appear ambiguous (#1844).
7180                let same_fqn_alias_family = candidates.len() > 1
7181                    && candidates.iter().all(|candidate| {
7182                        declared_type_alias(analyzer, candidate)
7183                            && same_logical_symbol(candidates[0], candidate)
7184                    })
7185                    && candidates
7186                        .iter()
7187                        .any(|candidate| candidate.source() != candidates[0].source());
7188                if same_fqn_alias_family {
7189                    let physically_visible = candidates
7190                        .iter()
7191                        .copied()
7192                        .filter(|candidate| self.is_physically_visible(file, candidate))
7193                        .collect::<Vec<_>>();
7194                    // The family is one logical declaration only when the
7195                    // reachable spellings agree. Two same-FQN aliases whose
7196                    // written targets differ (`using Choice = Canonical;` in
7197                    // one header, `using Choice = ::Canonical;` in another)
7198                    // are a genuine conflict, and choosing the first indexed
7199                    // one silently binds the reference to an arbitrary owner
7200                    // (#2398). Collapse only a single reachable declaration
7201                    // or reachable declarations with one structured target;
7202                    // everything else stays ambiguous below.
7203                    if let Some(alias) = select_alias_spelling_with_agreed_targets(
7204                        &physically_visible,
7205                        |candidate| self.structured_alias_target(analyzer, candidate),
7206                    ) {
7207                        return Ok(alias.clone());
7208                    }
7209                }
7210                unique_type_candidate_preserving_alias(analyzer, file, candidates)
7211                    .ok_or(TypeCandidateFailure::Ambiguous)
7212            }
7213            TypeCandidateResolution::PreserveTarget(target) => self
7214                .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
7215                .ok_or(TypeCandidateFailure::Ambiguous),
7216        }
7217    }
7218
7219    fn candidates_for_type_resolution<'b>(
7220        &self,
7221        analyzer: &CppGraphSource<'_>,
7222        file: &ProjectFile,
7223        candidates: &[&'b CodeUnit],
7224        resolution: TypeCandidateResolution<'_>,
7225    ) -> Vec<&'b CodeUnit> {
7226        if matches!(resolution, TypeCandidateResolution::PreserveAlias) && candidates.len() > 1 {
7227            let compile_proven = self.compile_proven_type_candidates(analyzer, file, candidates);
7228            if compile_proven.len() == 1 {
7229                return compile_proven;
7230            }
7231        }
7232        candidates.to_vec()
7233    }
7234
7235    /// Narrow a same-name forward lookup to the declaration selected by the
7236    /// translation unit's compile command. The lexical resolver intentionally
7237    /// does not receive a reference node, so this is the only compile-context
7238    /// evidence available at that stage. A single selected candidate is safe:
7239    /// the caller still checks include activation and the reference's own
7240    /// guards before reporting the result as visible.
7241    fn compile_proven_type_candidates<'b>(
7242        &self,
7243        analyzer: &CppGraphSource<'_>,
7244        file: &ProjectFile,
7245        candidates: &[&'b CodeUnit],
7246    ) -> Vec<&'b CodeUnit> {
7247        let proven = self.compile_proven_guards(file);
7248        if proven.is_empty() {
7249            return Vec::new();
7250        }
7251        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
7252            return Vec::new();
7253        };
7254        candidates
7255            .iter()
7256            .copied()
7257            .filter(|candidate| {
7258                let declaration_guards =
7259                    declaration_guard_requirements(analyzer, self.cpp, candidate);
7260                if declaration_guards.is_empty() {
7261                    return false;
7262                }
7263                if candidate.source() == file {
7264                    return declaration_guards.iter().any(|(_, required)| {
7265                        guard_requirements_hold_at_reference(required, Some(proven.as_ref()))
7266                    });
7267                }
7268                declaration_guards.iter().any(|(_, required)| {
7269                    self.foreign_declaration_reachable_from_compile_proven_guards(
7270                        file,
7271                        prepared.as_ref(),
7272                        candidate.source(),
7273                        required,
7274                        usize::MAX,
7275                    )
7276                })
7277            })
7278            .collect()
7279    }
7280
7281    pub fn resolve_callable_value_components_lexically(
7282        &self,
7283        analyzer: &CppGraphSource<'_>,
7284        file: &ProjectFile,
7285        owner_components: &[String],
7286        member_name: &str,
7287        global: bool,
7288        lexical_scope: &[String],
7289    ) -> LexicalCallableValueResolution {
7290        if owner_components.is_empty() || member_name.is_empty() {
7291            return LexicalCallableValueResolution::Missing;
7292        }
7293        for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
7294            let owner_name = qualified_owner.join("::");
7295            let type_candidates = self
7296                .type_candidates(file, &owner_name)
7297                .into_iter()
7298                .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
7299                .collect::<Vec<_>>();
7300            let resolved_type = if type_candidates.is_empty() {
7301                None
7302            } else {
7303                let Some(unit) =
7304                    self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
7305                else {
7306                    return LexicalCallableValueResolution::Ambiguous;
7307                };
7308                Some(unit)
7309            };
7310
7311            let mut qualified_callable = qualified_owner;
7312            qualified_callable.push(member_name.to_string());
7313            let callable_name = qualified_callable.join("::");
7314            let free_function = self
7315                .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
7316                .into_iter()
7317                .find(|candidate| {
7318                    canonical_cpp_name_matches(candidate, &callable_name)
7319                        && type_owner_of(analyzer, candidate).is_none()
7320                })
7321                .cloned();
7322
7323            match (resolved_type, free_function) {
7324                (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
7325                (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
7326                (None, Some(function)) => {
7327                    return LexicalCallableValueResolution::FreeFunction(function);
7328                }
7329                (None, None) => {}
7330            }
7331        }
7332        LexicalCallableValueResolution::Missing
7333    }
7334
7335    fn resolve_type_for_declaration(
7336        &self,
7337        visible_from: &ProjectFile,
7338        declaration: &CodeUnit,
7339        raw_name: &str,
7340    ) -> Option<CodeUnit> {
7341        let normalized = normalize_reference_name(raw_name)?;
7342        if !normalized.contains("::")
7343            && let Some(namespace) = cpp_namespace_for(declaration)
7344        {
7345            for prefix in namespace_prefixes(&namespace) {
7346                let qualified = format!("{prefix}::{normalized}");
7347                if let Some(unit) = self
7348                    .type_candidates(visible_from, &qualified)
7349                    .into_iter()
7350                    .next()
7351                {
7352                    return Some(unit.clone());
7353                }
7354            }
7355        }
7356        self.resolve_type(visible_from, raw_name)
7357    }
7358
7359    fn resolve_unique_canonical_type_for_declaration(
7360        &self,
7361        analyzer: &CppGraphSource<'_>,
7362        visible_from: &ProjectFile,
7363        declaration: &CodeUnit,
7364        raw_name: &str,
7365    ) -> Option<CodeUnit> {
7366        let current = self.resolve_defining_type_for_declaration(
7367            analyzer,
7368            visible_from,
7369            declaration,
7370            raw_name,
7371        )?;
7372        self.canonical_type_resolution(analyzer, visible_from, &current)
7373            .ok()
7374    }
7375
7376    pub fn canonical_type_unit(
7377        &self,
7378        analyzer: &CppGraphSource<'_>,
7379        visible_from: &ProjectFile,
7380        unit: &CodeUnit,
7381    ) -> Option<CodeUnit> {
7382        self.canonical_type_resolution(analyzer, visible_from, unit)
7383            .ok()
7384    }
7385
7386    /// Follow an alias only when it is visible at `reference`.
7387    ///
7388    /// The consumer need not spell the alias target. In particular, a
7389    /// conditional include can make `PublicPtr` visible at the reference while
7390    /// ordinary physical-include visibility is false. Prove the public alias
7391    /// with the reference's guard environment, then use the existing structured
7392    /// alias-chain resolver over the consumer's bounded declaration index.
7393    pub fn canonical_type_unit_in_context(
7394        &self,
7395        analyzer: &CppGraphSource<'_>,
7396        visible_from: &ProjectFile,
7397        reference: Node<'_>,
7398        unit: &CodeUnit,
7399    ) -> Option<CodeUnit> {
7400        if !self.external_type_candidate_visible_in_context(analyzer, visible_from, unit, reference)
7401        {
7402            return None;
7403        }
7404        self.canonical_type_resolution(analyzer, visible_from, unit)
7405            .ok()
7406    }
7407
7408    /// Follow `unit`'s alias chain to the class it names, or report why the
7409    /// chain does not end at one indexed class.
7410    ///
7411    /// A chain that leaves the index - an alias to a template parameter, to a
7412    /// standard-library type, or to any other declaration the workspace does
7413    /// not hold - is `Unresolvable`, not `Ambiguous` (#1828). So is a cycle:
7414    /// there is still nothing to choose between.
7415    fn canonical_type_resolution(
7416        &self,
7417        analyzer: &CppGraphSource<'_>,
7418        visible_from: &ProjectFile,
7419        unit: &CodeUnit,
7420    ) -> Result<CodeUnit, TypeCandidateFailure> {
7421        let mut current = unit.clone();
7422        let mut seen_aliases = HashSet::default();
7423        loop {
7424            let Some(target) = self.structured_alias_target(analyzer, &current) else {
7425                return current
7426                    .is_class()
7427                    .then_some(current)
7428                    .ok_or(TypeCandidateFailure::Unresolvable);
7429            };
7430            if matches!(target, StructuredAliasTarget::Builtin) {
7431                return current
7432                    .is_class()
7433                    .then_some(current)
7434                    .ok_or(TypeCandidateFailure::Unresolvable);
7435            }
7436            if !seen_aliases.insert(current.clone()) {
7437                return Err(TypeCandidateFailure::Unresolvable);
7438            }
7439            current = self.structured_alias_target_resolution(visible_from, &current, &target)?;
7440        }
7441    }
7442
7443    pub fn canonical_visible_full_type_unit(
7444        &self,
7445        analyzer: &CppGraphSource<'_>,
7446        visible_from: &ProjectFile,
7447        unit: &CodeUnit,
7448    ) -> Option<CodeUnit> {
7449        let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
7450        if cpp_class_declaration_strength(analyzer, &canonical)
7451            != CppClassDeclarationStrength::Forward
7452        {
7453            return Some(canonical);
7454        }
7455        let mut full = Vec::new();
7456        for candidate in self
7457            .visible_identifier_candidates(visible_from, canonical.identifier())
7458            .filter(|candidate| {
7459                candidate.is_class()
7460                    && candidate.fq_name() == canonical.fq_name()
7461                    && cpp_class_declaration_strength(analyzer, candidate)
7462                        == CppClassDeclarationStrength::Full
7463            })
7464        {
7465            if !full.iter().any(|existing| same_symbol(existing, candidate)) {
7466                full.push(candidate.clone());
7467            }
7468        }
7469        match full.len() {
7470            0 => Some(canonical),
7471            1 => full.pop(),
7472            _ => None,
7473        }
7474    }
7475
7476    fn resolve_structured_alias_target(
7477        &self,
7478        visible_from: &ProjectFile,
7479        declaration: &CodeUnit,
7480        target: &StructuredAliasTarget,
7481    ) -> Option<CodeUnit> {
7482        self.structured_alias_target_resolution(visible_from, declaration, target)
7483            .ok()
7484    }
7485
7486    fn structured_alias_target_resolution(
7487        &self,
7488        visible_from: &ProjectFile,
7489        declaration: &CodeUnit,
7490        target: &StructuredAliasTarget,
7491    ) -> Result<CodeUnit, TypeCandidateFailure> {
7492        let primary =
7493            self.structured_alias_primary_resolution(visible_from, declaration, target)?;
7494        let StructuredAliasTarget::Named { arguments, .. } = target else {
7495            return Err(TypeCandidateFailure::Unresolvable);
7496        };
7497        match arguments {
7498            Some(arguments) => self
7499                .resolve_template_arguments(visible_from, primary, arguments)
7500                .map_err(|error| match error {
7501                    CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
7502                        TypeCandidateFailure::Ambiguous
7503                    }
7504                    _ => TypeCandidateFailure::Unresolvable,
7505                }),
7506            None => Ok(primary),
7507        }
7508    }
7509
7510    fn resolve_structured_alias_primary(
7511        &self,
7512        visible_from: &ProjectFile,
7513        declaration: &CodeUnit,
7514        target: &StructuredAliasTarget,
7515    ) -> Option<CodeUnit> {
7516        self.structured_alias_primary_resolution(visible_from, declaration, target)
7517            .ok()
7518    }
7519
7520    fn structured_alias_primary_resolution(
7521        &self,
7522        visible_from: &ProjectFile,
7523        declaration: &CodeUnit,
7524        target: &StructuredAliasTarget,
7525    ) -> Result<CodeUnit, TypeCandidateFailure> {
7526        let StructuredAliasTarget::Named {
7527            components, global, ..
7528        } = target
7529        else {
7530            return Err(TypeCandidateFailure::Unresolvable);
7531        };
7532        let qualified = components.join("::");
7533        let candidates = if *global {
7534            // `::A::B` anchors at the root scope, so a candidate whose
7535            // canonical path merely ends with the spelled components does not
7536            // qualify. Without this filter a global `::Canonical` target also
7537            // collects `alpha::Canonical`, the lookup reports a false
7538            // ambiguity, and the alias arm silently drops out of its
7539            // conflicting family instead of proving the conflict (#2398).
7540            let mut candidates = self.type_candidates(visible_from, &qualified);
7541            candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
7542            candidates
7543        } else {
7544            self.type_candidates_for_declaration(visible_from, declaration, &qualified)
7545        };
7546        logical_type_candidate(candidates)
7547    }
7548
7549    pub fn structured_alias_primary_preserves_target(
7550        &self,
7551        analyzer: &CppGraphSource<'_>,
7552        visible_from: &ProjectFile,
7553        candidate: &CodeUnit,
7554        target: &CodeUnit,
7555    ) -> bool {
7556        let mut current = candidate.clone();
7557        let mut seen = HashSet::default();
7558        let mut matched_target = false;
7559        loop {
7560            if same_visible_symbol(&current, target)
7561                || self.compatible_primary_template_redeclarations(&current, target)
7562            {
7563                matched_target = true;
7564            }
7565            if !seen.insert(current.clone()) {
7566                return false;
7567            }
7568            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
7569                return matched_target;
7570            };
7571            if matches!(alias_target, StructuredAliasTarget::Builtin) {
7572                return matched_target;
7573            };
7574            let Some(primary) =
7575                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
7576            else {
7577                // A dependent member target such as `Detector<T>::type`
7578                // cannot be reduced to an indexed primary, but a preceding
7579                // structured alias hop may already have proven the requested
7580                // alias identity. Cycles still resolve a primary and are
7581                // rejected by `seen` above.
7582                return matched_target;
7583            };
7584            current = primary;
7585        }
7586    }
7587
7588    fn lexical_type_candidates_match_target(
7589        &self,
7590        analyzer: &CppGraphSource<'_>,
7591        unit: &CodeUnit,
7592        candidates: &[CodeUnit],
7593        target: &CodeUnit,
7594    ) -> bool {
7595        same_visible_symbol(unit, target)
7596            || self.same_template_member_identity(analyzer, unit, target)
7597            || candidates.iter().any(|candidate| {
7598                same_visible_symbol(candidate, target)
7599                    || self.same_template_member_identity(analyzer, candidate, target)
7600            })
7601    }
7602
7603    pub fn structured_class_alias_resolves_to_target(
7604        &self,
7605        analyzer: &CppGraphSource<'_>,
7606        visible_from: &ProjectFile,
7607        alias: &CodeUnit,
7608        target: &CodeUnit,
7609    ) -> bool {
7610        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7611            return false;
7612        };
7613        let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
7614            return false;
7615        };
7616        let StructuredAliasTarget::Named {
7617            components, global, ..
7618        } = &alias_target
7619        else {
7620            return false;
7621        };
7622        let lexical_scope = canonical_cpp_scope_components(&owner);
7623        match self.resolve_type_components_lexically_for_target(
7624            analyzer,
7625            visible_from,
7626            components,
7627            *global,
7628            &lexical_scope,
7629            target,
7630        ) {
7631            LexicalTypeResolution::Resolved {
7632                unit, candidates, ..
7633            } => self.lexical_type_candidates_match_target(analyzer, &unit, &candidates, target),
7634            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7635                self.structured_alias_primary_preserves_target(
7636                    analyzer,
7637                    visible_from,
7638                    alias,
7639                    target,
7640                ) || self.flattened_macro_namespace_alias_target_matches(
7641                    analyzer,
7642                    visible_from,
7643                    alias,
7644                    &alias_target,
7645                    target,
7646                )
7647            }
7648        }
7649    }
7650
7651    /// Return true when a class-owned alias names the requested type as one
7652    /// structured qualifier in its target path.
7653    ///
7654    /// A dependent target such as `Primary<T>::Type` cannot resolve to one
7655    /// indexed class. Forward lookup can still retain `Primary` as its bounded
7656    /// canonical identity. Inverse lookup needs the same evidence when later
7657    /// references use only the alias spelling.
7658    pub fn structured_class_alias_path_preserves_target(
7659        &self,
7660        analyzer: &CppGraphSource<'_>,
7661        visible_from: &ProjectFile,
7662        alias: &CodeUnit,
7663        target: &CodeUnit,
7664    ) -> bool {
7665        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7666            return false;
7667        };
7668        let Some(StructuredAliasTarget::Named {
7669            components, global, ..
7670        }) = self.structured_alias_target(analyzer, alias)
7671        else {
7672            return false;
7673        };
7674        let lexical_scope = canonical_cpp_scope_components(&owner);
7675        (1..components.len()).rev().any(|component_count| {
7676            matches!(
7677                self.resolve_type_components_lexically_for_target(
7678                    analyzer,
7679                    visible_from,
7680                    &components[..component_count],
7681                    global,
7682                    &lexical_scope,
7683                    target,
7684                ),
7685                LexicalTypeResolution::Resolved {
7686                    ref unit,
7687                    ref candidates,
7688                    ..
7689                } if self.lexical_type_candidates_match_target(
7690                    analyzer,
7691                    unit,
7692                    candidates,
7693                    target,
7694                )
7695            )
7696        })
7697    }
7698
7699    fn flattened_macro_namespace_alias_target_matches(
7700        &self,
7701        analyzer: &CppGraphSource<'_>,
7702        visible_from: &ProjectFile,
7703        alias: &CodeUnit,
7704        alias_target: &StructuredAliasTarget,
7705        target: &CodeUnit,
7706    ) -> bool {
7707        let StructuredAliasTarget::Named {
7708            components,
7709            global: false,
7710            arguments: None,
7711        } = alias_target
7712        else {
7713            return false;
7714        };
7715        let Some((target_name, namespace_components)) = components.split_last() else {
7716            return false;
7717        };
7718        if namespace_components.is_empty()
7719            || target_name != target.identifier()
7720            || alias.source() != target.source()
7721            || alias.source() != visible_from
7722            || !target.is_class()
7723            || declared_type_alias(analyzer, target)
7724        {
7725            return false;
7726        }
7727        if self
7728            .resolve_structured_alias_target(visible_from, alias, alias_target)
7729            .is_some()
7730        {
7731            return false;
7732        }
7733
7734        let Some(alias_start) = analyzer
7735            .declaration_source_occurrences(alias)
7736            .into_iter()
7737            .flatten()
7738            .map(|occurrence| occurrence.range.start_byte)
7739            .min()
7740        else {
7741            return false;
7742        };
7743        let has_matching_declaration = analyzer
7744            .declaration_source_occurrences(target)
7745            .into_iter()
7746            .flatten()
7747            .any(|occurrence| {
7748                occurrence.range.end_byte <= alias_start
7749                    && occurrence.fact.flattened_macro_namespace.as_deref()
7750                        == Some(namespace_components)
7751            });
7752        if !has_matching_declaration {
7753            return false;
7754        }
7755
7756        let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
7757        let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
7758        guard_requirement_sets_match(&alias_guards, &target_guards)
7759    }
7760
7761    pub fn template_alias_arguments_preserve_target(
7762        &self,
7763        analyzer: &CppGraphSource<'_>,
7764        visible_from: &ProjectFile,
7765        alias: &CodeUnit,
7766        arguments: &[CppTemplateExpression],
7767        target: &CodeUnit,
7768    ) -> bool {
7769        let Some(metadata) = self.cpp_template_metadata.get(alias) else {
7770            return false;
7771        };
7772        if metadata.alias_target.is_none()
7773            || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
7774        {
7775            return false;
7776        }
7777        self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
7778    }
7779
7780    pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
7781        self.cpp_template_metadata
7782            .get(unit)
7783            .is_some_and(CppTemplateMetadata::is_primary)
7784    }
7785
7786    pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
7787        self.cpp_template_metadata
7788            .get(unit)
7789            .is_some_and(CppTemplateMetadata::is_specialization)
7790    }
7791
7792    pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
7793        same_visible_symbol(left, right)
7794            || self.compatible_primary_template_redeclarations(left, right)
7795    }
7796
7797    pub fn same_template_member_identity(
7798        &self,
7799        analyzer: &CppGraphSource<'_>,
7800        left: &CodeUnit,
7801        right: &CodeUnit,
7802    ) -> bool {
7803        if same_visible_symbol(left, right) {
7804            return true;
7805        }
7806        if left.kind() != right.kind()
7807            || left.identifier() != right.identifier()
7808            || left.signature() != right.signature()
7809        {
7810            return false;
7811        }
7812        let (Some(left_owner), Some(right_owner)) =
7813            (analyzer.parent_of(left), analyzer.parent_of(right))
7814        else {
7815            return false;
7816        };
7817        left_owner.is_class()
7818            && right_owner.is_class()
7819            && self.same_template_owner_identity(&left_owner, &right_owner)
7820    }
7821
7822    fn unique_canonical_type_candidate(
7823        &self,
7824        analyzer: &CppGraphSource<'_>,
7825        visible_from: &ProjectFile,
7826        candidates: &[&CodeUnit],
7827    ) -> Option<CodeUnit> {
7828        self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
7829            .ok()
7830    }
7831
7832    fn canonical_type_candidate_resolution(
7833        &self,
7834        analyzer: &CppGraphSource<'_>,
7835        visible_from: &ProjectFile,
7836        candidates: &[&CodeUnit],
7837    ) -> Result<CodeUnit, TypeCandidateFailure> {
7838        let mut canonical = Vec::new();
7839        for candidate in candidates {
7840            let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
7841            if canonical
7842                .iter()
7843                .any(|existing| same_visible_symbol(existing, &resolved))
7844            {
7845                continue;
7846            }
7847            if let Some(existing) = canonical.iter_mut().find(|existing| {
7848                self.compatible_primary_template_redeclarations(existing, &resolved)
7849            }) {
7850                // A forward declaration and its full primary-template
7851                // definition are one C++ type even when they live in
7852                // different headers and alpha-rename their parameters. The
7853                // target-preserving path already reconciles this family; do
7854                // the same for ordinary canonical lookup so an out-of-line
7855                // member's lexical owner is not made ambiguous by its own
7856                // forward declaration. Retain the strongest physical
7857                // declaration for later owner/range queries.
7858                if matches!(
7859                    (
7860                        cpp_class_declaration_strength(analyzer, existing),
7861                        cpp_class_declaration_strength(analyzer, &resolved),
7862                    ),
7863                    (
7864                        CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
7865                        CppClassDeclarationStrength::Full,
7866                    ) | (
7867                        CppClassDeclarationStrength::Unknown,
7868                        CppClassDeclarationStrength::Forward,
7869                    )
7870                ) {
7871                    *existing = resolved;
7872                }
7873                continue;
7874            }
7875            canonical.push(resolved);
7876            if canonical.len() > 1 {
7877                return Err(TypeCandidateFailure::Ambiguous);
7878            }
7879        }
7880        canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
7881    }
7882
7883    pub fn unique_type_candidate_preserving_target(
7884        &self,
7885        analyzer: &CppGraphSource<'_>,
7886        visible_from: &ProjectFile,
7887        candidates: &[&CodeUnit],
7888        target: &CodeUnit,
7889    ) -> Option<CodeUnit> {
7890        // C++ headers often expose one logical type through mutually exclusive
7891        // physical declarations, for example a class in the fallback branch
7892        // and a `using` alias to the standard-library type in the configured
7893        // branch. The index intentionally retains both declarations so forward
7894        // lookup can report each target. Preserve the requested target when
7895        // that is the only ambiguity: every candidate has the same type kind,
7896        // exact canonical FQN, and source file, and the requested declaration
7897        // itself is one of the physical candidates. Do not merge same-named
7898        // declarations from different files or namespaces; those remain
7899        // ambiguous and fail closed below.
7900        if self.c_tag_declaration_family_matches_target(analyzer, visible_from, candidates, target)
7901            || self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7902        {
7903            return Some(target.clone());
7904        }
7905        let mut resolved_candidates = Vec::new();
7906        for candidate in candidates {
7907            // An ifdef branch that aliases an unindexed system type (for
7908            // example `typedef pthread_mutex_t k5_os_mutex`) cannot be
7909            // canonicalized. That branch does not name `target`. Dropping it
7910            // keeps the branch that does. Failing the whole family here would
7911            // deny every usage of the reachable spelling (#2368).
7912            let Some(resolved) =
7913                self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7914            else {
7915                continue;
7916            };
7917            if resolved_candidates
7918                .iter()
7919                .any(|existing| same_visible_symbol(existing, &resolved))
7920            {
7921                continue;
7922            }
7923            resolved_candidates.push(resolved);
7924        }
7925        match resolved_candidates.as_slice() {
7926            [] => None,
7927            [single] => Some(single.clone()),
7928            // The branches disagree about what the name aliases. When they are
7929            // spellings of one entity (#1845) that disagreement is a build
7930            // configuration, not a choice between types, so it must not deny
7931            // the requested target its reference.
7932            _ => self
7933                .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
7934                .map(|_| target.clone()),
7935        }
7936    }
7937
7938    /// The declaration a same-file same-FQN family stands for when a reference
7939    /// names `target`, or `None` when the candidates are not one family or the
7940    /// family does not name `target`.
7941    ///
7942    /// A translation unit cannot hold two different types under one qualified
7943    /// name, so several same-kind declarations of one FQN in one file are
7944    /// alternate spellings of one entity - the configuration branches of an
7945    /// `#if` family, for example log4cxx's `logchar`, which aliases `char` in
7946    /// the UTF-8 branch and `UniChar` in the unichar branch. Their alias
7947    /// targets differ; canonicalizing each branch on its own and then demanding
7948    /// agreement reports an ambiguity that denies every declaration in the
7949    /// family its usages (#1845). The family names `target` when it declares
7950    /// it, or when one branch's alias chain reaches it.
7951    ///
7952    /// Declarations in different files or namespaces are distinct entities and
7953    /// are deliberately excluded: their disagreement is a real ambiguity.
7954    pub fn same_fqn_type_spelling_for_target<'b>(
7955        &self,
7956        analyzer: &CppGraphSource<'_>,
7957        visible_from: &ProjectFile,
7958        candidates: &[&'b CodeUnit],
7959        target: &CodeUnit,
7960    ) -> Option<&'b CodeUnit> {
7961        let [first, rest @ ..] = candidates else {
7962            return None;
7963        };
7964        if rest.is_empty()
7965            || !rest.iter().all(|candidate| {
7966                candidate.kind() == first.kind()
7967                    && candidate.fq_name() == first.fq_name()
7968                    && candidate.source() == first.source()
7969            })
7970        {
7971            return None;
7972        }
7973        candidates
7974            .iter()
7975            .copied()
7976            .find(|candidate| same_symbol(candidate, target))
7977            .or_else(|| {
7978                candidates.iter().copied().find(|candidate| {
7979                    self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7980                        .is_some_and(|resolved| same_visible_symbol(&resolved, target))
7981                })
7982            })
7983    }
7984
7985    pub fn alternate_same_fqn_type_declarations(
7986        &self,
7987        analyzer: &CppGraphSource<'_>,
7988        candidates: &[&CodeUnit],
7989        target: &CodeUnit,
7990    ) -> bool {
7991        let Some(first) = candidates.first() else {
7992            return false;
7993        };
7994        let same_api = first.kind() == target.kind()
7995            && first.fq_name() == target.fq_name()
7996            && first.source() == target.source()
7997            && candidates.iter().all(|candidate| {
7998                candidate.kind() == target.kind()
7999                    && candidate.fq_name() == target.fq_name()
8000                    && candidate.source() == target.source()
8001            })
8002            && candidates
8003                .iter()
8004                .any(|candidate| same_symbol(candidate, target))
8005            && candidates
8006                .iter()
8007                .any(|candidate| !same_logical_symbol(candidate, target));
8008        if !same_api {
8009            return false;
8010        }
8011
8012        let requirements = candidates
8013            .iter()
8014            .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
8015            .collect::<Vec<_>>();
8016        requirements.len() > 1
8017            && requirements
8018                .iter()
8019                .all(|requirement| !requirement.is_empty())
8020            && requirements.iter().enumerate().all(|(index, left)| {
8021                requirements[index + 1..].iter().all(|right| {
8022                    left.iter().all(|(_, left_guards)| {
8023                        right.iter().all(|(_, right_guards)| {
8024                            merge_preprocessor_guards(left_guards, right_guards).is_none()
8025                        })
8026                    })
8027                })
8028            })
8029    }
8030
8031    fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
8032        let mut pending = vec![terms.to_vec()];
8033        while let Some(branch_terms) = pending.pop() {
8034            let mut normalized = Vec::new();
8035            let mut covers_branch = false;
8036            for term in branch_terms {
8037                if term.iter().any(|guard| term.contains(&guard.negated())) {
8038                    continue;
8039                }
8040                if term.is_empty() {
8041                    covers_branch = true;
8042                    break;
8043                }
8044                if !normalized.iter().any(|existing| existing == &term) {
8045                    normalized.push(term);
8046                }
8047            }
8048            if covers_branch {
8049                continue;
8050            }
8051            let Some(split_guard) = normalized
8052                .iter()
8053                .flat_map(|term| term.iter())
8054                .next()
8055                .cloned()
8056            else {
8057                return false;
8058            };
8059            let negated_guard = split_guard.negated();
8060            let mut when_defined = Vec::new();
8061            let mut when_undefined = Vec::new();
8062            for term in normalized {
8063                if term.contains(&negated_guard) {
8064                    // This term cannot hold when `split_guard` is true.
8065                } else if term.contains(&split_guard) {
8066                    let mut reduced = term.clone();
8067                    reduced.remove(&split_guard);
8068                    when_defined.push(reduced);
8069                } else {
8070                    when_defined.push(term.clone());
8071                }
8072                if term.contains(&split_guard) {
8073                    // This term cannot hold when `split_guard` is false.
8074                } else if term.contains(&negated_guard) {
8075                    let mut reduced = term;
8076                    reduced.remove(&negated_guard);
8077                    when_undefined.push(reduced);
8078                } else {
8079                    when_undefined.push(term);
8080                }
8081            }
8082            pending.push(when_defined);
8083            pending.push(when_undefined);
8084        }
8085        true
8086    }
8087
8088    /// The byte range of the one `#if` family with a terminal `#else` that holds
8089    /// every physical declaration of every candidate, or `None` when they do not
8090    /// share one such family.
8091    ///
8092    /// Guard terms alone cannot distinguish one `#if` family from separate blocks
8093    /// whose macros changed between declarations. Require every physical range to
8094    /// belong to one syntax-tree family with a terminal `#else` before the terms
8095    /// can prove branch coverage.
8096    fn declarations_share_exhaustive_conditional_family(
8097        &self,
8098        analyzer: &CppGraphSource<'_>,
8099        candidates: &[&CodeUnit],
8100    ) -> Option<(usize, usize)> {
8101        let mut family_range = None;
8102        for candidate in candidates {
8103            let mut candidate_family = None;
8104            for occurrence in analyzer.declaration_source_occurrences(candidate)? {
8105                let key = occurrence.exhaustive_family?;
8106                if candidate_family.is_some_and(|existing| existing != key) {
8107                    return None;
8108                }
8109                candidate_family = Some(key);
8110            }
8111            let candidate_family = candidate_family?;
8112            if family_range.is_some_and(|existing| existing != candidate_family) {
8113                return None;
8114            }
8115            family_range = Some(candidate_family);
8116        }
8117        family_range
8118    }
8119
8120    pub fn complementary_same_fqn_type_declarations(
8121        &self,
8122        analyzer: &CppGraphSource<'_>,
8123        candidates: &[&CodeUnit],
8124        target: &CodeUnit,
8125    ) -> bool {
8126        if candidates.len() < 2
8127            || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
8128            || self
8129                .declarations_share_exhaustive_conditional_family(analyzer, candidates)
8130                .is_none()
8131        {
8132            return false;
8133        }
8134        Self::preprocessor_guard_terms_cover_all_paths(
8135            &self.declaration_family_guard_terms(analyzer, candidates),
8136        )
8137    }
8138
8139    fn declaration_family_guard_terms(
8140        &self,
8141        analyzer: &CppGraphSource<'_>,
8142        candidates: &[&CodeUnit],
8143    ) -> Vec<HashSet<PreprocessorGuard>> {
8144        candidates
8145            .iter()
8146            .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
8147            .map(|(_, guards)| guards)
8148            .collect()
8149    }
8150
8151    /// A callable name declared on every branch of one completed `#if`/`#else`
8152    /// family is declared on every configuration path, so a reference below the
8153    /// whole family sees one of the branches whatever the preprocessor decides.
8154    /// Answer the family's end byte: only past `#endif` is every branch's
8155    /// declaration behind the reference.
8156    ///
8157    /// This is the callable analogue of `complementary_same_fqn_type_declarations`
8158    /// and shares both of its primitives. It does not require two distinct
8159    /// `CodeUnit`s: branches that declare the same signature can collapse into
8160    /// one unit carrying one physical range per branch.
8161    ///
8162    /// The branches are alternate spellings of one declaration, never competing
8163    /// declarations, so only the first branch stands for the family. Reporting
8164    /// every branch as visible would turn a name the source declares exactly
8165    /// once into an ambiguity between build configurations.
8166    fn exhaustive_guard_family_activation(
8167        &self,
8168        analyzer: &CppGraphSource<'_>,
8169        candidate: &CodeUnit,
8170        reference: &CallableReferenceContext<'_>,
8171    ) -> Option<usize> {
8172        // Branch coverage says nothing about scope: a block-local declaration
8173        // stays invisible however many branches declare it.
8174        if !analyzer
8175            .declaration_source_occurrences(candidate)?
8176            .iter()
8177            .any(|occurrence| occurrence.fact.callable_activation.is_some())
8178        {
8179            return None;
8180        }
8181        let family = self
8182            .visible_identifier_candidates(reference.file, candidate.identifier())
8183            .filter(|peer| {
8184                peer.kind() == candidate.kind()
8185                    && peer.fq_name() == candidate.fq_name()
8186                    && peer.source() == candidate.source()
8187            })
8188            .collect::<Vec<_>>();
8189        let (_, family_end) =
8190            self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
8191        if !Self::preprocessor_guard_terms_cover_all_paths(
8192            &self.declaration_family_guard_terms(analyzer, &family),
8193        ) {
8194            return None;
8195        }
8196        // A reference whose effective context picks one branch already reaches
8197        // that branch through the ordinary same-guard path; the family must not
8198        // resurrect the branch the reference contradicts. The context includes
8199        // the translation unit's stable `__cplusplus` state and build-proven
8200        // defines, not only the reference's source guards: a C file below
8201        // `#ifdef __cplusplus ... #else ... #endif` takes the `#else` arm
8202        // however the family enumerates its branches.
8203        let effective = self.effective_reference_guards(reference.file, reference.guards());
8204        if !declaration_guard_requirements(analyzer, self.cpp, candidate)
8205            .iter()
8206            .any(|(_, guards)| guards_compatible_at_reference(guards, effective.as_ref()))
8207        {
8208            return None;
8209        }
8210        (first_declaration_byte(analyzer, candidate)?
8211            == family
8212                .iter()
8213                .filter_map(|peer| first_declaration_byte(analyzer, peer))
8214                .min()?)
8215        .then_some(family_end)
8216    }
8217
8218    fn type_candidate_preserving_target(
8219        &self,
8220        analyzer: &CppGraphSource<'_>,
8221        visible_from: &ProjectFile,
8222        candidate: &CodeUnit,
8223        target: &CodeUnit,
8224    ) -> Option<CodeUnit> {
8225        let mut current = candidate.clone();
8226        let mut matched_target = same_visible_symbol(&current, target)
8227            || self.compatible_primary_template_redeclarations(&current, target);
8228        let mut seen = HashSet::default();
8229        loop {
8230            if !seen.insert(current.clone()) {
8231                return None;
8232            }
8233            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
8234                return matched_target
8235                    .then(|| target.clone())
8236                    .or_else(|| current.is_class().then_some(current));
8237            };
8238            if self.flattened_macro_namespace_alias_target_matches(
8239                analyzer,
8240                visible_from,
8241                &current,
8242                &alias_target,
8243                target,
8244            ) {
8245                return Some(target.clone());
8246            }
8247            if matches!(alias_target, StructuredAliasTarget::Builtin) {
8248                return matched_target
8249                    .then(|| target.clone())
8250                    .or_else(|| current.is_class().then_some(current));
8251            }
8252            // A non-template alias can name a template alias with explicit
8253            // arguments (for example, `using Result = Expected<int>`).  When
8254            // the requested target is that alias's primary declaration, keep
8255            // the primary identity before expanding the RHS arguments.  The
8256            // expansion would otherwise canonicalize through the underlying
8257            // implementation type and lose the target spelling used by the
8258            // forward resolver.
8259            if !self.cpp_template_metadata.contains_key(&current)
8260                && let Some(primary) =
8261                    self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
8262                && (same_visible_symbol(&primary, target)
8263                    || self.compatible_primary_template_redeclarations(&primary, target))
8264            {
8265                return Some(target.clone());
8266            }
8267            if same_visible_symbol(&current, target) {
8268                return Some(target.clone());
8269            }
8270            if self.cpp_template_metadata.contains_key(&current) {
8271                return None;
8272            }
8273            let Some(next) =
8274                self.resolve_structured_alias_target(visible_from, &current, &alias_target)
8275            else {
8276                return matched_target.then(|| target.clone());
8277            };
8278            current = next;
8279            matched_target |= same_visible_symbol(&current, target)
8280                || self.compatible_primary_template_redeclarations(&current, target);
8281        }
8282    }
8283
8284    fn compatible_primary_template_redeclarations(
8285        &self,
8286        left: &CodeUnit,
8287        right: &CodeUnit,
8288    ) -> bool {
8289        let (Some(left_metadata), Some(right_metadata)) = (
8290            self.cpp_template_metadata.get(left),
8291            self.cpp_template_metadata.get(right),
8292        ) else {
8293            return false;
8294        };
8295        left_metadata.primary_fq_name == right_metadata.primary_fq_name
8296            && left_metadata.is_primary()
8297            && right_metadata.is_primary()
8298            && cpp_reconcile_primary_template_parameters(
8299                &[(left, left_metadata), (right, right_metadata)],
8300                right,
8301            )
8302            .is_some()
8303    }
8304
8305    fn alias_candidate_may_preserve_target(
8306        &self,
8307        analyzer: &CppGraphSource<'_>,
8308        visible_from: &ProjectFile,
8309        candidate: &CodeUnit,
8310        target: &CodeUnit,
8311    ) -> bool {
8312        let mut current = candidate.clone();
8313        let mut seen = HashSet::default();
8314        loop {
8315            if same_visible_symbol(&current, target)
8316                || self.compatible_primary_template_redeclarations(&current, target)
8317            {
8318                return true;
8319            }
8320            if self.cpp_template_metadata.contains_key(&current) {
8321                return true;
8322            }
8323            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
8324                return false;
8325            };
8326            let StructuredAliasTarget::Named {
8327                components,
8328                global,
8329                arguments,
8330            } = alias_target
8331            else {
8332                return false;
8333            };
8334            if arguments.is_some() || !seen.insert(current.clone()) {
8335                return true;
8336            }
8337            let qualified = components.join("::");
8338            let next = if global {
8339                unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
8340            } else {
8341                self.resolve_unique_type_for_declaration(visible_from, &current, &qualified)
8342            };
8343            let Some(next) = next else {
8344                return true;
8345            };
8346            current = next;
8347        }
8348    }
8349
8350    fn alias_candidate_structurally_reaches_target(
8351        &self,
8352        analyzer: &CppGraphSource<'_>,
8353        visible_from: &ProjectFile,
8354        candidate: &CodeUnit,
8355        target: &CodeUnit,
8356    ) -> bool {
8357        let mut current = candidate.clone();
8358        let mut seen = HashSet::default();
8359        loop {
8360            if same_visible_symbol(&current, target)
8361                || self.compatible_primary_template_redeclarations(&current, target)
8362                || self
8363                    .cpp_template_metadata
8364                    .get(&current)
8365                    .zip(self.cpp_template_metadata.get(target))
8366                    .is_some_and(|(current, target)| {
8367                        current.primary_fq_name == target.primary_fq_name
8368                    })
8369            {
8370                return true;
8371            }
8372            if !seen.insert(current.clone()) {
8373                return false;
8374            }
8375            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
8376                return false;
8377            };
8378            if let StructuredAliasTarget::Named { components, .. } = &alias_target
8379                && components[..components.len().saturating_sub(1)]
8380                    .iter()
8381                    .any(|component| {
8382                        self.type_reference_component_directly_names_target(component, target)
8383                    })
8384                && self.structured_class_alias_path_preserves_target(
8385                    analyzer,
8386                    visible_from,
8387                    &current,
8388                    target,
8389                )
8390            {
8391                return true;
8392            }
8393            if matches!(alias_target, StructuredAliasTarget::Builtin) {
8394                return false;
8395            }
8396            let Some(primary) =
8397                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
8398            else {
8399                return false;
8400            };
8401            current = primary;
8402        }
8403    }
8404
8405    /// Whether one indexed alias component in a qualified type reference has
8406    /// a structured path to `target`.
8407    ///
8408    /// Alias terminal names such as `type` are deliberately common. A
8409    /// file-wide name set cannot distinguish `Gen<T>::type`, whose indexed RHS
8410    /// names the target, from thousands of unrelated `Traits<T>::type`
8411    /// references. Match every qualified prefix against the alias's canonical
8412    /// scope before following its structured RHS. An unresolved dependent RHS
8413    /// fails this coarse gate only when no other component names the target;
8414    /// references such as `Identity<Target>::type` are admitted by that target
8415    /// component before this helper is called.
8416    pub fn qualified_alias_reference_may_reach_target(
8417        &self,
8418        analyzer: &CppGraphSource<'_>,
8419        file: &ProjectFile,
8420        components: &[String],
8421        global: bool,
8422        target: &CodeUnit,
8423    ) -> bool {
8424        for end in 0..components.len() {
8425            let spelled = &components[..=end];
8426            for candidate in self
8427                .visible_identifier_candidates(file, &components[end])
8428                .filter(|candidate| declared_type_alias(analyzer, candidate))
8429            {
8430                let candidate_components = canonical_cpp_scope_components(candidate);
8431                let shape_matches = if global {
8432                    candidate_components == spelled
8433                } else {
8434                    candidate_components.ends_with(spelled)
8435                };
8436                if shape_matches
8437                    && self.alias_candidate_structurally_reaches_target(
8438                        analyzer, file, candidate, target,
8439                    )
8440                {
8441                    return true;
8442                }
8443            }
8444        }
8445        false
8446    }
8447
8448    /// Every indexed type declaration `raw_name` names when it is written in
8449    /// `declaration`'s namespace: the innermost enclosing namespace that holds
8450    /// the name wins, otherwise the name is looked up unqualified.
8451    fn type_candidates_for_declaration<'b>(
8452        &'b self,
8453        visible_from: &ProjectFile,
8454        declaration: &CodeUnit,
8455        raw_name: &str,
8456    ) -> Vec<&'b CodeUnit> {
8457        let Some(normalized) = normalize_reference_name(raw_name) else {
8458            return Vec::new();
8459        };
8460        if let Some(namespace) = cpp_namespace_for(declaration) {
8461            for prefix in namespace_prefixes(&namespace) {
8462                let qualified = format!("{prefix}::{normalized}");
8463                let candidates = self.type_candidates(visible_from, &qualified);
8464                if !candidates.is_empty() {
8465                    return candidates;
8466                }
8467            }
8468        }
8469        self.type_candidates(visible_from, &normalized)
8470    }
8471
8472    fn resolve_unique_type_for_declaration(
8473        &self,
8474        visible_from: &ProjectFile,
8475        declaration: &CodeUnit,
8476        raw_name: &str,
8477    ) -> Option<CodeUnit> {
8478        unique_logical_type_candidate(self.type_candidates_for_declaration(
8479            visible_from,
8480            declaration,
8481            raw_name,
8482        ))
8483    }
8484
8485    /// The type `raw_name` names at `declaration`, answered with the
8486    /// declaration that defines it.
8487    ///
8488    /// C spells one type twice. `typedef struct T T;` in the header that
8489    /// publishes the handle indexes an incomplete `struct T` beside the
8490    /// complete `struct T { ... }` in the header that declares the members, and
8491    /// both carry the same kind and fully qualified name, so
8492    /// [`logical_type_candidate`] is free to answer with either. mbedtls writes
8493    /// exactly that: `include/mbedtls/ssl.h` forward-declares
8494    /// `mbedtls_ssl_handshake_params` for the pointer member
8495    /// `mbedtls_ssl_handshake_params *MBEDTLS_PRIVATE(handshake)` while
8496    /// `library/ssl_misc.h` defines its fields. A receiver typed from a field
8497    /// declaration is asked for its members on the next link of the chain, and
8498    /// only the definition owns them (#2982).
8499    ///
8500    /// Competing definitions leave nothing to choose between, so keep the
8501    /// logical answer rather than picking one of them.
8502    fn resolve_defining_type_for_declaration(
8503        &self,
8504        analyzer: &CppGraphSource<'_>,
8505        visible_from: &ProjectFile,
8506        declaration: &CodeUnit,
8507        raw_name: &str,
8508    ) -> Option<CodeUnit> {
8509        let candidates = self.type_candidates_for_declaration(visible_from, declaration, raw_name);
8510        let logical = unique_logical_type_candidate(candidates.clone())?;
8511        let mut defining = candidates.into_iter().filter(|candidate| {
8512            candidate.is_class()
8513                && cpp_class_declaration_strength(analyzer, candidate)
8514                    == CppClassDeclarationStrength::Full
8515        });
8516        match (defining.next(), defining.next()) {
8517            (Some(unique_definition), None) => Some(unique_definition.clone()),
8518            _ => Some(logical),
8519        }
8520    }
8521
8522    pub fn resolves_to_type(
8523        &self,
8524        analyzer: &CppGraphSource<'_>,
8525        file: &ProjectFile,
8526        raw_name: &str,
8527        target: &CodeUnit,
8528    ) -> bool {
8529        let Some(normalized) = normalize_reference_name(raw_name) else {
8530            return false;
8531        };
8532        let candidates = self.type_candidates(file, &normalized);
8533        if candidates.is_empty() {
8534            return self.parser_alias_resolves_to_type(file, raw_name, target);
8535        }
8536        let Some(resolved) =
8537            self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
8538        else {
8539            return false;
8540        };
8541        same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
8542    }
8543
8544    pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
8545        let mut current = alias.clone();
8546        let mut visited = HashSet::default();
8547        while visited.insert(current.clone()) {
8548            let raw_target = cpp_canonical_alias_target_text(&self.cpp_source(), &current)?;
8549            let resolved =
8550                self.resolve_type_for_declaration(current.source(), &current, &raw_target)?;
8551            if resolved.is_class() {
8552                return Some(resolved);
8553            }
8554            if !declared_type_alias(&self.cpp_source(), &resolved) {
8555                return None;
8556            }
8557            current = resolved;
8558        }
8559        None
8560    }
8561
8562    /// Whether two callable declarations declare one function.
8563    ///
8564    /// [`same_logical_symbol`] compares the persisted signature strings, which
8565    /// embed each parameter type exactly as it was spelled. A header
8566    /// declaration written inside `namespace zmq { class dist_t { ... } }` says
8567    /// `send_to_matching(msg_t *)` while its out-of-line body at file scope
8568    /// says `zmq::msg_t *`, so the string comparison reports two symbols where
8569    /// C++ ([basic.def], [dcl.fct]) sees one declaration and one definition.
8570    /// This resolves the written parameter names before comparing them and
8571    /// reports the same answer the language does for the cases it can prove.
8572    ///
8573    /// Everything it cannot prove stays two symbols: a template declaration, a
8574    /// parameter with no comparable shape, a name that resolves on one side
8575    /// only, and an alias chain it cannot follow safely (#2010).
8576    pub fn same_logical_callable(
8577        &self,
8578        analyzer: &CppGraphSource<'_>,
8579        left: &CodeUnit,
8580        right: &CodeUnit,
8581    ) -> bool {
8582        if same_logical_symbol(left, right) {
8583            return true;
8584        }
8585        if left.kind() != right.kind()
8586            || !left.is_callable()
8587            || !right.is_callable()
8588            || left.fq_name() != right.fq_name()
8589        {
8590            return false;
8591        }
8592        let (Some(left_comparable), Some(right_comparable)) = (
8593            self.callable_comparable(analyzer, left),
8594            self.callable_comparable(analyzer, right),
8595        ) else {
8596            return false;
8597        };
8598        // A template declaration and its out-of-line body can also diverge
8599        // outside the parameter list - `template <class T>` against
8600        // `template <typename U>` - and the template head is part of the
8601        // persisted signature. The structured template levels decide whether
8602        // the heads and owner arguments agree, and bind each side's parameter
8603        // names for the parameter comparison below. A template whose levels
8604        // cannot be read keeps string identity.
8605        let binding = match (&left_comparable.template, &right_comparable.template) {
8606            (Some(left_template), Some(right_template))
8607                if left_template.is_templated() || right_template.is_templated() =>
8608            {
8609                match left_template.reconcile(right_template) {
8610                    CppTemplateReconciliation::Equivalent(binding) => binding,
8611                    CppTemplateReconciliation::Distinct
8612                    | CppTemplateReconciliation::Unsupported => return false,
8613                }
8614            }
8615            (Some(_), Some(_)) => CppTemplateBinding::default(),
8616            _ if self.callable_is_template_declaration(analyzer, left)
8617                || self.callable_is_template_declaration(analyzer, right) =>
8618            {
8619                return false;
8620            }
8621            _ => CppTemplateBinding::default(),
8622        };
8623        // The trailing member `const`, ref-qualifier, `noexcept`, trailing
8624        // return type and requires-clause are part of C++ callable identity and
8625        // an out-of-line definition repeats them verbatim, so they must agree
8626        // as written.
8627        if left_comparable.suffix != right_comparable.suffix
8628            || left_comparable.shapes.len() != right_comparable.shapes.len()
8629        {
8630            return false;
8631        }
8632        left_comparable
8633            .shapes
8634            .iter()
8635            .zip(right_comparable.shapes.iter())
8636            .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
8637                (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
8638                (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
8639                    self.comparable_shapes_agree(analyzer, &binding, left_shape, right_shape)
8640                }
8641                // An unstructured parameter records that the reduction failed,
8642                // not that the two spellings mean the same type, so it agrees
8643                // with nothing - including another unstructured parameter.
8644                _ => false,
8645            })
8646    }
8647
8648    /// Whether two complete class definitions in different files define one
8649    /// class.
8650    ///
8651    /// The one-definition rule makes every definition of a class with linkage
8652    /// the same entity, and it requires those definitions to agree token for
8653    /// token ([basic.def.odr]). A shared qualified name is only the spelling of
8654    /// that claim, so it is never enough. The evidence is structural: both
8655    /// definitions are complete, their template levels reconcile (heads, and
8656    /// the arguments that select a specialization), their base specifiers
8657    /// agree, and they declare the same members -- the same kinds, names and
8658    /// signatures, which embed each member's template head as written. Two
8659    /// classes that share a name but differ in any of these stay two classes.
8660    pub fn same_logical_class_definition(
8661        &self,
8662        analyzer: &CppGraphSource<'_>,
8663        left: &CodeUnit,
8664        right: &CodeUnit,
8665    ) -> bool {
8666        if same_symbol(left, right) {
8667            return true;
8668        }
8669        if !left.is_class() || !right.is_class() || left.fq_name() != right.fq_name() {
8670            return false;
8671        }
8672        let key = if left <= right {
8673            (left.clone(), right.clone())
8674        } else {
8675            (right.clone(), left.clone())
8676        };
8677        if let Some(cached) = self
8678            .class_definition_agreements
8679            .lock()
8680            .expect("C++ class definition agreement cache poisoned")
8681            .get(&key)
8682        {
8683            return *cached;
8684        }
8685        let agree = self.class_definitions_agree(analyzer, left, right);
8686        self.class_definition_agreements
8687            .lock()
8688            .expect("C++ class definition agreement cache poisoned")
8689            .insert(key, agree);
8690        agree
8691    }
8692
8693    fn class_definitions_agree(
8694        &self,
8695        analyzer: &CppGraphSource<'_>,
8696        left: &CodeUnit,
8697        right: &CodeUnit,
8698    ) -> bool {
8699        let complete_definition = |class: &CodeUnit| {
8700            let mut facts = analyzer
8701                .declaration_source_properties(class)?
8702                .into_iter()
8703                .filter(|fact| fact.class_strength == CppClassDeclarationStrength::Full);
8704            let fact = facts.next()?;
8705            // Two complete definitions in one file are a conditional family
8706            // this comparison does not choose between.
8707            facts.next().is_none().then_some(fact)
8708        };
8709        let (Some(left_fact), Some(right_fact)) =
8710            (complete_definition(left), complete_definition(right))
8711        else {
8712            return false;
8713        };
8714        if left_fact.bases != right_fact.bases {
8715            return false;
8716        }
8717        let (Some(left_template), Some(right_template)) = (
8718            self.declaration_template_context(left, left_fact.declaration),
8719            self.declaration_template_context(right, right_fact.declaration),
8720        ) else {
8721            return false;
8722        };
8723        if !matches!(
8724            left_template.reconcile(&right_template),
8725            CppTemplateReconciliation::Equivalent(_)
8726        ) {
8727            return false;
8728        }
8729        let members = |class: &CodeUnit| {
8730            let mut members = analyzer
8731                .direct_children(class)
8732                .into_iter()
8733                .filter(|member| member.source() == class.source())
8734                .map(|member| {
8735                    (
8736                        member.kind(),
8737                        member.identifier().to_owned(),
8738                        member.signature().map(str::to_owned),
8739                    )
8740                })
8741                .collect::<Vec<_>>();
8742            members.sort();
8743            members
8744        };
8745        members(left) == members(right)
8746    }
8747
8748    /// Whether `file` sees a definition of `class` through an identical
8749    /// definition in another header.
8750    ///
8751    /// The include graph relates a header to the translation units that
8752    /// include it. When a class is defined identically in two headers, a
8753    /// translation unit that includes only one copy still defines members of
8754    /// the class the other copy declares: the two definitions are one class
8755    /// ([`Self::same_logical_class_definition`]). Each source declaration
8756    /// keeps its own identity; only the relation crosses.
8757    pub fn class_definition_bridges_to_file(
8758        &self,
8759        analyzer: &CppGraphSource<'_>,
8760        class: &CodeUnit,
8761        file: &ProjectFile,
8762        header_body_related: &dyn Fn(&ProjectFile, &ProjectFile) -> bool,
8763    ) -> bool {
8764        class.is_class()
8765            && class.source() != file
8766            && crate::identity::cpp_source_path_is_header(class.source())
8767            && analyzer
8768                .index
8769                .definitions_by_structured_name(class.fq(), Language::Cpp)
8770                .into_iter()
8771                .any(|copy| {
8772                    // The copy's own header holds the copy's declarations,
8773                    // which keep their own provenance; only files beyond it
8774                    // are reached through the copy.
8775                    copy.is_class()
8776                        && copy.source() != class.source()
8777                        && copy.source() != file
8778                        && crate::identity::cpp_source_path_is_header(copy.source())
8779                        && header_body_related(copy.source(), file)
8780                        && self.same_logical_class_definition(analyzer, class, &copy)
8781                })
8782    }
8783
8784    /// Whether the header member declaration `declaration` reaches `body`
8785    /// through an identical definition of its class
8786    /// ([`Self::class_definition_bridges_to_file`]).
8787    pub fn member_declaration_bridges_to_body(
8788        &self,
8789        analyzer: &CppGraphSource<'_>,
8790        declaration: &CodeUnit,
8791        body: &CodeUnit,
8792        header_body_related: &dyn Fn(&ProjectFile, &ProjectFile) -> bool,
8793    ) -> bool {
8794        declaration.is_callable()
8795            && self
8796                .cpp
8797                .structural_parent_of(declaration)
8798                .is_some_and(|owner| {
8799                    self.class_definition_bridges_to_file(
8800                        analyzer,
8801                        &owner,
8802                        body.source(),
8803                        header_body_related,
8804                    )
8805                })
8806    }
8807
8808    /// Compare two parameter shapes node by node with an explicit paired stack.
8809    ///
8810    /// Shape variants and cv-qualifiers must agree exactly at every level; only
8811    /// the named leaves may be spelled differently, and they agree when they
8812    /// resolve to one type declaration.
8813    fn comparable_shapes_agree(
8814        &self,
8815        analyzer: &CppGraphSource<'_>,
8816        binding: &CppTemplateBinding,
8817        left: &CppComparableParameter,
8818        right: &CppComparableParameter,
8819    ) -> bool {
8820        let mut stack = vec![(left.root(), right.root())];
8821        while let Some((left_index, right_index)) = stack.pop() {
8822            match (left.node(left_index), right.node(right_index)) {
8823                (
8824                    CppComparableNode::Named {
8825                        name: left_name,
8826                        primitive: left_primitive,
8827                        konst: left_konst,
8828                        volatil: left_volatil,
8829                    },
8830                    CppComparableNode::Named {
8831                        name: right_name,
8832                        primitive: right_primitive,
8833                        konst: right_konst,
8834                        volatil: right_volatil,
8835                    },
8836                ) => {
8837                    if left_konst != right_konst
8838                        || left_volatil != right_volatil
8839                        || left_primitive != right_primitive
8840                        || !self.comparable_names_agree(
8841                            analyzer,
8842                            binding,
8843                            left_name,
8844                            right_name,
8845                            *left_primitive,
8846                        )
8847                    {
8848                        return false;
8849                    }
8850                }
8851                (
8852                    CppComparableNode::Pointer {
8853                        inner: left_inner,
8854                        konst: left_konst,
8855                        volatil: left_volatil,
8856                    },
8857                    CppComparableNode::Pointer {
8858                        inner: right_inner,
8859                        konst: right_konst,
8860                        volatil: right_volatil,
8861                    },
8862                ) => {
8863                    if left_konst != right_konst || left_volatil != right_volatil {
8864                        return false;
8865                    }
8866                    stack.push((*left_inner, *right_inner));
8867                }
8868                (
8869                    CppComparableNode::Reference { inner: left_inner },
8870                    CppComparableNode::Reference { inner: right_inner },
8871                )
8872                | (
8873                    CppComparableNode::Array { inner: left_inner },
8874                    CppComparableNode::Array { inner: right_inner },
8875                ) => stack.push((*left_inner, *right_inner)),
8876                (
8877                    CppComparableNode::Generic {
8878                        base: left_base,
8879                        arguments: left_arguments,
8880                    },
8881                    CppComparableNode::Generic {
8882                        base: right_base,
8883                        arguments: right_arguments,
8884                    },
8885                ) => {
8886                    if left_arguments.len() != right_arguments.len() {
8887                        return false;
8888                    }
8889                    stack.push((*left_base, *right_base));
8890                    stack.extend(
8891                        left_arguments.iter().zip(right_arguments.iter()).map(
8892                            |(left_argument, right_argument)| (*left_argument, *right_argument),
8893                        ),
8894                    );
8895                }
8896                _ => return false,
8897            }
8898        }
8899        true
8900    }
8901
8902    /// Whether two written type names denote one type.
8903    ///
8904    /// A primitive denotes the same type in every scope, so its recorded
8905    /// lexical scope is noise and its spelling decides. A nominal name is
8906    /// resolved on each side independently: two resolved names agree when they
8907    /// reach one type declaration, and two unresolved names agree only on
8908    /// exact agreement of what was written, which is no weaker than the
8909    /// whole-signature string equality this comparison replaces. Resolution on
8910    /// one side only is evidence of difference, never of agreement.
8911    ///
8912    /// A template parameter hides every outer name it spells, so a path that
8913    /// starts with one is decided by `binding` alone: both sides must start
8914    /// with the parameters the reconciled heads pair, and agree on the rest
8915    /// of the path (`typename T::value_type`).
8916    fn comparable_names_agree(
8917        &self,
8918        analyzer: &CppGraphSource<'_>,
8919        binding: &CppTemplateBinding,
8920        left: &StructuredTypeName,
8921        right: &StructuredTypeName,
8922        primitive: bool,
8923    ) -> bool {
8924        if primitive {
8925            return left.path() == right.path();
8926        }
8927        if !binding.is_empty() {
8928            let (Some(left_head), Some(right_head)) = (left.path().first(), right.path().first())
8929            else {
8930                return false;
8931            };
8932            match (
8933                binding.is_left_parameter(left_head),
8934                binding.is_right_parameter(right_head),
8935            ) {
8936                (true, true) => {
8937                    return !left.is_absolute()
8938                        && !right.is_absolute()
8939                        && binding.binds(right_head, left_head)
8940                        && left.path()[1..] == right.path()[1..];
8941                }
8942                (false, false) => {}
8943                _ => return false,
8944            }
8945        }
8946        match (
8947            self.comparable_name_terminal(analyzer, left),
8948            self.comparable_name_terminal(analyzer, right),
8949        ) {
8950            (Some(left_terminal), Some(right_terminal)) => {
8951                same_logical_symbol(&left_terminal, &right_terminal)
8952            }
8953            (None, None) => {
8954                left.path() == right.path() && left.is_absolute() == right.is_absolute()
8955            }
8956            _ => false,
8957        }
8958    }
8959
8960    /// The class declaration a written type name denotes, or `None` when the
8961    /// workspace cannot prove one.
8962    ///
8963    /// The lookup is a closure-independent lexical-scope prefix walk over the
8964    /// stored C++ identifier index rather than a visibility lookup: a body's
8965    /// `.cpp` is almost never in the reference file's include closure. Any name
8966    /// this walk resolves is one an enclosing-scope lookup could resolve, so it
8967    /// cannot invent a type the compiler could not see; `using`-directives are
8968    /// not modelled, and a name that needs one stays unresolved.
8969    fn comparable_name_terminal(
8970        &self,
8971        analyzer: &CppGraphSource<'_>,
8972        name: &StructuredTypeName,
8973    ) -> Option<CodeUnit> {
8974        let mut current = self.comparable_name_declaration(analyzer, name)?;
8975        let mut visited = HashSet::default();
8976        for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
8977            // The alias question is asked before the class question, and
8978            // through `declared_type_alias` rather than `is_type_alias`,
8979            // because extraction records `using A8 = A7;` as a *Class* unit
8980            // whose signature is the alias declaration. Reading the kind first
8981            // would end the chase on the alias itself and report an alias
8982            // spelling and its underlying class as two types (#2010).
8983            if !declared_type_alias(analyzer, &current) {
8984                return current.is_class().then_some(current);
8985            }
8986            if !visited.insert(current.clone()) {
8987                return None;
8988            }
8989            let facts = analyzer.declaration_source_properties(&current)?;
8990            if facts.iter().any(|fact| fact.adds_indirection) {
8991                return None;
8992            }
8993            let raw_target = cpp_canonical_alias_target_text(analyzer, &current)?;
8994            current = self.comparable_alias_target(analyzer, &current, &raw_target)?;
8995        }
8996        None
8997    }
8998
8999    /// The declaration one alias hop lands on: the type `raw_target` names,
9000    /// looked up from the alias declaration's own enclosing namespace.
9001    ///
9002    /// The hop takes the same closure-independent prefix walk the first lookup
9003    /// took, and deliberately not `resolve_type_for_declaration`: that one
9004    /// answers out of the `VisibilityIndex`, which is rooted at the reference
9005    /// file, while the alias declaration this hop starts from is reached
9006    /// through the workspace definition index and its file need not be in that
9007    /// root's include closure - where the visibility lookup answers nothing and
9008    /// the chase would stop on the alias itself (#2010).
9009    fn comparable_alias_target(
9010        &self,
9011        analyzer: &CppGraphSource<'_>,
9012        alias: &CodeUnit,
9013        raw_target: &str,
9014    ) -> Option<CodeUnit> {
9015        // `raw_target` is the alias declaration's written type text, so it is a
9016        // plain `::`-joined qualified-id: the same domain the shared symbol-path
9017        // parser reads, and the same leading `::` that marks an absolute name
9018        // everywhere else this crate normalizes a reference.
9019        let absolute = raw_target.trim_start().starts_with("::");
9020        let normalized = normalize_reference_name(raw_target)?;
9021        let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9022            brokk_bifrost_core::analyzer::Language::Cpp,
9023            &normalized,
9024        );
9025        let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
9026            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9027                brokk_bifrost_core::analyzer::Language::Cpp,
9028                &namespace,
9029            )
9030        });
9031        let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
9032        self.comparable_name_declaration(analyzer, &name)
9033    }
9034
9035    /// The one type declaration `name` names, by enclosing scope, innermost
9036    /// first.
9037    ///
9038    /// The first prefix depth that names anything decides: an inner scope hides
9039    /// an outer one, so a match there is the answer even when an outer scope
9040    /// also declares the name. Several logically distinct declarations at that
9041    /// depth are an ambiguity this comparison must not guess at.
9042    fn comparable_name_declaration(
9043        &self,
9044        analyzer: &CppGraphSource<'_>,
9045        name: &StructuredTypeName,
9046    ) -> Option<CodeUnit> {
9047        // The same parameter-name pair is commonly compared once for every
9048        // physical declaration in a candidate set. Keep the index lookup under
9049        // the cache lock so concurrent workers cannot all miss the same key
9050        // and repeat the workspace candidate filter. `None` is a meaningful
9051        // result too: unresolved and ambiguous names must remain unresolved on
9052        // every later comparison.
9053        let mut cache = self
9054            .comparable_name_declarations
9055            .lock()
9056            .expect("C++ comparable name declaration cache poisoned");
9057        if let Some(cached) = cache.get(name) {
9058            return cached.clone();
9059        }
9060        let interner = segment_interner();
9061        let identifier = name.path().last()?;
9062        let candidates_by_identifier = self.cpp.visibility_identifier_candidates(identifier);
9063        let first_depth = if name.is_absolute() {
9064            0
9065        } else {
9066            name.lexical_scope().len()
9067        };
9068        let mut resolved = None;
9069        for depth in (0..=first_depth).rev() {
9070            let mut structured = FqName::new();
9071            for component in name.lexical_scope()[..depth].iter().chain(name.path()) {
9072                structured.push(interner.intern(component, SegmentKind::Unknown));
9073            }
9074            let mut candidates = candidates_by_identifier
9075                .iter()
9076                .filter(|unit| unit.fq().same_segment_texts(&structured))
9077                .filter(|unit| {
9078                    unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
9079                })
9080                .cloned();
9081            let Some(first) = candidates.next() else {
9082                continue;
9083            };
9084            resolved = candidates
9085                .all(|unit| same_logical_symbol(&unit, &first))
9086                .then_some(first);
9087            break;
9088        }
9089        cache.insert(name.clone(), resolved.clone());
9090        resolved
9091    }
9092
9093    /// The comparison inputs of one callable declaration, extracted once.
9094    ///
9095    /// The comparison itself runs only when two candidates share kind and fully
9096    /// qualified name but not signature, which is rare; re-reading the same
9097    /// declaration for every pair in a candidate set is not.
9098    fn callable_comparable(
9099        &self,
9100        analyzer: &CppGraphSource<'_>,
9101        unit: &CodeUnit,
9102    ) -> Option<Arc<ExtractedComparable>> {
9103        if let Some(cached) = self
9104            .callable_comparables
9105            .lock()
9106            .expect("C++ callable comparable cache poisoned")
9107            .get(unit)
9108            .cloned()
9109        {
9110            return cached;
9111        }
9112        let extracted = self
9113            .extract_callable_comparable(analyzer, unit)
9114            .map(Arc::new);
9115        self.callable_comparables
9116            .lock()
9117            .expect("C++ callable comparable cache poisoned")
9118            .insert(unit.clone(), extracted.clone());
9119        extracted
9120    }
9121
9122    fn extract_callable_comparable(
9123        &self,
9124        analyzer: &CppGraphSource<'_>,
9125        unit: &CodeUnit,
9126    ) -> Option<ExtractedComparable> {
9127        let fact = analyzer
9128            .declaration_source_properties(unit)?
9129            .into_iter()
9130            .find(|fact| fact.callable_comparable_shapes.is_some())?;
9131        let template = self.declaration_template_context(unit, fact.declaration);
9132        Some(ExtractedComparable {
9133            shapes: fact.callable_comparable_shapes?,
9134            suffix: fact.callable_identity_suffix?,
9135            template,
9136        })
9137    }
9138
9139    /// The template levels of one source declaration of `unit`, read from
9140    /// the file's prepared syntax at the declaration's exact occurrence.
9141    pub fn declaration_template_context(
9142        &self,
9143        unit: &CodeUnit,
9144        declaration: SourceDeclarationId,
9145    ) -> Option<CppTemplateContext> {
9146        let facts = self
9147            .cpp
9148            .declaration_source_facts(self.token, unit.source())?;
9149        let occurrence = facts.source.declaration(declaration).occurrence;
9150        let range = facts.source.occurrence(occurrence).range;
9151        let syntax = self.cpp.prepared_syntax(self.token, unit.source())?;
9152        let mut node = node_for_exact_range(syntax.tree().root_node(), &range)?;
9153        // An occurrence may span the template wrapper; the context starts at
9154        // the declaration it wraps.
9155        while node.kind() == "template_declaration" {
9156            node = node.named_child(node.named_child_count().checked_sub(1)?)?;
9157        }
9158        cpp_declaration_template_context(node, syntax.source())
9159    }
9160
9161    pub fn canonical_type_for_reference(
9162        &self,
9163        file: &ProjectFile,
9164        raw_name: &str,
9165    ) -> Option<CodeUnit> {
9166        let resolved = self.resolve_type(file, raw_name)?;
9167        self.alias_target(&resolved).or(Some(resolved))
9168    }
9169
9170    pub fn parser_alias_resolves_to_type(
9171        &self,
9172        file: &ProjectFile,
9173        raw_name: &str,
9174        target: &CodeUnit,
9175    ) -> bool {
9176        let Some(alias_name) = normalize_reference_name(raw_name) else {
9177            return false;
9178        };
9179        any_visible_source_file_matches(&self.visible_source_files_by_root, file, |source_file| {
9180            self.file_alias_matches(self.cpp, source_file, &alias_name, target)
9181        })
9182    }
9183
9184    fn file_alias_matches(
9185        &self,
9186        cpp: &dyn CppSource,
9187        file: &ProjectFile,
9188        alias_name: &str,
9189        target: &CodeUnit,
9190    ) -> bool {
9191        self.canonical_alias_cell(cpp, file).is_some_and(|cell| {
9192            cell.get()
9193                .expect("published canonical aliases")
9194                .iter()
9195                .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
9196        })
9197    }
9198
9199    fn canonical_alias_cell(
9200        &self,
9201        cpp: &dyn CppSource,
9202        file: &ProjectFile,
9203    ) -> Option<Arc<OnceLock<Box<[CppAlias]>>>> {
9204        let cell = {
9205            let mut cells = self.alias_cells.lock().expect("alias cell map lock");
9206            Arc::clone(
9207                cells
9208                    .entry(file.clone())
9209                    .or_insert_with(|| Arc::new(OnceLock::new())),
9210            )
9211        };
9212        if cell.get().is_none() {
9213            let aliases = aliases_from_canonical_source(cpp, self.token, file)?;
9214            cell.get_or_init(|| {
9215                self.parser_alias_source_parses
9216                    .fetch_add(1, Ordering::Relaxed);
9217                #[cfg(any(test, feature = "test-support"))]
9218                {
9219                    *self
9220                        .alias_source_parse_counts
9221                        .lock()
9222                        .expect("alias source parse count lock")
9223                        .entry(file.clone())
9224                        .or_default() += 1;
9225                }
9226                aliases.into_boxed_slice()
9227            });
9228        }
9229        Some(cell)
9230    }
9231
9232    #[cfg(any(test, feature = "test-support"))]
9233    pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
9234        self.visible_source_files_by_root
9235            .get(file)
9236            .cloned()
9237            .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
9238    }
9239
9240    #[cfg(any(test, feature = "test-support"))]
9241    pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
9242        self.alias_source_parse_counts
9243            .lock()
9244            .expect("alias source parse count lock")
9245            .get(file)
9246            .copied()
9247            .unwrap_or(0)
9248    }
9249
9250    #[cfg(any(test, feature = "test-support"))]
9251    pub fn parser_alias_fallback_file_count_for_test(&self) -> usize {
9252        self.parser_alias_fallback_files.load(Ordering::Relaxed)
9253    }
9254
9255    pub fn resolve_named(
9256        &self,
9257        file: &ProjectFile,
9258        raw_name: &str,
9259        kind: TargetKind,
9260    ) -> Option<CodeUnit> {
9261        let normalized = normalize_reference_name(raw_name)?;
9262        self.named_candidates_for_normalized(file, &normalized, kind)
9263            .into_iter()
9264            .next()
9265            .cloned()
9266    }
9267
9268    pub fn contains_named_symbol(
9269        &self,
9270        file: &ProjectFile,
9271        raw_name: &str,
9272        kind: TargetKind,
9273        target: &CodeUnit,
9274    ) -> bool {
9275        let Some(normalized) = normalize_reference_name(raw_name) else {
9276            return false;
9277        };
9278        self.named_candidates_for_normalized(file, &normalized, kind)
9279            .into_iter()
9280            .any(|unit| {
9281                matches_kind_for_lookup(unit, kind)
9282                    && reference_matches_unit(&normalized, unit)
9283                    && same_visible_symbol(unit, target)
9284            })
9285    }
9286
9287    pub fn named_candidates(
9288        &self,
9289        file: &ProjectFile,
9290        raw_name: &str,
9291        kind: TargetKind,
9292    ) -> Vec<CodeUnit> {
9293        let Some(normalized) = normalize_reference_name(raw_name) else {
9294            return Vec::new();
9295        };
9296        self.named_candidates_for_normalized(file, &normalized, kind)
9297            .into_iter()
9298            .cloned()
9299            .collect()
9300    }
9301
9302    pub fn resolve_known_non_target(
9303        &self,
9304        file: &ProjectFile,
9305        raw_name: &str,
9306        kind: TargetKind,
9307        target: &CodeUnit,
9308    ) -> bool {
9309        let Some(normalized) = normalize_reference_name(raw_name) else {
9310            return false;
9311        };
9312        normalized.contains("::")
9313            && self
9314                .named_candidates_for_normalized(file, &normalized, kind)
9315                .into_iter()
9316                .any(|unit| {
9317                    matches_kind_for_lookup(unit, kind)
9318                        && reference_matches_unit(&normalized, unit)
9319                        && !same_visible_symbol(unit, target)
9320                })
9321    }
9322
9323    pub fn resolve_call_return_binding(
9324        &self,
9325        analyzer: &CppGraphSource<'_>,
9326        file: &ProjectFile,
9327        raw_name: &str,
9328        arity: usize,
9329        lexical_namespace: Option<&str>,
9330        direct_type: Option<&CodeUnit>,
9331    ) -> Option<CppScanBinding> {
9332        let normalized = normalize_reference_name(raw_name)?;
9333        let mut candidates = Vec::new();
9334        for function in
9335            self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
9336        {
9337            if cpp_callable_arity(analyzer, function).is_none_or(|known| known.accepts(arity))
9338                && !direct_type.is_some_and(|direct_type| {
9339                    self.callable_is_constructor_declaration(analyzer, function)
9340                        && type_owner_of(analyzer, function)
9341                            .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
9342                })
9343            {
9344                candidates.push(function.clone());
9345            }
9346        }
9347        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
9348        unanimous_return_binding(analyzer, self, file, &candidates)
9349    }
9350
9351    pub fn resolve_call_return_binding_without_arity(
9352        &self,
9353        analyzer: &CppGraphSource<'_>,
9354        file: &ProjectFile,
9355        raw_name: &str,
9356        lexical_namespace: Option<&str>,
9357        direct_type: Option<&CodeUnit>,
9358    ) -> (bool, Option<CppScanBinding>) {
9359        let Some(normalized) = normalize_reference_name(raw_name) else {
9360            return (false, None);
9361        };
9362        let mut candidates = self
9363            .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
9364            .into_iter()
9365            .filter(|function| {
9366                function.is_function()
9367                    && !direct_type.is_some_and(|direct_type| {
9368                        self.callable_is_constructor_declaration(analyzer, function)
9369                            && type_owner_of(analyzer, function)
9370                                .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
9371                    })
9372            })
9373            .cloned()
9374            .collect::<Vec<_>>();
9375        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
9376        let has_candidates = !candidates.is_empty();
9377        (
9378            has_candidates,
9379            unanimous_return_binding(analyzer, self, file, &candidates),
9380        )
9381    }
9382
9383    pub fn visible_identifier_candidates<'b>(
9384        &'b self,
9385        file: &ProjectFile,
9386        identifier: &str,
9387    ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
9388        self.visible_by_identifier
9389            .get(file)
9390            .and_then(|by_name| by_name.get(identifier))
9391            .into_iter()
9392            .flatten()
9393    }
9394
9395    /// Whether a grammar component is the direct spelling of `target`.
9396    ///
9397    /// Concrete template specializations are indexed with their arguments in
9398    /// `identifier`, while a reference's name component contains only the
9399    /// primary name. Argument matching remains the later resolver's job.
9400    pub fn type_reference_component_directly_names_target(
9401        &self,
9402        component: &str,
9403        target: &CodeUnit,
9404    ) -> bool {
9405        component == target.identifier()
9406            || self
9407                .cpp_template_metadata
9408                .get(target)
9409                .is_some_and(|metadata| component == metadata.primary_name)
9410    }
9411
9412    /// Return terminal reference names that can denote `target` from `file`.
9413    ///
9414    /// The indexed candidate table covers ordinary declarations and aliases;
9415    /// Parser-only aliases are tested lazily when their spelling is actually
9416    /// encountered in a scanned type node. Enumerating them here would parse
9417    /// every source in the include closure even when the target's direct name
9418    /// is the only spelling present in the file.
9419    pub fn visible_type_reference_component_names_for_target(
9420        &self,
9421        analyzer: &CppGraphSource<'_>,
9422        file: &ProjectFile,
9423        target: &CodeUnit,
9424    ) -> HashSet<String> {
9425        let mut names = HashSet::from_iter([target.identifier().to_string()]);
9426        if let Some(metadata) = self.cpp_template_metadata.get(target) {
9427            names.insert(metadata.primary_name.clone());
9428        }
9429
9430        if let Some(by_identifier) = self.visible_by_identifier.get(file) {
9431            for (identifier, candidates) in by_identifier {
9432                if candidates.iter().any(|candidate| {
9433                    (candidate.is_class()
9434                        && (same_visible_symbol(candidate, target)
9435                            || self.compatible_primary_template_redeclarations(candidate, target)))
9436                        || (declared_type_alias(analyzer, candidate)
9437                            && self.alias_candidate_structurally_reaches_target(
9438                                analyzer, file, candidate, target,
9439                            ))
9440                }) {
9441                    names.insert(identifier.clone());
9442                }
9443            }
9444        }
9445
9446        names
9447    }
9448
9449    pub fn indexed_structural_class_scope(
9450        &self,
9451        file: &ProjectFile,
9452        class: Node<'_>,
9453        source: &str,
9454    ) -> Option<Vec<String>> {
9455        let key = (file.clone(), class.start_byte(), class.end_byte());
9456        if let Some(cached) = self
9457            .indexed_structural_class_scopes
9458            .lock()
9459            .expect("C++ indexed structural-class scope cache poisoned")
9460            .get(&key)
9461            .cloned()
9462        {
9463            return cached;
9464        }
9465        let resolved = (|| {
9466            let name = class.child_by_field_name("name")?;
9467            let identifier = if name.kind() == "template_type" {
9468                node_text(name.child_by_field_name("name")?, source).to_string()
9469            } else {
9470                let mut components = Vec::new();
9471                append_cpp_name_components(name, source, &mut components)?;
9472                components.last()?.clone()
9473            };
9474            let visible = self
9475                .visible_identifier_candidates(file, &identifier)
9476                .cloned()
9477                .collect::<Vec<_>>();
9478            let mut visible = visible;
9479            for candidate in
9480                self.visible_by_file
9481                    .get(file)
9482                    .into_iter()
9483                    .flatten()
9484                    .filter(|candidate| {
9485                        self.cpp_template_metadata
9486                            .get(candidate)
9487                            .is_some_and(|metadata| metadata.primary_name == identifier)
9488                    })
9489            {
9490                if !visible
9491                    .iter()
9492                    .any(|existing| same_logical_symbol(existing, candidate))
9493                {
9494                    visible.push(candidate.clone());
9495                }
9496            }
9497            // Built once per call rather than per candidate; `cpp_source` rebuilds
9498            // the five-field source from the same `self.cpp` on every call.
9499            let cpp_source = self.cpp_source();
9500            let candidates = visible
9501                .iter()
9502                .filter(|candidate| {
9503                    candidate.source() == file
9504                        && candidate.is_class()
9505                        && !declared_type_alias(&cpp_source, candidate)
9506                        && self.cpp.ranges(candidate).iter().any(|range| {
9507                            range.start_byte <= class.start_byte()
9508                                && class.end_byte() <= range.end_byte
9509                        })
9510                })
9511                .collect::<Vec<_>>();
9512            let owner = if name.kind() == "template_type" {
9513                let expected = normalize_cpp_whitespace(node_text(name, source));
9514                let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
9515                let exact = candidates
9516                    .iter()
9517                    .copied()
9518                    .filter(|candidate| {
9519                        candidate
9520                            .fq()
9521                            .segments()
9522                            .iter()
9523                            .rev()
9524                            .find_map(|&segment| {
9525                                let (text, kind) = interner.resolve(segment);
9526                                matches!(
9527                                    kind,
9528                                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
9529                                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
9530                                )
9531                                .then_some(text)
9532                            })
9533                            .is_some_and(|text| text == expected)
9534                    })
9535                    .collect::<Vec<_>>();
9536                unique_logical_type_candidate(exact)
9537                    .or_else(|| unique_logical_type_candidate(candidates.clone()))?
9538            } else {
9539                unique_logical_type_candidate(candidates)?
9540            };
9541            Some(canonical_cpp_scope_components(&owner))
9542        })();
9543        self.indexed_structural_class_scopes
9544            .lock()
9545            .expect("C++ indexed structural-class scope cache poisoned")
9546            .insert(key, resolved.clone());
9547        resolved
9548    }
9549
9550    pub fn indexed_enclosing_owner_scope(
9551        &self,
9552        analyzer: &CppGraphSource<'_>,
9553        file: &ProjectFile,
9554        node: Node<'_>,
9555    ) -> Option<Vec<String>> {
9556        let anchor = std::iter::successors(Some(node), |current| current.parent())
9557            .find(|current| {
9558                matches!(
9559                    current.kind(),
9560                    "function_definition"
9561                        | "class_specifier"
9562                        | "struct_specifier"
9563                        | "union_specifier"
9564                )
9565            })
9566            .unwrap_or(node);
9567        let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
9568        if let Some(cached) = self
9569            .indexed_enclosing_owner_scopes
9570            .lock()
9571            .expect("C++ indexed enclosing-owner scope cache poisoned")
9572            .get(&key)
9573            .cloned()
9574        {
9575            return cached;
9576        }
9577        let resolved = (|| {
9578            let range = Range {
9579                start_byte: node.start_byte(),
9580                end_byte: node.end_byte(),
9581                start_line: node.start_position().row,
9582                end_line: node.end_position().row,
9583            };
9584            let start = analyzer.enclosing_code_unit(file, &range)?;
9585            let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
9586                start,
9587                |unit| self.cached_precise_parent_of(analyzer, unit),
9588            )
9589            .find(|unit| {
9590                unit.is_class()
9591                    && !analyzer
9592                        .type_alias_provider()
9593                        .is_some_and(|provider| provider.is_type_alias(unit))
9594            })?;
9595            Some(canonical_cpp_scope_components(&owner))
9596        })();
9597        self.indexed_enclosing_owner_scopes
9598            .lock()
9599            .expect("C++ indexed enclosing-owner scope cache poisoned")
9600            .insert(key, resolved.clone());
9601        resolved
9602    }
9603
9604    fn cached_precise_parent_of(
9605        &self,
9606        analyzer: &CppGraphSource<'_>,
9607        code_unit: &CodeUnit,
9608    ) -> Option<CodeUnit> {
9609        if let Some(cached) = self
9610            .precise_parent_cache
9611            .lock()
9612            .expect("C++ precise-parent cache poisoned")
9613            .get(code_unit)
9614            .cloned()
9615        {
9616            return cached;
9617        }
9618        let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
9619        self.precise_parent_cache
9620            .lock()
9621            .expect("C++ precise-parent cache poisoned")
9622            .insert(code_unit.clone(), resolved.clone());
9623        resolved
9624    }
9625
9626    pub fn callable_is_constructor_declaration(
9627        &self,
9628        analyzer: &CppGraphSource<'_>,
9629        candidate: &CodeUnit,
9630    ) -> bool {
9631        if !candidate.is_function() {
9632            return false;
9633        }
9634        if analyzer
9635            .declaration_source_properties(candidate)
9636            .is_some_and(|facts| facts.iter().any(|fact| fact.callable_is_constructor))
9637        {
9638            return true;
9639        }
9640        let metadata = analyzer.signature_metadata(candidate);
9641        !metadata.is_empty()
9642            && metadata
9643                .iter()
9644                .all(|signature| signature.return_type_text().is_none())
9645    }
9646
9647    /// Whether a callable declaration is a class-template deduction guide.
9648    ///
9649    /// Tree-sitter represents `Box(T) -> Box<T>;` as a declaration with no
9650    /// type field whose function declarator owns a trailing return type. This
9651    /// structured shape distinguishes a guide from both a constructor (no
9652    /// trailing return) and an ordinary trailing-return function (an `auto`
9653    /// type field).
9654    pub fn callable_is_deduction_guide_declaration(
9655        &self,
9656        analyzer: &CppGraphSource<'_>,
9657        candidate: &CodeUnit,
9658    ) -> bool {
9659        callable_declaration_has_fact(analyzer, candidate, |fact| fact.callable_is_deduction_guide)
9660    }
9661
9662    /// Whether a callable occurrence is directly wrapped by a C++ template
9663    /// declaration. This deliberately inspects declaration syntax instead of
9664    /// inferring template status from the rendered signature.
9665    pub fn callable_is_template_declaration(
9666        &self,
9667        analyzer: &CppGraphSource<'_>,
9668        candidate: &CodeUnit,
9669    ) -> bool {
9670        callable_declaration_has_fact(analyzer, candidate, |fact| fact.callable_is_template)
9671    }
9672
9673    pub fn type_name_candidates<'b>(
9674        &'b self,
9675        file: &ProjectFile,
9676        normalized: &str,
9677    ) -> Vec<&'b CodeUnit> {
9678        self.candidate_units(file, normalized, TargetKind::Type)
9679    }
9680
9681    pub fn visible_members_for_owner_name<'b>(
9682        &'b self,
9683        file: &ProjectFile,
9684        owner: &CodeUnit,
9685        name: &str,
9686    ) -> Vec<&'b CodeUnit> {
9687        self.visible_identifier_candidates(file, name)
9688            .filter(|unit| {
9689                // Structured owner pop on the unit's own `fq()` (shared with
9690                // `CodeUnitIndex::parent_of`), not a re-split of its rendered fqn
9691                // string.
9692                brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
9693                    .is_some_and(|parent| parent == owner.fq_name())
9694            })
9695            .collect()
9696    }
9697
9698    pub fn visible_member_for_owner_name(
9699        &self,
9700        file: &ProjectFile,
9701        owner: &CodeUnit,
9702        name: &str,
9703    ) -> VisibleMemberResolution {
9704        let candidates = self.visible_members_for_owner_name(file, owner, name);
9705        let mut callables = Vec::new();
9706        let mut non_callable = None;
9707        for candidate in candidates {
9708            if candidate.is_function() {
9709                callables.push(candidate.clone());
9710            } else if non_callable.is_none() {
9711                non_callable = Some(candidate.clone());
9712            }
9713        }
9714        match (callables.is_empty(), non_callable) {
9715            (false, None) => VisibleMemberResolution::Callable(callables),
9716            (true, Some(_)) => VisibleMemberResolution::NonCallable,
9717            (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
9718            (true, None) => VisibleMemberResolution::Missing,
9719        }
9720    }
9721
9722    fn field_declared_type_fact(
9723        &self,
9724        analyzer: &CppGraphSource<'_>,
9725        field: &CodeUnit,
9726    ) -> Option<DeclaredFieldTypeFact> {
9727        if let Some(cached) = self
9728            .field_type_facts
9729            .lock()
9730            .expect("C++ field type fact cache poisoned")
9731            .get(field)
9732            .cloned()
9733        {
9734            return cached;
9735        }
9736        let decoded = decode_field_declared_type_fact(analyzer, field);
9737        self.field_type_facts
9738            .lock()
9739            .expect("C++ field type fact cache poisoned")
9740            .insert(field.clone(), decoded.clone());
9741        decoded
9742    }
9743
9744    fn structured_alias_target(
9745        &self,
9746        analyzer: &CppGraphSource<'_>,
9747        unit: &CodeUnit,
9748    ) -> Option<StructuredAliasTarget> {
9749        if let Some(cached) = self
9750            .structured_alias_targets
9751            .lock()
9752            .expect("C++ structured alias target cache poisoned")
9753            .get(unit)
9754            .cloned()
9755        {
9756            return cached;
9757        }
9758        let decoded = decode_structured_alias_target(analyzer, unit);
9759        self.structured_alias_targets
9760            .lock()
9761            .expect("C++ structured alias target cache poisoned")
9762            .insert(unit.clone(), decoded.clone());
9763        decoded
9764    }
9765
9766    pub fn type_candidates<'b>(
9767        &'b self,
9768        file: &ProjectFile,
9769        normalized: &str,
9770    ) -> Vec<&'b CodeUnit> {
9771        let mut candidates = self
9772            .candidate_units(file, normalized, TargetKind::Type)
9773            .into_iter()
9774            .filter(|unit| {
9775                unit.kind() == CodeUnitType::Class || declared_type_alias(&self.cpp_source(), unit)
9776            })
9777            .collect::<Vec<_>>();
9778        dedup_unit_refs(&mut candidates);
9779        candidates
9780    }
9781
9782    pub fn named_candidates_for_normalized<'b>(
9783        &'b self,
9784        file: &ProjectFile,
9785        normalized: &str,
9786        kind: TargetKind,
9787    ) -> Vec<&'b CodeUnit> {
9788        let mut candidates = self
9789            .candidate_units(file, normalized, kind)
9790            .into_iter()
9791            .filter(|unit| {
9792                matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
9793            })
9794            .collect::<Vec<_>>();
9795        dedup_unit_refs(&mut candidates);
9796        candidates
9797    }
9798
9799    pub fn candidate_units<'b>(
9800        &'b self,
9801        file: &ProjectFile,
9802        normalized: &str,
9803        kind: TargetKind,
9804    ) -> Vec<&'b CodeUnit> {
9805        if normalized.contains("::") {
9806            // `normalized` comes from `normalize_cpp_reference_text`, which
9807            // truncates at the first `(`/`{`/`<`, leaving a plain `::`-joined
9808            // qualified-id with no embedded `.`/`/`/`\` and operator tokens
9809            // kept intact by the shared splitter's operator merge — the same
9810            // domain `cpp_reference_fqn_candidates` below already parses with
9811            // the shared splitter. Re-tokenizing and taking the last segment
9812            // reproduces `rsplit("::").find(non-empty)`'s terminal-component
9813            // scan exactly.
9814            let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9815                brokk_bifrost_core::analyzer::Language::Cpp,
9816                normalized,
9817            );
9818            let Some(identifier) = parts.last() else {
9819                return Vec::new();
9820            };
9821            let fqns = cpp_reference_fqn_candidates_from_parts(&parts, kind);
9822            return self
9823                .visible_identifier_candidates(file, identifier)
9824                .filter(|unit| {
9825                    #[cfg(any(test, feature = "test-support"))]
9826                    self.qualified_candidate_inspections
9827                        .fetch_add(1, Ordering::Relaxed);
9828                    fqns.iter().any(|fqn| unit.fq_name() == *fqn)
9829                        || canonical_cpp_name_matches(unit, normalized)
9830                })
9831                .collect();
9832        }
9833        self.visible_identifier_candidates(file, normalized)
9834            .collect()
9835    }
9836
9837    #[cfg(any(test, feature = "test-support"))]
9838    pub fn reset_qualified_candidate_inspections(&self) {
9839        self.qualified_candidate_inspections
9840            .store(0, Ordering::Relaxed);
9841    }
9842
9843    #[cfg(any(test, feature = "test-support"))]
9844    pub fn qualified_candidate_inspections(&self) -> usize {
9845        self.qualified_candidate_inspections.load(Ordering::Relaxed)
9846    }
9847
9848    #[cfg(any(test, feature = "test-support"))]
9849    pub fn visibility_identifier_lookup_count(&self) -> usize {
9850        self.visibility_identifier_lookup_count
9851    }
9852
9853    #[cfg(any(test, feature = "test-support"))]
9854    pub fn visibility_identifier_batch_count(&self) -> usize {
9855        self.visibility_identifier_batch_count
9856    }
9857
9858    #[cfg(any(test, feature = "test-support"))]
9859    pub fn reset_target_preserving_type_resolution_count(&self) {
9860        self.target_preserving_type_resolution_count
9861            .store(0, Ordering::Relaxed);
9862    }
9863
9864    #[cfg(any(test, feature = "test-support"))]
9865    pub fn target_preserving_type_resolution_count(&self) -> usize {
9866        self.target_preserving_type_resolution_count
9867            .load(Ordering::Relaxed)
9868    }
9869
9870    #[cfg(any(test, feature = "test-support"))]
9871    pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
9872        self.visible_parser_alias_name_set_build_count
9873            .load(Ordering::Relaxed)
9874    }
9875}
9876
9877fn callable_declaration_has_fact(
9878    analyzer: &CppGraphSource<'_>,
9879    candidate: &CodeUnit,
9880    matches: impl Fn(&brokk_bifrost_core::analyzer::cpp_facts::CppDeclarationSourceFact) -> bool,
9881) -> bool {
9882    candidate.is_function()
9883        && analyzer
9884            .declaration_source_properties(candidate)
9885            .is_some_and(|facts| facts.iter().any(matches))
9886}
9887
9888#[derive(Default)]
9889struct IncludeGraph {
9890    targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
9891}
9892
9893impl IncludeGraph {
9894    fn extend_with<F>(
9895        &mut self,
9896        root: &ProjectFile,
9897        cancellation: Option<&CancellationToken>,
9898        targets_for: &mut F,
9899    ) where
9900        F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9901    {
9902        let mut stack = vec![root.clone()];
9903        while let Some(file) = stack.pop() {
9904            if cancellation.is_some_and(CancellationToken::is_cancelled) {
9905                break;
9906            }
9907            if self.targets_by_file.contains_key(&file) {
9908                continue;
9909            }
9910            let targets = targets_for(&file);
9911            stack.extend(targets.iter().cloned());
9912            self.targets_by_file.insert(file, targets);
9913        }
9914    }
9915
9916    fn files(&self) -> impl Iterator<Item = &ProjectFile> {
9917        self.targets_by_file.keys()
9918    }
9919
9920    fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
9921        self.targets_by_file
9922            .get(file)
9923            .map(Vec::as_slice)
9924            .unwrap_or_default()
9925    }
9926
9927    fn reachable_files(
9928        &self,
9929        root: &ProjectFile,
9930        cancellation: Option<&CancellationToken>,
9931    ) -> HashSet<ProjectFile> {
9932        let mut pending = vec![root.clone()];
9933        let mut visited = HashSet::default();
9934        while let Some(file) = pending.pop() {
9935            if cancellation.is_some_and(CancellationToken::is_cancelled) {
9936                break;
9937            }
9938            if visited.insert(file.clone()) {
9939                pending.extend(self.targets(&file).iter().cloned());
9940            }
9941        }
9942        visited
9943    }
9944}
9945
9946fn build_bounded_visible_declarations(
9947    cpp: &dyn CppSource,
9948    token: QueryToken<'_>,
9949    analyzer: &CppGraphSource<'_>,
9950    roots: &HashSet<ProjectFile>,
9951    visible_sources: &HashMap<ProjectFile, HashSet<ProjectFile>>,
9952    cancellation: Option<&CancellationToken>,
9953    stats: &mut BoundedVisibilityStats,
9954) -> HashMap<ProjectFile, HashSet<CodeUnit>> {
9955    let mut candidates_by_identifier = HashMap::default();
9956    let mut declarations_by_source_and_reading: HashMap<
9957        (ProjectFile, bool),
9958        BoundedVisibilityDeclarations,
9959    > = HashMap::default();
9960    let mut dependency_names_by_unit: HashMap<CodeUnit, HashSet<String>> = HashMap::default();
9961    roots
9962        .iter()
9963        .map(|root| {
9964            let reading_is_c = analyzer.reference_uses_c_semantics(root);
9965            let root_declarations = declarations_by_source_and_reading
9966                .entry((root.clone(), reading_is_c))
9967                .or_insert_with(|| {
9968                    bounded_visibility_declarations(cpp, analyzer, root, reading_is_c, stats)
9969                });
9970            let mut visible = root_declarations
9971                .all
9972                .iter()
9973                .cloned()
9974                .collect::<HashSet<_>>();
9975            let mut pending_names = HashSet::default();
9976            if let Some(prepared) = cpp.prepared_syntax(token, root) {
9977                // One cursor for the whole file walk: `named_child(index)`
9978                // re-steps the sibling list from the first child on every
9979                // access, which made this whole-file scan quadratic in the
9980                // fan-out of each node (#3097).
9981                let mut cursor = prepared.tree().walk();
9982                let mut pending_nodes = vec![prepared.tree().root_node()];
9983                while let Some(node) = pending_nodes.pop() {
9984                    if matches!(
9985                        node.kind(),
9986                        "identifier"
9987                            | "type_identifier"
9988                            | "field_identifier"
9989                            | "namespace_identifier"
9990                    ) {
9991                        pending_names.insert(node_text(node, prepared.source()).to_string());
9992                    }
9993                    if node.kind() == "preproc_arg" {
9994                        for reference in
9995                            object_macro_replacement_type_references(node, prepared.source())
9996                        {
9997                            pending_names.extend(reference.components);
9998                        }
9999                    }
10000                    pending_nodes.extend(node.named_children(&mut cursor));
10001                }
10002            }
10003            stats.root_names += pending_names.len();
10004            let mut completed_names = HashSet::default();
10005            while !pending_names.is_empty() {
10006                stats.rounds += 1;
10007                let round_names = std::mem::take(&mut pending_names);
10008                let mut requested_names_by_source: HashMap<ProjectFile, HashSet<String>> =
10009                    HashMap::default();
10010                let mut identifiers = Vec::new();
10011                for identifier in round_names {
10012                    if !completed_names.insert(identifier.clone())
10013                        || cancellation.is_some_and(CancellationToken::is_cancelled)
10014                    {
10015                        continue;
10016                    }
10017                    identifiers.push(identifier);
10018                }
10019                let missing_identifiers = identifiers
10020                    .iter()
10021                    .filter(|identifier| !candidates_by_identifier.contains_key(*identifier))
10022                    .cloned()
10023                    .collect::<HashSet<_>>();
10024                if !missing_identifiers.is_empty() {
10025                    let lookup_started = Instant::now();
10026                    let mut candidates = cpp
10027                        .visibility_identifier_candidates_batch(&missing_identifiers, cancellation);
10028                    stats.lookup_elapsed += lookup_started.elapsed();
10029                    stats.identifier_lookups += missing_identifiers.len();
10030                    stats.identifier_batches += 1;
10031                    if cancellation.is_some_and(CancellationToken::is_cancelled) {
10032                        break;
10033                    }
10034                    for identifier in missing_identifiers {
10035                        let units = candidates.remove(&identifier).unwrap_or_default();
10036                        let candidate_count = units.len();
10037                        let candidate_sources = units
10038                            .into_iter()
10039                            .map(|unit| unit.source().clone())
10040                            .collect::<HashSet<_>>();
10041                        candidates_by_identifier
10042                            .insert(identifier, (candidate_sources, candidate_count));
10043                    }
10044                }
10045                for identifier in identifiers {
10046                    let (candidate_sources, candidate_count) = candidates_by_identifier
10047                        .get(&identifier)
10048                        .expect("every missing identifier was inserted after the batch lookup");
10049                    stats.candidate_units += *candidate_count;
10050                    for source in candidate_sources.iter().cloned() {
10051                        if source != *root
10052                            && visible_sources
10053                                .get(root)
10054                                .is_some_and(|files| files.contains(&source))
10055                        {
10056                            requested_names_by_source
10057                                .entry(source)
10058                                .or_default()
10059                                .insert(identifier.clone());
10060                        }
10061                    }
10062                }
10063                stats.candidate_sources += requested_names_by_source.len();
10064                for (source, requested_names) in requested_names_by_source {
10065                    let declarations = declarations_by_source_and_reading
10066                        .entry((source.clone(), reading_is_c))
10067                        .or_insert_with(|| {
10068                            bounded_visibility_declarations(
10069                                cpp,
10070                                analyzer,
10071                                &source,
10072                                reading_is_c,
10073                                stats,
10074                            )
10075                        });
10076                    let mut selected = HashSet::default();
10077                    for name in requested_names {
10078                        if let Some(units) = declarations.by_identifier.get(&name) {
10079                            selected.extend(units.iter().cloned());
10080                        }
10081                    }
10082                    for unit in selected {
10083                        stats.selected_units += 1;
10084                        let dependency_names = dependency_names_by_unit
10085                            .entry(unit.clone())
10086                            .or_insert_with(|| {
10087                                let facts_started = Instant::now();
10088                                let mut dependency_names = HashSet::default();
10089                                if let Some(facts) = analyzer.declaration_source_properties(&unit) {
10090                                    for fact in facts {
10091                                        for name in fact.dependency_type_names {
10092                                            stats.dependency_source_names += 1;
10093                                            #[cfg(any(test, feature = "test-support"))]
10094                                            BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT
10095                                                .with(|count| count.set(count.get() + 1));
10096                                            dependency_names.insert(name);
10097                                        }
10098                                    }
10099                                    stats.dependency_source_elapsed += facts_started.elapsed();
10100                                }
10101                                dependency_names
10102                            });
10103                        for name in dependency_names.iter() {
10104                            if !completed_names.contains(name) && pending_names.insert(name.clone())
10105                            {
10106                                stats.dependency_names += 1;
10107                            }
10108                        }
10109                        visible.insert(unit);
10110                    }
10111                }
10112            }
10113            (root.clone(), visible)
10114        })
10115        .collect()
10116}
10117
10118struct BoundedVisibilityDeclarations {
10119    all: Vec<CodeUnit>,
10120    by_identifier: HashMap<String, Vec<CodeUnit>>,
10121}
10122
10123fn bounded_visibility_declarations(
10124    cpp: &dyn CppSource,
10125    analyzer: &CppGraphSource<'_>,
10126    file: &ProjectFile,
10127    c_semantics: bool,
10128    stats: &mut BoundedVisibilityStats,
10129) -> BoundedVisibilityDeclarations {
10130    let declarations_started = Instant::now();
10131    let all = bounded_visibility_declarations_in_reading(analyzer, file, c_semantics)
10132        .into_iter()
10133        .collect::<Vec<_>>();
10134    stats.declaration_elapsed += declarations_started.elapsed();
10135    stats.declaration_reads += 1;
10136    stats.declaration_units += all.len();
10137
10138    let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
10139    for unit in &all {
10140        by_identifier
10141            .entry(unit.identifier().to_string())
10142            .or_default()
10143            .push(unit.clone());
10144        if unit.is_class()
10145            && let Some(metadata) = cpp.template_metadata(unit)
10146            && metadata.primary_name != unit.identifier()
10147        {
10148            by_identifier
10149                .entry(metadata.primary_name)
10150                .or_default()
10151                .push(unit.clone());
10152        }
10153    }
10154    BoundedVisibilityDeclarations { all, by_identifier }
10155}
10156
10157#[derive(Default)]
10158struct BoundedVisibilityStats {
10159    rounds: usize,
10160    root_names: usize,
10161    identifier_lookups: usize,
10162    identifier_batches: usize,
10163    candidate_units: usize,
10164    candidate_sources: usize,
10165    declaration_reads: usize,
10166    declaration_units: usize,
10167    selected_units: usize,
10168    dependency_source_names: usize,
10169    dependency_names: usize,
10170    lookup_elapsed: Duration,
10171    declaration_elapsed: Duration,
10172    dependency_source_elapsed: Duration,
10173}
10174
10175fn bounded_visibility_declarations_in_reading(
10176    analyzer: &CppGraphSource<'_>,
10177    file: &ProjectFile,
10178    c_semantics: bool,
10179) -> BTreeSet<CodeUnit> {
10180    #[cfg(any(test, feature = "test-support"))]
10181    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(count.get() + 1));
10182    analyzer.declarations_in_reading(file, c_semantics)
10183}
10184
10185#[cfg(any(test, feature = "test-support"))]
10186pub fn reset_bounded_visibility_declaration_read_count_for_test() {
10187    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(0));
10188}
10189
10190#[cfg(any(test, feature = "test-support"))]
10191pub fn bounded_visibility_declaration_read_count_for_test() -> usize {
10192    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(Cell::get)
10193}
10194
10195#[cfg(any(test, feature = "test-support"))]
10196pub fn reset_bounded_visibility_dependency_ast_node_count_for_test() {
10197    BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(|count| count.set(0));
10198}
10199
10200#[cfg(any(test, feature = "test-support"))]
10201pub fn bounded_visibility_dependency_ast_node_count_for_test() -> usize {
10202    BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(Cell::get)
10203}
10204
10205/// Admit the class that an out-of-line definition proves is in scope.
10206///
10207/// `Owner::member(...) { ... }` in a file is structured proof that `Owner`
10208/// names a class-like entity in that file's scope: a member declaration can
10209/// live in a file other than its class's only when it is written out of line.
10210/// A file a build concatenates rather than compiles carries no `#include` edge
10211/// to the header declaring `Owner` -- google/wuffs
10212/// `internal/cgen/auxiliary/image.cc` defines
10213/// `DecodeImageResult::DecodeImageResult` and never includes `image.hh` -- so
10214/// every unqualified member and constructor reference in it had no candidate at
10215/// all (#1832).
10216///
10217/// The evidence is the indexed declaration's own owner name, taken from its
10218/// `FqName`, so this stays a structured answer rather than a text fallback.
10219/// Only an owner the file cannot already see is admitted: that is what keeps a
10220/// header declaring its own class from additionally seeing every same-named
10221/// class in the workspace, and it makes the pass free for the ordinary file
10222/// whose owners are all visible.
10223#[derive(Default)]
10224struct OutOfLineOwnerBindingStats {
10225    unseen_owners: usize,
10226    definition_lookups: usize,
10227    admitted: usize,
10228}
10229
10230fn extend_with_out_of_line_owner_bindings(
10231    cpp: &dyn CppSource,
10232    visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
10233) -> OutOfLineOwnerBindingStats {
10234    let mut stats = OutOfLineOwnerBindingStats::default();
10235    for (file, visible) in visible_by_file.iter_mut() {
10236        // The include-closure walk seeds every root with its own declarations,
10237        // so the file's members are already here; re-reading them from the
10238        // analyzer would pay for the same declaration set twice.
10239        let mut unseen_owners: HashSet<String> = visible
10240            .iter()
10241            .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
10242            .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
10243            .collect();
10244        if unseen_owners.is_empty() {
10245            continue;
10246        }
10247        for unit in visible.iter().filter(|unit| unit.is_class()) {
10248            unseen_owners.remove(&unit.fq_name());
10249        }
10250        stats.unseen_owners += unseen_owners.len();
10251        stats.definition_lookups += unseen_owners.len();
10252        let admitted = unseen_owners
10253            .iter()
10254            .flat_map(|owner| cpp.definitions(owner))
10255            .filter(CodeUnit::is_class)
10256            .collect::<Vec<_>>();
10257        stats.admitted += admitted.len();
10258        visible.extend(admitted);
10259    }
10260    stats
10261}
10262
10263pub enum VisibleMemberResolution {
10264    Callable(Vec<CodeUnit>),
10265    NonCallable,
10266    AmbiguousKind,
10267    Missing,
10268}
10269
10270#[derive(Clone)]
10271pub enum EnclosingMemberOwnerResolution {
10272    Owner(CodeUnit),
10273    Ambiguous,
10274    Missing,
10275}
10276
10277pub fn resolve_declaring_member_owner(
10278    analyzer: &CppGraphSource<'_>,
10279    visibility: &VisibilityIndex<'_>,
10280    file: &ProjectFile,
10281    receiver_owner: &CodeUnit,
10282    member_name: &str,
10283) -> EnclosingMemberOwnerResolution {
10284    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
10285        return EnclosingMemberOwnerResolution::Missing;
10286    };
10287    let Some(receiver_owner) =
10288        visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
10289    else {
10290        return EnclosingMemberOwnerResolution::Ambiguous;
10291    };
10292    let resolve_level = |frontier: &[CodeUnit]| {
10293        let mut member_owners = Vec::new();
10294        for raw_owner in frontier {
10295            let Some(owner) =
10296                visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
10297            else {
10298                return EnclosingMemberOwnerResolution::Ambiguous;
10299            };
10300            for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
10301                // A type nested in an owner shares the owner's member-name
10302                // index, but it cannot be the value receiver of `owner.name`.
10303                // Keep value members here so an anonymous aggregate field
10304                // does not become ambiguous with its promoted receiver type.
10305                if !member.is_field() && !member.is_function() {
10306                    continue;
10307                }
10308                let Some(member_owner) = type_owner_of(analyzer, member) else {
10309                    return EnclosingMemberOwnerResolution::Ambiguous;
10310                };
10311                if !member_owners
10312                    .iter()
10313                    .any(|existing| same_visible_symbol(existing, &member_owner))
10314                {
10315                    member_owners.push(member_owner);
10316                }
10317            }
10318        }
10319        match member_owners.len() {
10320            0 => EnclosingMemberOwnerResolution::Missing,
10321            1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
10322            _ => EnclosingMemberOwnerResolution::Ambiguous,
10323        }
10324    };
10325    // The first declaration on each structured base path hides deeper names,
10326    // regardless of whether its callable overload is applicable at a particular
10327    // call site. Applicability is checked only after this owner is established.
10328    let direct = resolve_level(std::slice::from_ref(&receiver_owner));
10329    if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
10330        return direct;
10331    }
10332    let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
10333    let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
10334    let mut path_matches = Vec::new();
10335    while let Some(raw_owner) = stack.pop() {
10336        let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
10337        else {
10338            return EnclosingMemberOwnerResolution::Ambiguous;
10339        };
10340        // Persisted hierarchy edges do not encode virtual-base or base-subobject paths.
10341        // Propagate at most two occurrences of each owner: that preserves the distinction
10342        // between one and multiple resolving base paths without exponential diamond walks.
10343        let propagated = propagated_counts.entry(owner.clone()).or_default();
10344        if *propagated == 2 {
10345            continue;
10346        }
10347        *propagated += 1;
10348        match resolve_level(std::slice::from_ref(&owner)) {
10349            EnclosingMemberOwnerResolution::Owner(owner) => {
10350                path_matches.push(owner);
10351                if path_matches.len() == 2 {
10352                    return EnclosingMemberOwnerResolution::Ambiguous;
10353                }
10354            }
10355            EnclosingMemberOwnerResolution::Ambiguous => {
10356                return EnclosingMemberOwnerResolution::Ambiguous;
10357            }
10358            EnclosingMemberOwnerResolution::Missing => {
10359                stack.extend(hierarchy.get_direct_ancestors(&owner));
10360            }
10361        }
10362    }
10363    match path_matches.len() {
10364        0 => EnclosingMemberOwnerResolution::Missing,
10365        1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
10366        _ => unreachable!("base-path matches are capped at one before returning"),
10367    }
10368}
10369
10370/// Resolve the declaring owner of a callable after applying a member
10371/// `using <Base>::<member>;` declaration to one exact call arity.
10372///
10373/// Ordinary member lookup is intentionally name-based: the first class that
10374/// declares a name hides the same name on deeper bases. A member
10375/// using-declaration is the one exception. When none of the declarations on
10376/// that first owner accepts the call arity, it can reintroduce an applicable
10377/// overload from the named base. If a declaration on the first owner does
10378/// accept the arity, argument types would be needed to choose between it and
10379/// a same-arity introduced overload, so this resolver conservatively keeps the
10380/// ordinary owner (#1835/#1843).
10381///
10382/// The caller supplies ordinary name-based owner resolution so a file scan can
10383/// reuse its existing owner cache before applying this callable-only exception.
10384pub fn resolve_declaring_callable_owner(
10385    analyzer: &CppGraphSource<'_>,
10386    visibility: &VisibilityIndex<'_>,
10387    file: &ProjectFile,
10388    ordinary: EnclosingMemberOwnerResolution,
10389    member_name: &str,
10390    call_arity: usize,
10391) -> EnclosingMemberOwnerResolution {
10392    let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
10393        return ordinary;
10394    };
10395    if visibility
10396        .visible_members_for_owner_name(file, ordinary_owner, member_name)
10397        .into_iter()
10398        .any(|unit| {
10399            unit.is_function()
10400                && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(call_arity))
10401        })
10402    {
10403        return ordinary;
10404    }
10405
10406    let mut pending = match member_using_declaration_bases(
10407        analyzer,
10408        visibility,
10409        file,
10410        ordinary_owner,
10411        member_name,
10412    ) {
10413        Ok(bases) => bases,
10414        Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
10415    };
10416    let mut visited = HashSet::default();
10417    let mut introduced_owners = Vec::new();
10418    while let Some(owner) = pending.pop() {
10419        if !visited.insert(owner.clone()) {
10420            continue;
10421        }
10422        let accepts_arity = visibility
10423            .visible_members_for_owner_name(file, &owner, member_name)
10424            .into_iter()
10425            .any(|unit| {
10426                unit.is_function()
10427                    && cpp_callable_arity(analyzer, unit)
10428                        .is_none_or(|known| known.accepts(call_arity))
10429            });
10430        if accepts_arity {
10431            if !introduced_owners
10432                .iter()
10433                .any(|existing| same_visible_symbol(existing, &owner))
10434            {
10435                introduced_owners.push(owner);
10436            }
10437            continue;
10438        }
10439        match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
10440            Ok(bases) => pending.extend(bases),
10441            Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
10442        }
10443    }
10444    match introduced_owners.as_slice() {
10445        [] => ordinary,
10446        [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
10447        _ => EnclosingMemberOwnerResolution::Ambiguous,
10448    }
10449}
10450
10451fn member_using_declaration_bases(
10452    analyzer: &CppGraphSource<'_>,
10453    visibility: &VisibilityIndex<'_>,
10454    file: &ProjectFile,
10455    owner: &CodeUnit,
10456    member_name: &str,
10457) -> Result<Vec<CodeUnit>, ()> {
10458    let Some(facts) = analyzer.declaration_source_properties(owner) else {
10459        return Err(());
10460    };
10461    let scopes: Vec<_> = facts
10462        .iter()
10463        .flat_map(|fact| &fact.member_usings)
10464        .filter(|using| using.member == member_name)
10465        .map(|using| using.scope.join("::"))
10466        .collect();
10467    if scopes.is_empty() {
10468        return Ok(Vec::new());
10469    }
10470    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
10471        return Ok(Vec::new());
10472    };
10473    let mut bases = Vec::new();
10474    for raw_ancestor in hierarchy.get_ancestors(owner) {
10475        let Some(ancestor) =
10476            visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
10477        else {
10478            return Err(());
10479        };
10480        let qualified = cpp_name_for(&ancestor);
10481        if scopes
10482            .iter()
10483            .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
10484            && !bases
10485                .iter()
10486                .any(|existing| same_visible_symbol(existing, &ancestor))
10487        {
10488            bases.push(ancestor);
10489        }
10490    }
10491    Ok(bases)
10492}
10493
10494pub fn lexical_component_tiers<'a>(
10495    components: &'a [String],
10496    global: bool,
10497    lexical_scope: &'a [String],
10498) -> impl Iterator<Item = Vec<String>> + 'a {
10499    let first_prefix_len = if global { 0 } else { lexical_scope.len() };
10500    (0..=first_prefix_len).rev().map(move |prefix_len| {
10501        let mut qualified = Vec::with_capacity(prefix_len + components.len());
10502        qualified.extend_from_slice(&lexical_scope[..prefix_len]);
10503        qualified.extend_from_slice(components);
10504        qualified
10505    })
10506}
10507
10508pub fn build_visible_identifier_index(
10509    analyzer: &CppGraphSource<'_>,
10510    visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
10511    visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
10512    global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
10513) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
10514    let mut out = HashMap::default();
10515    for (file, visible) in visible_by_file {
10516        let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
10517        for unit in visible {
10518            if unit.is_field()
10519                && !visible_source_files_by_root
10520                    .get(file)
10521                    .is_some_and(|sources| sources.contains(unit.source()))
10522                && cpp_global_field_has_internal_linkage_cached(
10523                    analyzer,
10524                    global_field_internal_linkage,
10525                    unit,
10526                )
10527            {
10528                continue;
10529            }
10530            by_identifier
10531                .entry(unit.identifier().to_string())
10532                .or_default()
10533                .push(unit.clone());
10534        }
10535        for units in by_identifier.values_mut() {
10536            sort_lookup_units(units);
10537            units.dedup();
10538        }
10539        out.insert(file.clone(), by_identifier);
10540    }
10541    out
10542}
10543
10544fn sort_lookup_units(units: &mut [CodeUnit]) {
10545    units.sort_by(|left, right| {
10546        left.fq_name()
10547            .cmp(&right.fq_name())
10548            .then_with(|| left.signature().cmp(&right.signature()))
10549            .then_with(|| left.source().cmp(right.source()))
10550            .then_with(|| left.kind().cmp(&right.kind()))
10551            .then_with(|| {
10552                left.package_segment_count()
10553                    .cmp(&right.package_segment_count())
10554            })
10555            .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
10556            .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
10557    });
10558}
10559
10560fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
10561    let interner = segment_interner();
10562    for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
10563        let (left_text, left_kind) = interner.resolve(left_id);
10564        let (right_text, right_kind) = interner.resolve(right_id);
10565        let order = left_text
10566            .cmp(right_text)
10567            .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
10568        if order != CmpOrdering::Equal {
10569            return order;
10570        }
10571    }
10572    left.len().cmp(&right.len())
10573}
10574
10575const fn segment_kind_order(kind: SegmentKind) -> u8 {
10576    match kind {
10577        SegmentKind::Path => 0,
10578        SegmentKind::Package => 1,
10579        SegmentKind::Type => 2,
10580        SegmentKind::Companion => 3,
10581        SegmentKind::Nested => 4,
10582        SegmentKind::Member => 5,
10583        SegmentKind::Unknown => 6,
10584    }
10585}
10586
10587fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
10588    *units = brokk_bifrost_core::analyzer::usages::inverted_edges::dedup_preserving_order(
10589        std::mem::take(units),
10590    );
10591}
10592
10593pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
10594    // Same domain as `candidate_units` above: `reference` is a plain
10595    // `::`-joined qualified-id with operator tokens kept intact by the shared
10596    // splitter's operator merge.
10597    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10598        brokk_bifrost_core::analyzer::Language::Cpp,
10599        reference,
10600    );
10601    cpp_reference_fqn_candidates_from_parts(&parts, kind)
10602}
10603
10604fn cpp_reference_fqn_candidates_from_parts(parts: &[String], kind: TargetKind) -> Vec<String> {
10605    if parts.is_empty() {
10606        return Vec::new();
10607    }
10608
10609    let mut candidates = Vec::new();
10610    for package_len in 0..parts.len() {
10611        let package = parts[..package_len].join("::");
10612        let rest = &parts[package_len..];
10613        if rest.is_empty() {
10614            continue;
10615        }
10616        match kind {
10617            TargetKind::Type | TargetKind::Constructor => {
10618                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
10619                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10620            }
10621            TargetKind::FreeFunction
10622            | TargetKind::Method
10623            | TargetKind::GlobalField
10624            | TargetKind::MemberField
10625            | TargetKind::Macro => {
10626                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10627                if rest.len() > 1 {
10628                    let owner = rest[..rest.len() - 1].join("$");
10629                    let short = format!("{}.{}", owner, rest[rest.len() - 1]);
10630                    push_cpp_fqn_candidate(&mut candidates, &package, &short);
10631                }
10632            }
10633        }
10634    }
10635    candidates
10636}
10637
10638fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
10639    let fqn = if package.is_empty() {
10640        short.to_string()
10641    } else {
10642        format!("{package}.{short}")
10643    };
10644    if !out.contains(&fqn) {
10645        out.push(fqn);
10646    }
10647}
10648
10649pub fn infer_cpp_initializer_type(
10650    analyzer: &CppGraphSource<'_>,
10651    visibility: &VisibilityIndex<'_>,
10652    file: &ProjectFile,
10653    source: &str,
10654    node: Node<'_>,
10655) -> Option<CodeUnit> {
10656    infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
10657        .and_then(|binding| binding.unit)
10658}
10659
10660pub fn infer_cpp_initializer_binding(
10661    analyzer: &CppGraphSource<'_>,
10662    visibility: &VisibilityIndex<'_>,
10663    file: &ProjectFile,
10664    source: &str,
10665    node: Node<'_>,
10666    receiver_resolver: Option<&ReceiverResolver<'_>>,
10667) -> Option<CppScanBinding> {
10668    match node.kind() {
10669        "new_expression" => {
10670            let type_node = cpp_new_expression_type_node(node)?;
10671            let type_text = node_text(type_node, source);
10672            let name = normalize_cpp_type_name(type_text);
10673            Some(CppScanBinding::from_type_name(
10674                name.clone(),
10675                visibility.resolve_type(file, &name),
10676                1,
10677            ))
10678        }
10679        "call_expression" => node.child_by_field_name("function").and_then(|function| {
10680            // `a().b()` and `p->b()` invoke a member on a receiver *value*. The
10681            // callee's source text is an expression, not a name, and every name
10682            // lookup below normalizes a reference by truncating at the first
10683            // `(`: `first().second` would read as `first`, so the chained call
10684            // would take the type of `first()` instead of the type of
10685            // `first().second()` (#2178). Only the member path can answer for
10686            // this shape, so route to it from the callee's node kind.
10687            if function.kind() == "field_expression" {
10688                let arity = visibility.call_arity_evidence(file, node, source).exact()?;
10689                return resolve_field_method_call_return_binding(
10690                    analyzer,
10691                    visibility,
10692                    file,
10693                    source,
10694                    function,
10695                    arity,
10696                    receiver_resolver,
10697                );
10698            }
10699            let function_text = node_text(function, source);
10700            let direct_type_binding = visibility
10701                .resolve_type(file, function_text)
10702                .map(|unit| CppScanBinding::from_unit(unit, 0));
10703            if function.kind() == "template_function" && direct_type_binding.is_some() {
10704                let lexical_namespace = enclosing_namespace_context(node, source);
10705                let arity = visibility.call_arity_evidence(file, node, source).exact();
10706                if let Some(arity) = arity
10707                    && let Some(binding) = visibility.resolve_call_return_binding(
10708                        analyzer,
10709                        file,
10710                        function_text,
10711                        arity,
10712                        lexical_namespace.as_deref(),
10713                        direct_type_binding
10714                            .as_ref()
10715                            .and_then(|binding| binding.unit.as_ref()),
10716                    )
10717                {
10718                    return Some(binding);
10719                }
10720                let (has_callable, callable_binding) = visibility
10721                    .resolve_call_return_binding_without_arity(
10722                        analyzer,
10723                        file,
10724                        function_text,
10725                        lexical_namespace.as_deref(),
10726                        direct_type_binding
10727                            .as_ref()
10728                            .and_then(|binding| binding.unit.as_ref()),
10729                    );
10730                if let Some(binding) = callable_binding {
10731                    return Some(binding);
10732                }
10733                if has_callable {
10734                    return None;
10735                }
10736                return direct_type_binding;
10737            }
10738            // Only the return-typed branches need the argument count. An
10739            // unknown arity leaves them out, exactly as in the template arm
10740            // above, and still constructs the direct type: `File(getPath())`
10741            // names `File` whether or not `getPath()`'s expansion is provable.
10742            let arity = visibility.call_arity_evidence(file, node, source).exact();
10743            if let Some(arity) = arity {
10744                let direct_type_binding_for_call = direct_type_binding.clone();
10745                if let Some(binding) = resolve_static_method_call_return_binding(
10746                    analyzer, visibility, file, source, function, arity,
10747                )
10748                .or_else(|| {
10749                    // An applicable free function supplies the receiver value
10750                    // before an unrelated visible type with the same terminal
10751                    // name. The direct type still excludes its own constructor
10752                    // declaration below and remains the construction fallback.
10753                    visibility.resolve_call_return_binding(
10754                        analyzer,
10755                        file,
10756                        function_text,
10757                        arity,
10758                        enclosing_namespace_context(node, source).as_deref(),
10759                        direct_type_binding_for_call
10760                            .as_ref()
10761                            .and_then(|binding| binding.unit.as_ref()),
10762                    )
10763                }) {
10764                    return Some(binding);
10765                }
10766            }
10767            direct_type_binding
10768        }),
10769        _ => None,
10770    }
10771}
10772
10773/// Return the type specifier carried by a C++ `new_expression`.
10774///
10775/// The grammar names this child with the `type` field. If recovery omits that
10776/// field, recover only from type-specifier nodes retained in the parse tree;
10777/// skip placement arguments, the array declarator, and initializer arguments.
10778pub fn cpp_new_expression_type_node(expression: Node<'_>) -> Option<Node<'_>> {
10779    debug_assert_eq!(expression.kind(), "new_expression");
10780    if let Some(type_node) = expression.child_by_field_name("type") {
10781        return Some(type_node);
10782    }
10783
10784    let mut pending = Vec::new();
10785    let mut cursor = expression.walk();
10786    pending.extend(
10787        expression
10788            .named_children(&mut cursor)
10789            .filter(|child| {
10790                !matches!(
10791                    child.kind(),
10792                    "argument_list" | "initializer_list" | "new_declarator"
10793                )
10794            })
10795            .collect::<Vec<_>>()
10796            .into_iter()
10797            .rev(),
10798    );
10799    while let Some(node) = pending.pop() {
10800        if is_cpp_type_specifier_node(node) {
10801            return Some(node);
10802        }
10803        if node.kind() == "ERROR" {
10804            let mut cursor = node.walk();
10805            pending.extend(
10806                node.named_children(&mut cursor)
10807                    .collect::<Vec<_>>()
10808                    .into_iter()
10809                    .rev(),
10810            );
10811        }
10812    }
10813    None
10814}
10815
10816fn is_cpp_type_specifier_node(node: Node<'_>) -> bool {
10817    matches!(
10818        node.kind(),
10819        "class_specifier"
10820            | "decltype"
10821            | "dependent_type"
10822            | "enum_specifier"
10823            | "placeholder_type_specifier"
10824            | "primitive_type"
10825            | "qualified_identifier"
10826            | "sized_type_specifier"
10827            | "struct_specifier"
10828            | "template_type"
10829            | "type_identifier"
10830            | "union_specifier"
10831    )
10832}
10833
10834fn resolve_static_method_call_return_binding(
10835    analyzer: &CppGraphSource<'_>,
10836    visibility: &VisibilityIndex<'_>,
10837    file: &ProjectFile,
10838    source: &str,
10839    function: Node<'_>,
10840    arity: usize,
10841) -> Option<CppScanBinding> {
10842    if function.kind() != "qualified_identifier" {
10843        return None;
10844    }
10845    let qualified = normalize_cpp_reference_text(node_text(function, source));
10846    // A C++ qualified-id is `::`-joined with no embedded delimiters in any
10847    // single component (the shared splitter's operator-token merge keeps
10848    // `operator+`-style names intact), so re-tokenizing with the shared
10849    // structured splitter and peeling the terminal segment reproduces
10850    // `rsplit_once("::")`'s (owner, member) split exactly — same shape as
10851    // `cpp_out_of_line_function_owner`'s `qualified` split above.
10852    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10853        brokk_bifrost_core::analyzer::Language::Cpp,
10854        &qualified,
10855    );
10856    let (owner_text, member_name) = match parts.split_last() {
10857        Some((member, owner_parts)) if !owner_parts.is_empty() => {
10858            (owner_parts.join("::"), member.clone())
10859        }
10860        _ => {
10861            let scope = function.child_by_field_name("scope")?;
10862            let name = function.child_by_field_name("name")?;
10863            (
10864                node_text(scope, source).to_string(),
10865                node_text(name, source).to_string(),
10866            )
10867        }
10868    };
10869    let owner = visibility.resolve_type(file, &owner_text)?;
10870    let candidates =
10871        callable_members_accepting_arity(analyzer, visibility, file, &owner, &member_name, arity);
10872    unanimous_return_binding(analyzer, visibility, file, &candidates)
10873}
10874
10875fn resolve_field_method_call_return_binding(
10876    analyzer: &CppGraphSource<'_>,
10877    visibility: &VisibilityIndex<'_>,
10878    file: &ProjectFile,
10879    source: &str,
10880    function: Node<'_>,
10881    arity: usize,
10882    receiver_resolver: Option<&ReceiverResolver<'_>>,
10883) -> Option<CppScanBinding> {
10884    debug_assert_eq!(
10885        function.kind(),
10886        "field_expression",
10887        "the member-call return binding answers only for a field-expression callee"
10888    );
10889    let receiver_resolver = receiver_resolver?;
10890    let field = function.child_by_field_name("field")?;
10891    let member_name = node_text(function_terminal_node(field), source);
10892    let receiver = function
10893        .child_by_field_name("argument")
10894        .or_else(|| function.named_child(0))?;
10895    let owners = receiver_resolver(receiver, source);
10896    let mut candidates = Vec::new();
10897    for owner in owners {
10898        let declaring_owner =
10899            match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
10900                EnclosingMemberOwnerResolution::Owner(owner) => owner,
10901                EnclosingMemberOwnerResolution::Missing => continue,
10902                EnclosingMemberOwnerResolution::Ambiguous => return None,
10903            };
10904        candidates.extend(callable_members_accepting_arity(
10905            analyzer,
10906            visibility,
10907            file,
10908            &declaring_owner,
10909            member_name,
10910            arity,
10911        ));
10912    }
10913    unanimous_return_binding(analyzer, visibility, file, &candidates)
10914}
10915
10916fn callable_members_accepting_arity(
10917    analyzer: &CppGraphSource<'_>,
10918    visibility: &VisibilityIndex<'_>,
10919    file: &ProjectFile,
10920    owner: &CodeUnit,
10921    member_name: &str,
10922    arity: usize,
10923) -> Vec<CodeUnit> {
10924    visibility
10925        .visible_members_for_owner_name(file, owner, member_name)
10926        .into_iter()
10927        .filter(|unit| {
10928            unit.is_function()
10929                && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(arity))
10930        })
10931        .cloned()
10932        .collect()
10933}
10934
10935fn unanimous_return_binding(
10936    analyzer: &CppGraphSource<'_>,
10937    visibility: &VisibilityIndex<'_>,
10938    file: &ProjectFile,
10939    candidates: &[CodeUnit],
10940) -> Option<CppScanBinding> {
10941    let mut resolved_return: Option<CppScanBinding> = None;
10942    for function in candidates {
10943        let metadata = analyzer.signature_metadata(function);
10944        let return_types = if metadata.is_empty() {
10945            vec![cpp_function_return_type_text(analyzer, function)?]
10946        } else {
10947            metadata
10948                .iter()
10949                .map(|metadata| metadata.return_type_text().map(str::to_string))
10950                .collect::<Option<Vec<_>>>()?
10951        };
10952        for return_text in return_types {
10953            let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
10954            let name = normalize_cpp_type_name(&return_text);
10955            let binding = CppScanBinding::from_type_name(
10956                name.clone(),
10957                visibility
10958                    .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
10959                indirection,
10960            );
10961            if let Some(existing) = resolved_return.as_ref()
10962                && (existing.indirection != binding.indirection
10963                    || match (&existing.unit, &binding.unit) {
10964                        (Some(left), Some(right)) => !same_visible_symbol(left, right),
10965                        (None, None) => existing.type_name != binding.type_name,
10966                        (Some(_), None) | (None, Some(_)) => true,
10967                    })
10968            {
10969                return None;
10970            }
10971            resolved_return = Some(binding);
10972        }
10973    }
10974    resolved_return
10975}
10976
10977fn aliases_from_canonical_source(
10978    cpp: &dyn CppSource,
10979    token: QueryToken<'_>,
10980    file: &ProjectFile,
10981) -> Option<Vec<CppAlias>> {
10982    let source = cpp.declaration_source_facts(token, file)?;
10983    Some(
10984        source
10985            .facts
10986            .declarations
10987            .iter()
10988            .filter_map(|fact| {
10989                let alias = fact.file_scope_alias.as_ref()?;
10990                Some(CppAlias {
10991                    name: alias.name.clone(),
10992                    target: alias.target.clone(),
10993                    namespace: alias.namespace.clone(),
10994                })
10995            })
10996            .collect(),
10997    )
10998}
10999
11000pub(crate) fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
11001    let mut current = node.parent();
11002    while let Some(parent) = current {
11003        match parent.kind() {
11004            "translation_unit"
11005            | "namespace_definition"
11006            | "declaration_list"
11007            | "linkage_specification" => current = parent.parent(),
11008            "template_declaration" => current = parent.parent(),
11009            _ => return false,
11010        }
11011    }
11012    true
11013}
11014
11015pub fn collect_include_closure(
11016    analyzer: &CppGraphSource<'_>,
11017    include_targets: &IncludeTargetIndex,
11018    file: &ProjectFile,
11019    out: &mut HashSet<ProjectFile>,
11020    cancellation: Option<&CancellationToken>,
11021) {
11022    let mut stack = vec![file.clone()];
11023    while let Some(file) = stack.pop() {
11024        if cancellation.is_some_and(CancellationToken::is_cancelled) {
11025            break;
11026        }
11027        if !out.insert(file.clone()) {
11028            continue;
11029        }
11030        for include in analyzer.canonical_include_paths(&file).unwrap_or_default() {
11031            for target in resolve_include_targets_with_index(&file, &include, include_targets) {
11032                stack.push(target);
11033            }
11034        }
11035    }
11036}
11037
11038fn parse_macro_parameter_list<T>(
11039    replacement: &str,
11040    read: impl FnOnce(Node<'_>, &str) -> T,
11041) -> Option<T> {
11042    let parsed = parse_sentinel(
11043        "void __bifrost_macro_parameters(",
11044        SentinelInput::replacement(replacement),
11045        ");",
11046    );
11047    let (source, tree) = (parsed.source, parsed.tree);
11048    let root = tree.root_node();
11049    if root.has_error() {
11050        return None;
11051    }
11052    let declaration = single_named_child_matching(root, |child| !child.is_extra())?;
11053    let declarator = declaration.child_by_field_name("declarator")?;
11054    let parameters = declarator.child_by_field_name("parameters")?;
11055    Some(read(parameters, &source))
11056}
11057
11058fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
11059    parse_macro_parameter_list(replacement, crate::declarations::cpp_callable_arity)
11060}
11061
11062/// Interpret opaque macro replacement parameters with the primary producer's
11063/// canonical type spelling, preserving cv/ref qualifiers for overload hiding.
11064pub fn cpp_macro_parameter_types(replacement: &str) -> Option<Vec<String>> {
11065    parse_macro_parameter_list(
11066        replacement,
11067        crate::declarations::cpp_callable_parameter_types,
11068    )
11069}
11070
11071pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> Option<CallableArity> {
11072    brokk_bifrost_core::analyzer::model::first_callable_arity(analyzer.signature_metadata(unit))
11073}
11074
11075pub fn cpp_callable_parameter_types(
11076    analyzer: &CppGraphSource<'_>,
11077    unit: &CodeUnit,
11078) -> Option<Vec<String>> {
11079    brokk_bifrost_core::analyzer::model::first_callable_parameter_types(
11080        analyzer.signature_metadata(unit),
11081    )
11082}
11083
11084fn merge_compatible_callable_arities(
11085    left: CallableArity,
11086    right: CallableArity,
11087) -> Option<CallableArity> {
11088    let total = left.total();
11089    let left_repeated = left.accepts(total.saturating_add(1));
11090    let right_repeated = right.accepts(right.total().saturating_add(1));
11091    if total != right.total() || left_repeated != right_repeated {
11092        return None;
11093    }
11094    let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
11095    Some(CallableArity::new(required, total, left_repeated))
11096}
11097
11098fn collect_macro_events(
11099    cpp: &dyn CppSource,
11100    token: QueryToken<'_>,
11101    file: &ProjectFile,
11102) -> Vec<MacroEvent> {
11103    let Some(prepared) = cpp.prepared_syntax(token, file) else {
11104        return Vec::new();
11105    };
11106    let source = prepared.source();
11107    let mut events = Vec::new();
11108    let root = prepared.tree().root_node();
11109    let mut stack = vec![root];
11110    while let Some(node) = stack.pop() {
11111        match node.kind() {
11112            "preproc_def" | "preproc_function_def" => {
11113                let Some(name) = node.child_by_field_name("name") else {
11114                    continue;
11115                };
11116                let name = node_text(name, source).to_string();
11117                events.push(MacroEvent::Define {
11118                    name,
11119                    binding: MacroBinding {
11120                        source: file.clone(),
11121                        declaration_byte: node.start_byte(),
11122                        definition: VisibilityIndex::decode_macro_definition(node, source),
11123                        exact: true,
11124                    },
11125                    byte: node.start_byte(),
11126                    conditionals: owning_preprocessor_conditionals(root, node, source),
11127                });
11128                continue;
11129            }
11130            "preproc_include" => {
11131                let Some(path) = node.child_by_field_name("path") else {
11132                    events.push(MacroEvent::Include {
11133                        targets: Vec::new(),
11134                        byte: node.start_byte(),
11135                        conditionals: owning_preprocessor_conditionals(root, node, source),
11136                    });
11137                    continue;
11138                };
11139                let include = crate::imports::include_path_literal_value(path, source);
11140                let targets = include.map_or_else(Vec::new, |include| {
11141                    resolve_include_targets_with_index(file, include, cpp.include_target_index())
11142                });
11143                // An include no indexed file can answer -- its final path component names
11144                // no analyzable workspace file -- crosses into sources this index does not
11145                // hold, so it cannot hide indexed macro state and must not poison every
11146                // later local macro binding. The include's spelling does not decide that:
11147                // a quoted include falls back to the same search path an angle include
11148                // uses, and esphome's `core/log.h` reaches every component with
11149                // `#include "WString.h"`, an Arduino header absent from the tree (#3057).
11150                // A computed include, and a spelling some indexed file name does match
11151                // without resolving to one target, may still hide indexed macro state and
11152                // therefore fail closed.
11153                if targets.is_empty()
11154                    && include.is_some_and(|include| {
11155                        !cpp.include_target_index().names_indexed_file(include)
11156                    })
11157                {
11158                    continue;
11159                }
11160                events.push(MacroEvent::Include {
11161                    targets,
11162                    byte: node.start_byte(),
11163                    conditionals: owning_preprocessor_conditionals(root, node, source),
11164                });
11165                continue;
11166            }
11167            "preproc_call" => {
11168                let Some(directive) = node.child_by_field_name("directive") else {
11169                    continue;
11170                };
11171                if node_text(directive, source) != "#undef" {
11172                    continue;
11173                }
11174                let name = node
11175                    .child_by_field_name("argument")
11176                    .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
11177                if let Some(name) = name {
11178                    events.push(MacroEvent::Undef {
11179                        name,
11180                        byte: node.start_byte(),
11181                        conditionals: owning_preprocessor_conditionals(root, node, source),
11182                    });
11183                } else {
11184                    events.push(MacroEvent::Invalidate {
11185                        byte: node.start_byte(),
11186                    });
11187                }
11188                continue;
11189            }
11190            _ => {}
11191        }
11192        push_named_children_reversed(node, &mut stack);
11193    }
11194    events.sort_by_key(MacroEvent::byte);
11195    events
11196}
11197
11198fn find_include_activation(
11199    cpp: &dyn CppSource,
11200    token: QueryToken<'_>,
11201    file: &ProjectFile,
11202    prepared: &PreparedSyntaxTree,
11203    donor_source: &ProjectFile,
11204    language_guard_is_stable: &dyn Fn() -> bool,
11205) -> Option<usize> {
11206    let include_targets = cpp.include_target_index();
11207    let reference_is_c = file
11208        .rel_path()
11209        .extension()
11210        .and_then(|extension| extension.to_str())
11211        == Some("c");
11212    let mut direct_includes = Vec::new();
11213    let mut nodes = vec![prepared.tree().root_node()];
11214    // An include activates for the whole file, so only an unconditional
11215    // directive counts here.
11216    while let Some(node) = nodes.pop() {
11217        if node.kind() == "preproc_include" {
11218            let Some(required_guards) = include_directive_guard_requirements(
11219                node,
11220                prepared.source(),
11221                reference_is_c,
11222                language_guard_is_stable,
11223            ) else {
11224                continue;
11225            };
11226            if required_guards.all.is_empty() && required_guards.partial.is_empty() {
11227                for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
11228                    if let Some(target) = unique_include_target(resolve_include_targets_with_index(
11229                        file,
11230                        include,
11231                        include_targets,
11232                    )) {
11233                        direct_includes.push((node.end_byte(), target));
11234                    }
11235                }
11236            }
11237            continue;
11238        }
11239        push_named_children_reversed(node, &mut nodes);
11240    }
11241    direct_includes.sort_by_key(|(activation, _)| *activation);
11242    let mut known_missing = HashSet::default();
11243    direct_includes
11244        .into_iter()
11245        .find(|(_, direct)| {
11246            unconditional_include_reaches(
11247                cpp,
11248                token,
11249                direct,
11250                donor_source,
11251                file,
11252                &mut known_missing,
11253                language_guard_is_stable,
11254            )
11255        })
11256        .map(|(activation, _)| activation)
11257}
11258
11259fn find_conditional_include_projection_index(
11260    cpp: &dyn CppSource,
11261    token: QueryToken<'_>,
11262    file: &ProjectFile,
11263    prepared: &PreparedSyntaxTree,
11264    language_guard_is_stable: &dyn Fn() -> bool,
11265    on_state: &dyn Fn(),
11266) -> ConditionalIncludeProjectionIndex {
11267    let reference_is_c = reference_uses_c_semantics(cpp, file);
11268    let include_targets = cpp.include_target_index();
11269    let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
11270        HashMap::default();
11271    let mut pending = Vec::new();
11272    let mut nodes = vec![prepared.tree().root_node()];
11273    while let Some(node) = nodes.pop() {
11274        if node.kind() == "preproc_include" {
11275            let Some(required_guards) = include_directive_guard_requirements(
11276                node,
11277                prepared.source(),
11278                reference_is_c,
11279                language_guard_is_stable,
11280            ) else {
11281                continue;
11282            };
11283            for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
11284                let Some(target) = unique_include_target(resolve_include_targets_with_index(
11285                    file,
11286                    include,
11287                    include_targets,
11288                )) else {
11289                    continue;
11290                };
11291                pending.push((target, node.end_byte(), required_guards.clone()));
11292            }
11293            continue;
11294        }
11295        push_named_children_reversed(node, &mut nodes);
11296    }
11297
11298    // Admission checks both the full guard set and the subset contributed by
11299    // incomplete conditional families. A path subsumes another only when both
11300    // sets are subsets: equal full guards can have different proof obligations
11301    // when one route uses a complete #if/#else and another a lone #if.
11302    // Keep the minimal pairs to avoid enumerating dense guard lattices (#2365).
11303    let mut expanded: HashMap<(ProjectFile, usize), Vec<PreprocessorGuardEnvironment>> =
11304        HashMap::default();
11305    while let Some((current_file, activation_byte, path)) = pending.pop() {
11306        let guard_sets = expanded
11307            .entry((current_file.clone(), activation_byte))
11308            .or_default();
11309        if guard_sets.iter().any(|existing| existing.is_subset(&path)) {
11310            continue;
11311        }
11312        let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
11313            .drain(..)
11314            .partition(|existing| path.is_subset(existing));
11315        *guard_sets = kept;
11316        guard_sets.push(path.clone());
11317        if !evicted.is_empty()
11318            && let Some(projections) = projections_by_source.get_mut(&current_file)
11319        {
11320            projections.retain(|projection| {
11321                projection.activation_byte != activation_byte
11322                    || !evicted.iter().any(|existing| {
11323                        existing.all == projection.required_guards
11324                            && existing.partial == projection.partial_guards
11325                    })
11326            });
11327        }
11328        on_state();
11329
11330        // A fresh minimal set has no equal in the store: equality would have
11331        // been caught by the subset check above.
11332        projections_by_source
11333            .entry(current_file.clone())
11334            .or_default()
11335            .push(ConditionalIncludeProjection {
11336                activation_byte,
11337                required_guards: path.all.clone(),
11338                partial_guards: path.partial.clone(),
11339            });
11340
11341        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
11342            continue;
11343        };
11344        let mut nodes = vec![current_prepared.tree().root_node()];
11345        while let Some(node) = nodes.pop() {
11346            if node.kind() == "preproc_include" {
11347                let Some(include_guards) = include_directive_guard_requirements(
11348                    node,
11349                    current_prepared.source(),
11350                    reference_is_c,
11351                    language_guard_is_stable,
11352                ) else {
11353                    continue;
11354                };
11355                let Some(reached) = path.merged(&include_guards) else {
11356                    continue;
11357                };
11358                for (include, _) in
11359                    include_path_from_node(node, current_prepared.source()).into_iter()
11360                {
11361                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
11362                        &current_file,
11363                        include,
11364                        include_targets,
11365                    )) else {
11366                        continue;
11367                    };
11368                    pending.push((target, activation_byte, reached.clone()));
11369                }
11370                continue;
11371            }
11372            push_named_children_reversed(node, &mut nodes);
11373        }
11374    }
11375
11376    projections_by_source
11377        .into_iter()
11378        .map(|(source, mut projections)| {
11379            projections.sort_by_key(|projection| projection.activation_byte);
11380            (source, Arc::from(projections))
11381        })
11382        .collect()
11383}
11384
11385/// Decide one conditional include target without materializing every source
11386/// reached by every guard combination. A path `admission` already rejects
11387/// cannot become admissible after adding nested include guards -- both rules
11388/// only ever add requirements -- so discard it before expanding the next
11389/// header.
11390#[allow(clippy::too_many_arguments)]
11391fn find_conditional_include_projection_for_source(
11392    cpp: &dyn CppSource,
11393    token: QueryToken<'_>,
11394    file: &ProjectFile,
11395    prepared: &PreparedSyntaxTree,
11396    donor_source: &ProjectFile,
11397    admission: IncludePathAdmission,
11398    reference_guards: Option<&HashSet<PreprocessorGuard>>,
11399    reference_byte: usize,
11400    language_guard_is_stable: &dyn Fn() -> bool,
11401    on_state: &dyn Fn(),
11402) -> bool {
11403    let Some(reference_guards) = reference_guards else {
11404        return false;
11405    };
11406    let reference_is_c = reference_uses_c_semantics(cpp, file);
11407    let include_targets = cpp.include_target_index();
11408    let mut pending = Vec::new();
11409    let mut nodes = vec![prepared.tree().root_node()];
11410    while let Some(node) = nodes.pop() {
11411        if node.kind() == "preproc_include" {
11412            let Some(required_guards) = include_directive_guard_requirements(
11413                node,
11414                prepared.source(),
11415                reference_is_c,
11416                language_guard_is_stable,
11417            ) else {
11418                continue;
11419            };
11420            if node.end_byte() > reference_byte
11421                || !admission.admits(
11422                    &required_guards.all,
11423                    &required_guards.partial,
11424                    Some(reference_guards),
11425                )
11426            {
11427                continue;
11428            }
11429            for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
11430                let Some(target) = unique_include_target(resolve_include_targets_with_index(
11431                    file,
11432                    include,
11433                    include_targets,
11434                )) else {
11435                    continue;
11436                };
11437                if &target == donor_source {
11438                    return true;
11439                }
11440                pending.push((target, required_guards.clone()));
11441            }
11442            continue;
11443        }
11444        push_named_children_reversed(node, &mut nodes);
11445    }
11446
11447    let mut expanded: HashMap<ProjectFile, Vec<HashSet<PreprocessorGuard>>> = HashMap::default();
11448    while let Some((current_file, path)) = pending.pop() {
11449        let guard_sets = expanded.entry(current_file.clone()).or_default();
11450        if guard_sets.contains(&path.all) {
11451            continue;
11452        }
11453        guard_sets.push(path.all.clone());
11454        on_state();
11455
11456        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
11457            continue;
11458        };
11459        let mut nodes = vec![current_prepared.tree().root_node()];
11460        while let Some(node) = nodes.pop() {
11461            if node.kind() == "preproc_include" {
11462                let Some(include_guards) = include_directive_guard_requirements(
11463                    node,
11464                    current_prepared.source(),
11465                    reference_is_c,
11466                    language_guard_is_stable,
11467                ) else {
11468                    continue;
11469                };
11470                let Some(reached) = path.merged(&include_guards) else {
11471                    continue;
11472                };
11473                if !admission.admits(&reached.all, &reached.partial, Some(reference_guards)) {
11474                    continue;
11475                }
11476                for (include, _) in
11477                    include_path_from_node(node, current_prepared.source()).into_iter()
11478                {
11479                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
11480                        &current_file,
11481                        include,
11482                        include_targets,
11483                    )) else {
11484                        continue;
11485                    };
11486                    if &target == donor_source {
11487                        return true;
11488                    }
11489                    pending.push((target, reached.clone()));
11490                }
11491                continue;
11492            }
11493            push_named_children_reversed(node, &mut nodes);
11494        }
11495    }
11496    false
11497}
11498
11499/// Whether `translation_unit`'s unconditional `#include` closure reaches
11500/// `header`, directly or through any chain of headers.
11501///
11502/// The include-closure question asked on its own, for
11503/// [`crate::identity::cpp_header_body_files_are_related`]. The walk resolves
11504/// each include the way visibility does -- to a unique target or to nothing --
11505/// so a duplicated basename relates nothing, and it is memoized per file pair
11506/// and language-proof state on the analyzer.
11507///
11508/// The reference position is `translation_unit` itself: the question is
11509/// whether that unit compiles the header, so that unit's own dialect and
11510/// preprocessor context govern the walk.
11511pub fn cpp_include_closure_reaches(
11512    cpp: &dyn CppSource,
11513    token: QueryToken<'_>,
11514    translation_unit: &ProjectFile,
11515    header: &ProjectFile,
11516) -> bool {
11517    let language_guard_stable = OnceCell::new();
11518    unconditional_include_reaches(
11519        cpp,
11520        token,
11521        translation_unit,
11522        header,
11523        translation_unit,
11524        &mut HashSet::default(),
11525        &|| {
11526            *language_guard_stable.get_or_init(|| {
11527                let mut macro_events =
11528                    |file: &ProjectFile| collect_macro_events(cpp, token, file).into_boxed_slice();
11529                include_language_guard_is_stable_with(
11530                    cpp,
11531                    token,
11532                    translation_unit,
11533                    &mut macro_events,
11534                    &mut |_: &ProjectFile, prepared: &PreparedSyntaxTree| {
11535                        collect_structured_include_facts(prepared)
11536                    },
11537                )
11538            })
11539        },
11540    )
11541}
11542
11543fn unconditional_include_reaches(
11544    cpp: &dyn CppSource,
11545    token: QueryToken<'_>,
11546    first: &ProjectFile,
11547    donor_source: &ProjectFile,
11548    reference_file: &ProjectFile,
11549    known_missing: &mut HashSet<ProjectFile>,
11550    language_guard_is_stable: &dyn Fn() -> bool,
11551) -> bool {
11552    if first == donor_source {
11553        return true;
11554    }
11555    if known_missing.contains(first) {
11556        return false;
11557    }
11558    let reference_is_c = reference_file
11559        .rel_path()
11560        .extension()
11561        .and_then(|extension| extension.to_str())
11562        == Some("c");
11563    if let Some(reaches) =
11564        cpp.cached_unconditional_include_reachability(first, donor_source, reference_file)
11565    {
11566        return reaches;
11567    }
11568    let mut visited = HashSet::default();
11569    let mut files = vec![first.clone()];
11570    let include_targets = cpp.include_target_index();
11571    while let Some(file) = files.pop() {
11572        if file == *donor_source {
11573            cpp.cache_unconditional_include_reachability(first, donor_source, reference_file, true);
11574            return true;
11575        }
11576        if known_missing.contains(&file) || !visited.insert(file.clone()) {
11577            continue;
11578        }
11579        let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11580            continue;
11581        };
11582        // One cursor for the whole-file walk; see `named_children_iter` (#3097).
11583        let mut cursor = prepared.tree().walk();
11584        let mut nodes = vec![prepared.tree().root_node()];
11585        while let Some(node) = nodes.pop() {
11586            if node.kind() == "preproc_include" {
11587                let Some(required_guards) = include_directive_guard_requirements(
11588                    node,
11589                    prepared.source(),
11590                    reference_is_c,
11591                    language_guard_is_stable,
11592                ) else {
11593                    continue;
11594                };
11595                if required_guards.all.is_empty() && required_guards.partial.is_empty() {
11596                    for (include, _) in include_path_from_node(node, prepared.source()).into_iter()
11597                    {
11598                        if let Some(target) = unique_include_target(
11599                            resolve_include_targets_with_index(&file, include, include_targets),
11600                        ) {
11601                            files.push(target);
11602                        }
11603                    }
11604                }
11605                continue;
11606            }
11607            let first_pushed = nodes.len();
11608            nodes.extend(node.named_children(&mut cursor));
11609            nodes[first_pushed..].reverse();
11610        }
11611    }
11612    known_missing.extend(visited);
11613    cpp.cache_unconditional_include_reachability(first, donor_source, reference_file, false);
11614    false
11615}
11616
11617fn include_language_guard_is_stable_with<MacroEvents, IncludeFacts>(
11618    cpp: &dyn CppSource,
11619    token: QueryToken<'_>,
11620    file: &ProjectFile,
11621    mut macro_events: MacroEvents,
11622    mut include_facts: IncludeFacts,
11623) -> bool
11624where
11625    MacroEvents: FnMut(&ProjectFile) -> Box<[MacroEvent]>,
11626    IncludeFacts: FnMut(&ProjectFile, &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]>,
11627{
11628    // A header has no intrinsic dialect. An opaque incoming include can hide
11629    // its compiler context from reverse include attribution. Only a
11630    // translation unit can supply the source-only language fact.
11631    if !crate::imports::is_cpp_translation_unit(file) {
11632        return false;
11633    }
11634    // When compiler data exists, keep its existing authoritative guard proof.
11635    // Source-only dialect facts cannot reconstruct -D/-U ordering.
11636    if !cpp.compile_contexts_for(file).is_empty()
11637        || cpp
11638            .reaching_translation_units(file)
11639            .iter()
11640            .any(|tu| !cpp.compile_contexts_for(tu).is_empty())
11641    {
11642        return false;
11643    }
11644    let mut pending = vec![file.clone()];
11645    let mut visited = HashSet::default();
11646    while let Some(file) = pending.pop() {
11647        if !visited.insert(file.clone()) {
11648            continue;
11649        }
11650        let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11651            return false;
11652        };
11653        if macro_events(&file).iter().any(|event| match event {
11654            MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
11655                name == "__cplusplus"
11656            }
11657            MacroEvent::Invalidate { .. } => true,
11658            MacroEvent::Include { targets, .. } => targets.is_empty(),
11659        }) {
11660            return false;
11661        }
11662        // Unlike indexed macro-binding replay, intrinsic-macro proof also
11663        // needs every include source: an opaque header may change it.
11664        for fact in include_facts(&file, prepared.as_ref()).iter() {
11665            let targets =
11666                resolve_include_targets_with_index(&file, &fact.path, cpp.include_target_index());
11667            if targets.is_empty() {
11668                return false;
11669            }
11670            pending.extend(targets);
11671        }
11672    }
11673    true
11674}
11675
11676fn declaration_guard_requirements(
11677    analyzer: &CppGraphSource<'_>,
11678    cpp: &dyn CppSource,
11679    candidate: &CodeUnit,
11680) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
11681    if cpp
11682        .prepared_syntax(analyzer.token, candidate.source())
11683        .is_none()
11684    {
11685        return Vec::new();
11686    }
11687    analyzer
11688        .declaration_source_occurrences(candidate)
11689        .into_iter()
11690        .flatten()
11691        .filter_map(|occurrence| {
11692            let guards = cpp_guard_set_to_runtime(occurrence.fact.guard_requirements.as_ref()?)?;
11693            Some((occurrence.range.start_byte, guards))
11694        })
11695        .collect()
11696}
11697
11698/// One declaration range's preprocessor facts, as a visible-at-a-reference
11699/// question reads them.
11700struct DeclarationGuardSite {
11701    /// The byte at which the range introduces the name.
11702    byte: usize,
11703    /// The preprocessor requirements the range's declaration stands under.
11704    guards: HashSet<PreprocessorGuard>,
11705    /// Whether the range is one branch of completed `#if`/`#else` families
11706    /// that all close before the reference.
11707    completed_family_before_reference: bool,
11708}
11709
11710/// The per-range facts behind [`declaration_guard_requirements`], plus the
11711/// reference-relative fact the completed-family rule asks for.
11712fn declaration_guard_sites(
11713    analyzer: &CppGraphSource<'_>,
11714    cpp: &dyn CppSource,
11715    candidate: &CodeUnit,
11716    reference_byte: usize,
11717) -> Vec<DeclarationGuardSite> {
11718    let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
11719        return Vec::new();
11720    };
11721    let root = prepared.tree().root_node();
11722    analyzer
11723        .declaration_source_occurrences(candidate)
11724        .into_iter()
11725        .flatten()
11726        .filter_map(|occurrence| {
11727            let guards = cpp_guard_set_to_runtime(occurrence.fact.guard_requirements.as_ref()?)?;
11728            Some(DeclarationGuardSite {
11729                byte: occurrence.range.start_byte,
11730                guards,
11731                completed_family_before_reference: {
11732                    root.descendant_for_byte_range(
11733                        occurrence.range.start_byte,
11734                        occurrence.range.end_byte,
11735                    )
11736                    .is_some_and(|node| {
11737                        declaration_follows_completed_family_before_reference(
11738                            node,
11739                            prepared.source(),
11740                            reference_byte,
11741                        )
11742                    })
11743                },
11744            })
11745        })
11746        .collect()
11747}
11748
11749/// Whether every conditional enclosing `node` is a completed
11750/// `#if`/`#elif`/`#else` family that closes before `reference_byte`.
11751///
11752/// A completed family is an exhaustive case analysis: every configuration
11753/// takes exactly one of its branches. A reference sited below the whole family
11754/// is compiled in a configuration that either takes the declaration's branch,
11755/// in which case the declaration is what the name resolves to there, or takes
11756/// another branch, in which case the declaration is simply not part of that
11757/// configuration. Nothing in the reference's own position settles the family,
11758/// so the declaration is one resolution the source presents at the reference
11759/// rather than a contradiction of it (#3297).
11760///
11761/// A lone `#if` is not a case analysis: nothing in the source says any
11762/// configuration defines its macro, so a declaration under one stays
11763/// unprovable and the implication rule keeps it out. Neither does a family
11764/// whose end is not behind the reference: a reference inside the other branch
11765/// contradicts the declaration, and a reference inside the declaration's own
11766/// branch is already covered by implication.
11767fn declaration_follows_completed_family_before_reference(
11768    node: Node<'_>,
11769    source: &str,
11770    reference_byte: usize,
11771) -> bool {
11772    callable_guard_completion_byte(node, source).is_some_and(|end| end > 0 && end <= reference_byte)
11773}
11774
11775/// Producer-owned completion boundary for all enclosing conditional families.
11776/// Zero is the neutral boundary for a declaration outside those families.
11777pub(crate) fn callable_guard_completion_byte(node: Node<'_>, source: &str) -> Option<usize> {
11778    let mut cache = PreprocessorContextCache::default();
11779    callable_guard_completion_byte_with_cache(node, source, &mut cache)
11780}
11781
11782pub(crate) fn callable_guard_completion_byte_with_cache(
11783    node: Node<'_>,
11784    source: &str,
11785    cache: &mut PreprocessorContextCache,
11786) -> Option<usize> {
11787    let mut completion_byte = 0;
11788    let mut ancestor = node.parent();
11789    while let Some(conditional) = ancestor {
11790        if matches!(
11791            conditional.kind(),
11792            "preproc_if" | "preproc_ifdef" | "preproc_elif"
11793        ) && !is_file_covering_include_guard(conditional, source)
11794            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11795            && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
11796        {
11797            let family = preprocessor_conditional_family_root(conditional);
11798            if !preprocessor_conditional_family_has_terminal_else(family, cache) {
11799                return None;
11800            }
11801            completion_byte = completion_byte.max(family.end_byte());
11802        }
11803        ancestor = conditional.parent();
11804    }
11805    Some(completion_byte)
11806}
11807
11808fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
11809    analyzer
11810        .ranges(candidate)
11811        .into_iter()
11812        .map(|range| range.start_byte)
11813        .min()
11814}
11815
11816/// The macro names every configuration in `contexts` defines -- the fact set
11817/// one file's compile-database coverage proves (#2011). `None` when the
11818/// database has no entry for the file, which is different from an empty
11819/// intersection: no entry means no coverage, while an empty intersection is
11820/// covered-and-proves-nothing.
11821fn context_fact_names(contexts: &[CppCompileContext]) -> Option<HashSet<String>> {
11822    let (first, rest) = contexts.split_first()?;
11823    Some(
11824        first
11825            .defined_macros
11826            .iter()
11827            .filter(|name| {
11828                rest.iter()
11829                    .all(|context| context.defined_macros.contains(*name))
11830            })
11831            .cloned()
11832            .collect(),
11833    )
11834}
11835
11836pub fn guard_requirements_hold_at_reference(
11837    required: &HashSet<PreprocessorGuard>,
11838    reference: Option<&HashSet<PreprocessorGuard>>,
11839) -> bool {
11840    reference.is_some_and(|active| {
11841        required
11842            .iter()
11843            .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
11844    })
11845}
11846
11847fn preprocessor_guard_holds_at_reference(
11848    required: &PreprocessorGuard,
11849    active: &HashSet<PreprocessorGuard>,
11850) -> bool {
11851    if active.contains(required) {
11852        return true;
11853    }
11854    let active_expression =
11855        BooleanGuardExpression::all(active.iter().map(PreprocessorGuard::as_boolean_expression));
11856    active_expression.implies(&required.as_boolean_expression())
11857}
11858
11859/// Candidate discovery rule: two guard sets are compatible when neither
11860/// contradicts the other. Compatibility never establishes proven visibility;
11861/// that requires `guard_requirements_hold_at_reference`.
11862fn guards_compatible_at_reference(
11863    declaration: &HashSet<PreprocessorGuard>,
11864    reference: Option<&HashSet<PreprocessorGuard>>,
11865) -> bool {
11866    reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
11867}
11868
11869pub(crate) fn preprocessor_conditional_family_node(node: Node<'_>) -> Option<Node<'_>> {
11870    let mut ancestor = Some(node);
11871    while let Some(current) = ancestor {
11872        if is_cpp_preprocessor_conditional(current)
11873            && preprocessor_conditional_contains_descendant(current, node)
11874        {
11875            return Some(preprocessor_conditional_family_root(current));
11876        }
11877        ancestor = current.parent();
11878    }
11879    None
11880}
11881
11882pub(crate) fn preprocessor_conditional_family_for_declaration_with_cache<'tree>(
11883    node: Node<'tree>,
11884    cache: &mut PreprocessorContextCache,
11885) -> Option<Node<'tree>> {
11886    let mut ancestor = node.parent();
11887    while let Some(current) = ancestor {
11888        if is_cpp_preprocessor_conditional(current)
11889            && preprocessor_conditional_contains_descendant_with_cache(current, node, cache)
11890        {
11891            let family = preprocessor_conditional_family_root(current);
11892            if preprocessor_conditional_family_has_terminal_else(family, cache) {
11893                return Some(family);
11894            }
11895        }
11896        ancestor = current.parent();
11897    }
11898    None
11899}
11900
11901fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
11902    while let Some(parent) = conditional.parent() {
11903        let is_alternative = parent
11904            .child_by_field_name("alternative")
11905            .is_some_and(|alternative| {
11906                alternative.start_byte() == conditional.start_byte()
11907                    && alternative.end_byte() == conditional.end_byte()
11908            });
11909        if !is_alternative {
11910            break;
11911        }
11912        conditional = parent;
11913    }
11914    conditional
11915}
11916
11917fn preprocessor_conditional_family_has_terminal_else(
11918    mut conditional: Node<'_>,
11919    cache: &mut PreprocessorContextCache,
11920) -> bool {
11921    loop {
11922        let Some(alternative) = conditional.child_by_field_name("alternative") else {
11923            return cache
11924                .displaced_branch(conditional)
11925                .is_some_and(|directive| directive.is_else);
11926        };
11927        match alternative.kind() {
11928            "preproc_else" => return true,
11929            "preproc_elif" => conditional = alternative,
11930            _ => return false,
11931        }
11932    }
11933}
11934
11935/// The undecidable preprocessor guards an `#include` directive stands under,
11936/// or `None` when its conditional context keeps the directive out of a
11937/// translation unit of the reference's language: a false constant, a
11938/// condition this analyzer cannot read, or a `__cplusplus` branch that
11939/// language never takes.
11940///
11941/// This is [`callable_declaration_guard_requirements`]'s rule for the include
11942/// edge, and the language test is what keeps
11943/// [`IncludePathAdmission::Compatible`] honest: an empty active guard set
11944/// never implies `defined(__cplusplus)`, but it never contradicts it either,
11945/// so without this a `.c` file would reach the C++ arm of every
11946/// `#ifdef __cplusplus` / `#else` include pair.
11947fn include_directive_guard_requirements(
11948    node: Node<'_>,
11949    source: &str,
11950    reference_is_c: bool,
11951    language_guard_is_stable: &dyn Fn() -> bool,
11952) -> Option<PreprocessorGuardEnvironment> {
11953    let mut required = preprocessor_guard_environment_by_family(node, source)?;
11954    let excluded_by_language = required
11955        .all
11956        .iter()
11957        .any(|guard| preprocessor_guard_excludes_language(guard, reference_is_c));
11958    if excluded_by_language {
11959        return None;
11960    }
11961    let is_language_guard = |guard: &PreprocessorGuard| {
11962        matches!(guard, PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
11963            if name == "__cplusplus")
11964    };
11965    // The dialect decides this guard only when the admitted source closure
11966    // cannot mutate the builtin. Unknown include sources keep it undecided.
11967    if required.all.iter().any(is_language_guard) && language_guard_is_stable() {
11968        for guards in [&mut required.all, &mut required.partial] {
11969            guards.retain(|guard| !is_language_guard(guard));
11970        }
11971    }
11972    Some(required)
11973}
11974
11975fn preprocessor_guard_excludes_language(guard: &PreprocessorGuard, reference_is_c: bool) -> bool {
11976    match guard {
11977        PreprocessorGuard::Defined(name) => reference_is_c && name == "__cplusplus",
11978        PreprocessorGuard::Undefined(name) => !reference_is_c && name == "__cplusplus",
11979        _ => false,
11980    }
11981}
11982
11983pub fn preprocessor_guard_environment(
11984    node: Node<'_>,
11985    source: &str,
11986) -> Option<HashSet<PreprocessorGuard>> {
11987    let mut cache = PreprocessorContextCache::default();
11988    preprocessor_guard_environment_with_cache(node, source, &mut cache)
11989}
11990
11991pub(crate) fn preprocessor_guard_environment_with_cache(
11992    node: Node<'_>,
11993    source: &str,
11994    cache: &mut PreprocessorContextCache,
11995) -> Option<HashSet<PreprocessorGuard>> {
11996    preprocessor_guard_environment_by_family_with_cache(node, source, cache)
11997        .map(|environment| environment.all)
11998}
11999
12000/// The preprocessor guards `node` stands under, split by whether the
12001/// conditional that contributes each one belongs to a family that covers every
12002/// configuration.
12003///
12004/// A complete `#if`/`#else` or `#if`/`#elif`/`#else` family is a case analysis
12005/// every configuration takes exactly one branch of, which is why the resolver
12006/// already reads a declaration through one (`exhaustive_guard_family_activation`,
12007/// `complementary_same_fqn_type_declarations`). A lone `#if` is not: nothing in
12008/// the source says any configuration defines that macro, so only the build can
12009/// decide it, and it stays in `partial`.
12010#[derive(Clone)]
12011struct PreprocessorGuardEnvironment {
12012    all: HashSet<PreprocessorGuard>,
12013    partial: HashSet<PreprocessorGuard>,
12014}
12015
12016impl PreprocessorGuardEnvironment {
12017    fn is_subset(&self, other: &Self) -> bool {
12018        self.all.is_subset(&other.all) && self.partial.is_subset(&other.partial)
12019    }
12020
12021    /// The environment of a path standing under both `self` and `other`, or
12022    /// `None` when the two contradict. A guard a lone `#if` contributes on
12023    /// either side stays undecided for the whole path.
12024    fn merged(&self, other: &Self) -> Option<Self> {
12025        Some(Self {
12026            all: merge_preprocessor_guards(&self.all, &other.all)?,
12027            partial: self.partial.union(&other.partial).cloned().collect(),
12028        })
12029    }
12030}
12031
12032fn preprocessor_guard_environment_by_family(
12033    node: Node<'_>,
12034    source: &str,
12035) -> Option<PreprocessorGuardEnvironment> {
12036    let mut cache = PreprocessorContextCache::default();
12037    preprocessor_guard_environment_by_family_with_cache(node, source, &mut cache)
12038}
12039
12040fn preprocessor_guard_environment_by_family_with_cache(
12041    node: Node<'_>,
12042    source: &str,
12043    cache: &mut PreprocessorContextCache,
12044) -> Option<PreprocessorGuardEnvironment> {
12045    let mut all = HashSet::default();
12046    let mut partial = HashSet::default();
12047    let mut ancestor = node.parent();
12048    while let Some(conditional) = ancestor {
12049        if matches!(
12050            conditional.kind(),
12051            "preproc_if" | "preproc_ifdef" | "preproc_elif"
12052        ) && !is_file_covering_include_guard(conditional, source)
12053            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
12054            && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
12055        {
12056            let guard = preprocessor_guard_for_descendant(conditional, node, source, cache)?;
12057            match guard {
12058                PreprocessorGuard::Constant(true) => {
12059                    ancestor = conditional.parent();
12060                    continue;
12061                }
12062                PreprocessorGuard::Constant(false) => return None,
12063                _ => {}
12064            }
12065            if all.contains(&guard.negated()) {
12066                return None;
12067            }
12068            if !preprocessor_conditional_family_has_terminal_else(
12069                preprocessor_conditional_family_root(conditional),
12070                cache,
12071            ) {
12072                partial.insert(guard.clone());
12073            }
12074            all.insert(guard);
12075        }
12076        ancestor = conditional.parent();
12077    }
12078    if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
12079        match guard {
12080            PreprocessorGuard::Constant(true) => {}
12081            PreprocessorGuard::Constant(false) => return None,
12082            _ => {
12083                if all.contains(&guard.negated()) {
12084                    return None;
12085                }
12086                // The family of a conditional the parser split across a
12087                // statement boundary is not readable from the tree, so it
12088                // counts as undecided.
12089                partial.insert(guard.clone());
12090                all.insert(guard);
12091            }
12092        }
12093    }
12094    Some(PreprocessorGuardEnvironment { all, partial })
12095}
12096
12097pub(crate) fn callable_preprocessor_guard_requirements_with_cache(
12098    node: Node<'_>,
12099    source: &str,
12100    cache: &mut PreprocessorContextCache,
12101) -> Option<HashSet<PreprocessorGuard>> {
12102    let mut guards = HashSet::default();
12103    for guard in callable_preprocessor_ancestor_guards_with_cache(node, source, cache)? {
12104        match guard {
12105            PreprocessorGuard::Constant(true) => {}
12106            PreprocessorGuard::Constant(false) => return None,
12107            _ => {
12108                if guards.contains(&guard.negated()) {
12109                    return None;
12110                }
12111                guards.insert(guard);
12112            }
12113        }
12114    }
12115    Some(guards)
12116}
12117
12118fn callable_preprocessor_ancestor_guards(
12119    node: Node<'_>,
12120    source: &str,
12121) -> Option<Vec<PreprocessorGuard>> {
12122    let mut cache = PreprocessorContextCache::default();
12123    callable_preprocessor_ancestor_guards_with_cache(node, source, &mut cache)
12124}
12125
12126fn callable_preprocessor_ancestor_guards_with_cache(
12127    node: Node<'_>,
12128    source: &str,
12129    cache: &mut PreprocessorContextCache,
12130) -> Option<Vec<PreprocessorGuard>> {
12131    let mut guards = Vec::new();
12132    let mut ancestor = node.parent();
12133    while let Some(conditional) = ancestor {
12134        if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
12135            && !is_file_covering_include_guard(conditional, source)
12136            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
12137            && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
12138        {
12139            guards.push(preprocessor_guard_for_descendant(
12140                conditional,
12141                node,
12142                source,
12143                cache,
12144            )?);
12145        }
12146        ancestor = conditional.parent();
12147    }
12148    Some(guards)
12149}
12150
12151fn fragmented_statement_preprocessor_guard(
12152    descendant: Node<'_>,
12153    source: &str,
12154) -> Option<PreprocessorGuard> {
12155    // A conditional that starts before `} else if (...) {` crosses the
12156    // enclosing statement's grammar boundary. tree-sitter leaves its opener
12157    // as a `preproc_if` with a missing terminator in the consequence and
12158    // reparses the real `#endif` as a `preproc_call` in the alternative. Pair
12159    // those structured nodes before restoring the guard to intervening uses.
12160    let mut ancestor = descendant.parent();
12161    while let Some(statement) = ancestor {
12162        if statement.kind() == "if_statement"
12163            && let (Some(consequence), Some(alternative)) = (
12164                statement.child_by_field_name("consequence"),
12165                statement.child_by_field_name("alternative"),
12166            )
12167            && alternative.start_byte() <= descendant.start_byte()
12168            && descendant.end_byte() <= alternative.end_byte()
12169        {
12170            let mut cursor = consequence.walk();
12171            let openers = consequence
12172                .named_children(&mut cursor)
12173                .filter(|child| {
12174                    matches!(child.kind(), "preproc_if" | "preproc_ifdef")
12175                        && child
12176                            .child(child.child_count().saturating_sub(1))
12177                            .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
12178                })
12179                .collect::<Vec<_>>();
12180            if openers.len() != 1 {
12181                ancestor = statement.parent();
12182                continue;
12183            }
12184
12185            let mut terminators = Vec::new();
12186            let mut stack = vec![alternative];
12187            while let Some(node) = stack.pop() {
12188                if node.kind() == "preproc_call"
12189                    && node.start_byte() >= descendant.end_byte()
12190                    && node
12191                        .child_by_field_name("directive")
12192                        .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
12193                {
12194                    terminators.push(node);
12195                    continue;
12196                }
12197                push_named_children_reversed(node, &mut stack);
12198            }
12199            if terminators.len() == 1 {
12200                return simple_preprocessor_guard(openers[0], source);
12201            }
12202        }
12203        ancestor = statement.parent();
12204    }
12205    None
12206}
12207
12208pub(crate) fn preprocessor_guard_for_descendant(
12209    conditional: Node<'_>,
12210    descendant: Node<'_>,
12211    source: &str,
12212    cache: &mut PreprocessorContextCache,
12213) -> Option<PreprocessorGuard> {
12214    let guard = simple_preprocessor_guard(conditional, source)?;
12215    match preprocessor_branch_of_descendant(conditional, descendant, cache) {
12216        PreprocessorBranch::Consequence => Some(guard),
12217        // Tree-sitter nests an `#elif` chain in each `alternative` field. A
12218        // descendant in any later branch must first exclude the parent branch,
12219        // then collect the nested `preproc_elif` guard from its own ancestor.
12220        PreprocessorBranch::Structured(alternative)
12221            if matches!(alternative.kind(), "preproc_else" | "preproc_elif") =>
12222        {
12223            Some(guard.negated())
12224        }
12225        PreprocessorBranch::DisplacedElse => Some(guard.negated()),
12226        PreprocessorBranch::Structured(_) | PreprocessorBranch::Unreadable => None,
12227    }
12228}
12229
12230/// The branch of `conditional` that holds `descendant`.
12231enum PreprocessorBranch<'tree> {
12232    Consequence,
12233    /// Inside the structured `alternative` field.
12234    Structured(Node<'tree>),
12235    /// After an `#else` the parser displaced into an `ERROR` of the
12236    /// consequence ([`cpp_displaced_preprocessor_branch`]).
12237    DisplacedElse,
12238    /// After a displaced `#elif`, whose condition has no structured node.
12239    Unreadable,
12240}
12241
12242fn preprocessor_branch_of_descendant<'tree>(
12243    conditional: Node<'tree>,
12244    descendant: Node<'_>,
12245    cache: &mut PreprocessorContextCache,
12246) -> PreprocessorBranch<'tree> {
12247    if let Some(alternative) =
12248        conditional
12249            .child_by_field_name("alternative")
12250            .filter(|alternative| {
12251                alternative.start_byte() <= descendant.start_byte()
12252                    && descendant.end_byte() <= alternative.end_byte()
12253            })
12254    {
12255        return PreprocessorBranch::Structured(alternative);
12256    }
12257    match cache.displaced_branch(conditional) {
12258        Some(directive) if directive.end_byte <= descendant.start_byte() => {
12259            if directive.is_else {
12260                PreprocessorBranch::DisplacedElse
12261            } else {
12262                PreprocessorBranch::Unreadable
12263            }
12264        }
12265        _ => PreprocessorBranch::Consequence,
12266    }
12267}
12268
12269pub(crate) fn preprocessor_conditional_contains_descendant(
12270    conditional: Node<'_>,
12271    descendant: Node<'_>,
12272) -> bool {
12273    cpp_displaced_preprocessor_boundary(conditional)
12274        .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
12275}
12276
12277fn preprocessor_conditional_contains_descendant_with_cache(
12278    conditional: Node<'_>,
12279    descendant: Node<'_>,
12280    cache: &mut PreprocessorContextCache,
12281) -> bool {
12282    cache
12283        .displaced_boundary_end(conditional)
12284        .is_none_or(|end_byte| descendant.end_byte() <= end_byte)
12285}
12286
12287pub fn merge_preprocessor_guards(
12288    left: &HashSet<PreprocessorGuard>,
12289    right: &HashSet<PreprocessorGuard>,
12290) -> Option<HashSet<PreprocessorGuard>> {
12291    let mut merged = left.clone();
12292    for guard in right {
12293        let boolean_negation = guard.as_boolean_expression().negated();
12294        if merged.contains(&guard.negated())
12295            || merged
12296                .iter()
12297                .map(PreprocessorGuard::as_boolean_expression)
12298                .any(|existing| existing == boolean_negation)
12299        {
12300            return None;
12301        }
12302        merged.insert(guard.clone());
12303    }
12304    Some(merged)
12305}
12306
12307fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
12308    if conditional.kind() == "preproc_ifdef" {
12309        let name = conditional.child_by_field_name("name")?;
12310        let name = node_text(name, source).to_string();
12311        return match conditional.child(0)?.kind() {
12312            "#ifdef" => Some(PreprocessorGuard::Defined(name)),
12313            "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
12314            _ => None,
12315        };
12316    }
12317    let condition = conditional.child_by_field_name("condition")?;
12318    simple_preprocessor_expression_guard(condition, source).or_else(|| {
12319        Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
12320            node_text(condition, source),
12321        )))
12322    })
12323}
12324
12325fn simple_preprocessor_expression_guard(
12326    expression: Node<'_>,
12327    source: &str,
12328) -> Option<PreprocessorGuard> {
12329    match expression.kind() {
12330        "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
12331            node_text(expression, source).to_string(),
12332        ))),
12333        "number_literal" => match node_text(expression, source).trim() {
12334            "0" => Some(PreprocessorGuard::Constant(false)),
12335            "1" => Some(PreprocessorGuard::Constant(true)),
12336            _ => None,
12337        },
12338        "preproc_defined" => {
12339            let identifier = (0..expression.named_child_count())
12340                .filter_map(|index| expression.named_child(index))
12341                .find(|child| child.kind() == "identifier")?;
12342            Some(PreprocessorGuard::Defined(
12343                node_text(identifier, source).to_string(),
12344            ))
12345        }
12346        "unary_expression"
12347            if expression
12348                .child_by_field_name("operator")
12349                .is_some_and(|operator| operator.kind() == "!") =>
12350        {
12351            simple_preprocessor_expression_guard(
12352                expression.child_by_field_name("argument")?,
12353                source,
12354            )
12355            .map(|guard| guard.negated())
12356        }
12357        "parenthesized_expression" => (0..expression.named_child_count())
12358            .filter_map(|index| expression.named_child(index))
12359            .next()
12360            .and_then(|child| simple_preprocessor_expression_guard(child, source)),
12361        "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
12362            expression, source,
12363        ))),
12364        _ => None,
12365    }
12366}
12367
12368fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
12369    match expression.kind() {
12370        "number_literal" => match node_text(expression, source).trim() {
12371            "0" => BooleanGuardExpression::Constant(false),
12372            "1" => BooleanGuardExpression::Constant(true),
12373            _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12374                expression, source,
12375            ))),
12376        },
12377        "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
12378        "preproc_defined" => {
12379            let identifier = (0..expression.named_child_count())
12380                .filter_map(|index| expression.named_child(index))
12381                .find(|child| child.kind() == "identifier");
12382            identifier.map_or_else(
12383                || {
12384                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12385                        expression, source,
12386                    )))
12387                },
12388                |identifier| {
12389                    BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
12390                },
12391            )
12392        }
12393        "unary_expression"
12394            if expression
12395                .child_by_field_name("operator")
12396                .is_some_and(|operator| operator.kind() == "!") =>
12397        {
12398            expression.child_by_field_name("argument").map_or_else(
12399                || {
12400                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12401                        expression, source,
12402                    )))
12403                },
12404                |argument| boolean_preprocessor_expression(argument, source).negated(),
12405            )
12406        }
12407        "parenthesized_expression" => (0..expression.named_child_count())
12408            .filter_map(|index| expression.named_child(index))
12409            .next()
12410            .map_or_else(
12411                || {
12412                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12413                        expression, source,
12414                    )))
12415                },
12416                |child| boolean_preprocessor_expression(child, source),
12417            ),
12418        "binary_expression" => {
12419            let operands = || {
12420                Some((
12421                    boolean_preprocessor_expression(
12422                        expression.child_by_field_name("left")?,
12423                        source,
12424                    ),
12425                    boolean_preprocessor_expression(
12426                        expression.child_by_field_name("right")?,
12427                        source,
12428                    ),
12429                ))
12430            };
12431            match expression
12432                .child_by_field_name("operator")
12433                .map(|operator| operator.kind())
12434            {
12435                Some("&&") => operands().map_or_else(
12436                    || {
12437                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12438                            expression, source,
12439                        )))
12440                    },
12441                    |(left, right)| BooleanGuardExpression::all([left, right]),
12442                ),
12443                Some("||") => operands().map_or_else(
12444                    || {
12445                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12446                            expression, source,
12447                        )))
12448                    },
12449                    |(left, right)| BooleanGuardExpression::any([left, right]),
12450                ),
12451                _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12452                    expression, source,
12453                ))),
12454            }
12455        }
12456        _ => {
12457            BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
12458        }
12459    }
12460}
12461
12462fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
12463    if targets.len() == 1 {
12464        targets.pop()
12465    } else {
12466        None
12467    }
12468}
12469
12470fn callable_declaration_nodes<'tree>(
12471    analyzer: &CppGraphSource<'_>,
12472    prepared: &'tree PreparedSyntaxTree,
12473    candidate: &CodeUnit,
12474) -> Vec<Node<'tree>> {
12475    let root = prepared.tree().root_node();
12476    analyzer
12477        .ranges(candidate)
12478        .into_iter()
12479        .filter_map(|range| {
12480            let mut declaration =
12481                root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
12482            // A declaration an attribute-like macro invocation swallowed lives
12483            // inside the `ERROR` the parser left, not inside a `declaration`
12484            // node, so that envelope is where the climb stops (#2552).
12485            //
12486            // The climb can also run out of ancestors. A collapse that takes a
12487            // whole file with it -- whisper.cpp's `include/whisper.h` from its
12488            // first `WHISPER_DEPRECATED(` to the end (#3094) -- leaves no
12489            // envelope around most of what a region reparse recovers there, and
12490            // the recovered declaration's own `;` can fall outside the envelope
12491            // it does leave, so the range spans the container rather than one
12492            // node. Either way the declaration stands at the scope the climb
12493            // ended in, which is what the callers ask these nodes about.
12494            while !matches!(
12495                declaration.kind(),
12496                "declaration" | "field_declaration" | "function_definition"
12497            ) && !crate::declarations::is_macro_wrapped_declaration_envelope(
12498                declaration,
12499                prepared.source(),
12500            ) {
12501                let Some(parent) = declaration.parent() else {
12502                    break;
12503                };
12504                declaration = parent;
12505            }
12506            Some(declaration)
12507        })
12508        .collect()
12509}
12510
12511fn real_function_definition_ancestor<'tree>(
12512    node: Node<'tree>,
12513    source: &str,
12514) -> Option<Node<'tree>> {
12515    let mut ancestor = node.parent();
12516    while let Some(node) = ancestor {
12517        if node.kind() == "function_definition"
12518            && !is_recovered_declaration_scope_container(node, source)
12519        {
12520            return Some(node);
12521        }
12522        ancestor = node.parent();
12523    }
12524    None
12525}
12526
12527fn callable_declaration_activation_in_file(
12528    analyzer: &CppGraphSource<'_>,
12529    candidate: &CodeUnit,
12530    reference: &CallableReferenceContext<'_>,
12531) -> Option<usize> {
12532    analyzer
12533        .declaration_source_occurrences(candidate)?
12534        .into_iter()
12535        .filter_map(|occurrence| {
12536            let activation = occurrence.fact.callable_activation?;
12537            let guards = cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
12538            callable_guard_requirements_are_visible(
12539                &guards,
12540                occurrence.fact.callable_guard_completion_byte,
12541                reference,
12542            )
12543            .then_some(activation)
12544        })
12545        .min()
12546}
12547
12548fn callable_guard_requirements_are_visible(
12549    required: &HashSet<PreprocessorGuard>,
12550    completion_byte: Option<usize>,
12551    reference: &CallableReferenceContext<'_>,
12552) -> bool {
12553    if required.iter().all(|guard| match guard {
12554        PreprocessorGuard::Defined(name) if name == "__cplusplus" => !reference.is_c(),
12555        PreprocessorGuard::Undefined(name) if name == "__cplusplus" => reference.is_c(),
12556        guard => reference
12557            .guards()
12558            .is_some_and(|active| preprocessor_guard_holds_at_reference(guard, active)),
12559    }) {
12560        return true;
12561    }
12562    if required.iter().any(|guard| match guard {
12563        PreprocessorGuard::Defined(name) if name == "__cplusplus" => reference.is_c(),
12564        PreprocessorGuard::Undefined(name) if name == "__cplusplus" => !reference.is_c(),
12565        _ => false,
12566    }) {
12567        return false;
12568    }
12569    let undecided: HashSet<_> = required
12570        .iter()
12571        .filter(|guard| {
12572            !matches!(guard, PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
12573            if name == "__cplusplus")
12574        })
12575        .cloned()
12576        .collect();
12577    reference.position.as_ref().is_some_and(|position| {
12578        completion_byte.is_some_and(|end| end > 0 && end <= position.byte)
12579            && guards_compatible_at_reference(&undecided, reference.guards())
12580    })
12581}
12582
12583/// The reference side of a callable visibility question.
12584///
12585/// An include-graph walk and a whole-file arity activation ask the question
12586/// without one reference position, so they carry no `position` and therefore no
12587/// guard environment.
12588struct CallableReferenceContext<'a> {
12589    file: &'a ProjectFile,
12590    position: Option<CallableReferencePosition<'a>>,
12591}
12592
12593/// One reference position plus its preprocessor guard environment. The
12594/// environment is computed on demand because most declarations carry no
12595/// non-trivial guard.
12596struct CallableReferencePosition<'a> {
12597    prepared: &'a PreparedSyntaxTree,
12598    byte: usize,
12599    guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
12600}
12601
12602impl CallableReferenceContext<'_> {
12603    fn is_c(&self) -> bool {
12604        self.file
12605            .rel_path()
12606            .extension()
12607            .and_then(|extension| extension.to_str())
12608            == Some("c")
12609    }
12610
12611    fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
12612        let position = self.position.as_ref()?;
12613        position
12614            .guards
12615            .get_or_init(|| {
12616                position
12617                    .prepared
12618                    .tree()
12619                    .root_node()
12620                    .descendant_for_byte_range(position.byte, position.byte.saturating_add(1))
12621                    .and_then(|node| {
12622                        preprocessor_guard_environment(node, position.prepared.source())
12623                    })
12624            })
12625            .as_ref()
12626    }
12627}
12628
12629/// The undecidable preprocessor guards `node` stands under, or `None` when its
12630/// conditional context excludes it from `reference`'s translation unit outright:
12631/// a false constant, a condition this analyzer cannot read, or a `__cplusplus`
12632/// branch the reference's language never takes.
12633///
12634/// Collecting one guard per enclosing conditional makes the whole walk a
12635/// conjunction of the declaration's requirements. Which comparison those
12636/// requirements then face depends on where the declaration lives: a same-file
12637/// declaration shares the reference's configuration, so the reference's active
12638/// guards must imply them, while a foreign file resolves its own conditionals
12639/// and only has to stay compatible with the reference.
12640fn callable_declaration_guard_requirements(
12641    node: Node<'_>,
12642    source: &str,
12643    reference: &CallableReferenceContext<'_>,
12644) -> Option<HashSet<PreprocessorGuard>> {
12645    let reference_is_c = reference.is_c();
12646    let mut required = HashSet::default();
12647    for guard in callable_preprocessor_ancestor_guards(node, source)? {
12648        match guard {
12649            PreprocessorGuard::Constant(true) => {}
12650            PreprocessorGuard::Constant(false) => return None,
12651            guard if preprocessor_guard_excludes_language(&guard, reference_is_c) => {
12652                return None;
12653            }
12654            guard => {
12655                required.insert(guard);
12656            }
12657        }
12658    }
12659    Some(required)
12660}
12661
12662/// Whether a declaration in the reference's own file is co-active with the
12663/// reference: one translation unit resolves every conditional the same way, so
12664/// the reference's active guards must imply the declaration's requirements.
12665///
12666/// The exception is a declaration in one branch of a completed `#if`/`#else`
12667/// family the reference follows: the reference's own position settles nothing
12668/// about that family, so a compatible branch is one resolution the source
12669/// presents at the reference (#3297), exactly as
12670/// [`VisibilityIndex::external_type_candidate_visible_in_context`] reads a type
12671/// declaration there.
12672fn callable_preprocessor_context_is_visible_for_reference(
12673    node: Node<'_>,
12674    source: &str,
12675    reference: &CallableReferenceContext<'_>,
12676) -> bool {
12677    let Some(required) = callable_declaration_guard_requirements(node, source, reference) else {
12678        return false;
12679    };
12680    if required.is_empty() || guard_requirements_hold_at_reference(&required, reference.guards()) {
12681        return true;
12682    }
12683    reference.position.as_ref().is_some_and(|position| {
12684        guards_compatible_at_reference(&required, reference.guards())
12685            && declaration_follows_completed_family_before_reference(node, source, position.byte)
12686    })
12687}
12688
12689fn flattened_macro_namespace_declaration_matches(
12690    analyzer: &CppGraphSource<'_>,
12691    _cpp: &dyn CppSource,
12692    reference_file: &ProjectFile,
12693    visible_declaration: &CodeUnit,
12694    qualified_candidate: &CodeUnit,
12695    reference_byte: usize,
12696) -> bool {
12697    // Namespace-opening macros can leave tree-sitter unable to retain the
12698    // namespace owner after a later recovery point. In that shape the forward
12699    // declaration is indexed at translation-unit scope, while the definition
12700    // still has its qualified owner. Require all surviving structural evidence
12701    // before treating the declaration as activation for that definition.
12702    if visible_declaration.kind() != qualified_candidate.kind()
12703        || visible_declaration.identifier() != qualified_candidate.identifier()
12704        || visible_declaration.signature() != qualified_candidate.signature()
12705        || !visible_declaration.package_name().is_empty()
12706        || qualified_candidate.package_name().is_empty()
12707    {
12708        return false;
12709    }
12710
12711    let Some(occurrences) = analyzer.declaration_source_occurrences(visible_declaration) else {
12712        return false;
12713    };
12714    occurrences.into_iter().any(|occurrence| {
12715        occurrence
12716            .displaced_namespace_closing_brace
12717            .is_some_and(|closing_brace| {
12718                visible_declaration.source() != reference_file || closing_brace < reference_byte
12719            })
12720    })
12721}
12722
12723pub(crate) fn flattened_macro_namespace_components(
12724    declaration: Node<'_>,
12725    source: &str,
12726) -> Option<Vec<String>> {
12727    flattened_macro_function_namespace_components(declaration, source)
12728        .or_else(|| flattened_macro_error_namespace_components(declaration, source))
12729}
12730
12731fn flattened_macro_function_namespace_components(
12732    declaration: Node<'_>,
12733    source: &str,
12734) -> Option<Vec<String>> {
12735    let body = declaration
12736        .parent()
12737        .filter(|parent| parent.kind() == "compound_statement")?;
12738    let function = body.parent()?;
12739    if function.child_by_field_name("body") != Some(body) {
12740        return None;
12741    }
12742    let namespace_name = recovered_macro_namespace_name(function, source)?;
12743    let mut components = enclosing_namespace_components(declaration, source)?;
12744    components.push(namespace_name);
12745    Some(components)
12746}
12747
12748/// The namespace name a namespace-opening macro token displaced into a
12749/// synthetic `function_definition`, or `None` when `function` is not that
12750/// recovery shape.
12751///
12752/// `ABSL_NAMESPACE_BEGIN` (or `FMT_BEGIN_NAMESPACE`, ...) immediately before
12753/// `namespace x {` leaves tree-sitter with a `function_definition` whose type is
12754/// the macro token, whose declarator is the namespace name behind an `ERROR`
12755/// holding the `namespace` keyword, and whose body spans the whole namespace
12756/// region. The macro-declaration grammar keeps the same envelope clean: the
12757/// token is a `macro_modifier` (itself admitted only where the parse would
12758/// otherwise contain an error) and the keyword occupies the type field. The
12759/// matching `*_NAMESPACE_END` sibling is what separates the recovery artifact
12760/// from a real function definition.
12761fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
12762    if function.kind() != "function_definition" {
12763        return None;
12764    }
12765    let body = function
12766        .child_by_field_name("body")
12767        .filter(|body| body.kind() == "compound_statement")?;
12768    let mut cursor = function.walk();
12769    let prefix = function
12770        .named_children(&mut cursor)
12771        .take_while(|child| child.start_byte() < body.start_byte())
12772        .filter(|child| child.kind() != "comment")
12773        .collect::<Vec<_>>();
12774    let begin_index = prefix.iter().rposition(|child| {
12775        flattened_macro_sentinel_name(*child, source)
12776            .is_some_and(|name| is_namespace_begin_sentinel(&name))
12777    })?;
12778    let mut identifiers = Vec::new();
12779    let mut stack = prefix[begin_index + 1..]
12780        .iter()
12781        .rev()
12782        .copied()
12783        .collect::<Vec<_>>();
12784    while let Some(current) = stack.pop() {
12785        if let Some(identifier) = direct_cpp_identifier_name(current, source) {
12786            identifiers.push(identifier);
12787            continue;
12788        }
12789        let mut cursor = current.walk();
12790        let children = current.named_children(&mut cursor).collect::<Vec<_>>();
12791        stack.extend(children.into_iter().rev());
12792    }
12793    let [keyword, namespace_name] = identifiers.as_slice() else {
12794        return None;
12795    };
12796    // The namespace keyword and the complete sentinel envelope establish the
12797    // identifier's role. Capitalization does not establish a macro binding.
12798    if keyword != "namespace" || namespace_name.is_empty() {
12799        return None;
12800    }
12801    let mut next = function.next_named_sibling();
12802    let next = loop {
12803        let candidate = next?;
12804        next = candidate.next_named_sibling();
12805        if candidate.kind() != "comment" {
12806            break candidate;
12807        }
12808    };
12809    flattened_macro_sentinel_name(next, source)
12810        .is_some_and(|name| is_namespace_end_sentinel(&name))
12811        .then(|| namespace_name.clone())
12812}
12813
12814/// A `function_definition` that exists only because tree-sitter recovered a
12815/// macro-decorated class head or a namespace-opening macro token. A declaration
12816/// in such a body keeps class or namespace scope, so a scope walk must step over
12817/// the wrapper instead of treating the declaration as block local.
12818pub(crate) fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
12819    crate::declarations::is_recovered_exported_class_container(node, source)
12820        || crate::declarations::is_recovered_fragmented_partial_specialization_container(
12821            node, source,
12822        )
12823        || recovered_macro_namespace_name(node, source).is_some()
12824}
12825
12826fn flattened_macro_error_namespace_components(
12827    declaration: Node<'_>,
12828    source: &str,
12829) -> Option<Vec<String>> {
12830    let parent = declaration
12831        .parent()
12832        .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
12833    let mut cursor = parent.walk();
12834    let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
12835    let declaration_index = siblings
12836        .iter()
12837        .position(|candidate| same_node(*candidate, declaration))?;
12838    let begin_index = (0..declaration_index).rev().find(|index| {
12839        flattened_macro_sentinel_name(siblings[*index], source)
12840            .is_some_and(|name| is_namespace_begin_sentinel(&name))
12841    })?;
12842
12843    let significant = siblings[begin_index + 1..declaration_index]
12844        .iter()
12845        .copied()
12846        .filter(|node| node.kind() != "comment")
12847        .collect::<Vec<_>>();
12848    let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
12849        return None;
12850    };
12851    if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
12852        return None;
12853    }
12854    let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
12855    if significant[2..].iter().any(|node| {
12856        flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
12857            is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
12858        })
12859    }) {
12860        return None;
12861    }
12862
12863    let mut saw_namespace_close = false;
12864    for sibling in siblings.iter().skip(declaration_index + 1).copied() {
12865        if sibling.kind() == "comment" {
12866            continue;
12867        }
12868        if !saw_namespace_close {
12869            if direct_unmatched_closing_brace(sibling) {
12870                saw_namespace_close = true;
12871                continue;
12872            }
12873            if flattened_macro_sentinel_name(sibling, source).is_some() {
12874                return None;
12875            }
12876            continue;
12877        }
12878        if !flattened_macro_sentinel_name(sibling, source)
12879            .is_some_and(|name| is_namespace_end_sentinel(&name))
12880        {
12881            return None;
12882        }
12883        let mut components = enclosing_namespace_components(declaration, source)?;
12884        components.push(namespace_name);
12885        return Some(components);
12886    }
12887    None
12888}
12889
12890fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
12891    // At translation-unit scope the trailing `X_NAMESPACE_END` token parses as
12892    // an `expression_statement` with a missing semicolon; inside a namespace
12893    // body the same token stays a bare `type_identifier`. Under the
12894    // macro-declaration grammar the token is wrapped in a `macro_modifier`
12895    // wherever it decorates a declaration head, and a stacked `END`/`BEGIN`
12896    // pair heads the next region's bogus function with the trailing `END` as
12897    // its first modifier child.
12898    let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
12899        node.named_child(0)?
12900    } else {
12901        node
12902    };
12903    let candidates = [
12904        direct_cpp_identifier_name(node, source),
12905        node.child_by_field_name("type")
12906            .and_then(|type_node| direct_cpp_identifier_name(type_node, source)),
12907        macro_modifier_identifier(node)
12908            .map(|identifier| normalize_cpp_whitespace(node_text(identifier, source))),
12909        {
12910            let mut cursor = node.walk();
12911            node.named_children(&mut cursor)
12912                .find(|child| child.kind() != "comment")
12913                .and_then(macro_modifier_identifier)
12914                .map(|identifier| normalize_cpp_whitespace(node_text(identifier, source)))
12915        },
12916    ];
12917    candidates.into_iter().flatten().find(|candidate| {
12918        cpp_export_macro_token(candidate)
12919            && (is_namespace_begin_sentinel(candidate) || is_namespace_end_sentinel(candidate))
12920    })
12921}
12922
12923/// Namespace-opening macros are spelled both ways in the wild:
12924/// `ABSL_NAMESPACE_BEGIN` (abseil, nlohmann) and `FMT_BEGIN_NAMESPACE` (fmt).
12925#[derive(Clone, Copy, PartialEq, Eq)]
12926enum NamespaceSentinel {
12927    Begin,
12928    End,
12929}
12930
12931fn namespace_sentinel(name: &str) -> Option<NamespaceSentinel> {
12932    if name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE") {
12933        Some(NamespaceSentinel::Begin)
12934    } else if name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE") {
12935        Some(NamespaceSentinel::End)
12936    } else {
12937        None
12938    }
12939}
12940
12941fn is_namespace_begin_sentinel(name: &str) -> bool {
12942    namespace_sentinel(name) == Some(NamespaceSentinel::Begin)
12943}
12944
12945fn is_namespace_end_sentinel(name: &str) -> bool {
12946    namespace_sentinel(name) == Some(NamespaceSentinel::End)
12947}
12948
12949fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
12950    if node.kind() != "ERROR" || node.named_child_count() != 1 {
12951        return None;
12952    }
12953    let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
12954    (!cpp_export_macro_token(&name)).then_some(name)
12955}
12956
12957fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
12958    if !matches!(
12959        node.kind(),
12960        "identifier" | "namespace_identifier" | "type_identifier"
12961    ) {
12962        return None;
12963    }
12964    let name = normalize_cpp_whitespace(node_text(node, source));
12965    (!name.is_empty()).then_some(name)
12966}
12967
12968fn guard_requirement_sets_match(
12969    left: &[(usize, HashSet<PreprocessorGuard>)],
12970    right: &[(usize, HashSet<PreprocessorGuard>)],
12971) -> bool {
12972    left.len() == right.len()
12973        && left.iter().all(|(_, left_guards)| {
12974            right
12975                .iter()
12976                .any(|(_, right_guards)| left_guards == right_guards)
12977        })
12978        && right.iter().all(|(_, right_guards)| {
12979            left.iter()
12980                .any(|(_, left_guards)| right_guards == left_guards)
12981        })
12982}
12983
12984pub(crate) fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
12985    node.kind() == "ERROR"
12986        && (0..node.child_count())
12987            .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
12988}
12989
12990/// The preprocessor requirements a scope-level binding (a using declaration or
12991/// directive, or a namespace alias) stands under, or `None` when its guard
12992/// environment cannot be represented.
12993pub fn binding_site_guards(node: Node<'_>, source: &str) -> Option<HashSet<PreprocessorGuard>> {
12994    if callable_preprocessor_context_is_visible(node, source) {
12995        Some(HashSet::default())
12996    } else {
12997        preprocessor_guard_environment(node, source)
12998    }
12999}
13000
13001pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
13002    let mut ancestor = node.parent();
13003    while let Some(parent) = ancestor {
13004        if is_cpp_preprocessor_conditional(parent)
13005            && !is_file_covering_include_guard(parent, source)
13006            && !is_split_cpp_language_linkage_wrapper(parent, node, source)
13007        {
13008            return false;
13009        }
13010        ancestor = parent.parent();
13011    }
13012    true
13013}
13014
13015fn is_split_cpp_language_linkage_wrapper(
13016    conditional: Node<'_>,
13017    descendant: Node<'_>,
13018    source: &str,
13019) -> bool {
13020    if conditional.child_by_field_name("alternative").is_some()
13021        || !matches!(
13022            simple_preprocessor_guard(conditional, source),
13023            Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
13024        )
13025    {
13026        return false;
13027    }
13028    let mut current = descendant.parent();
13029    let linkage = loop {
13030        let Some(node) = current else {
13031            return false;
13032        };
13033        if node == conditional {
13034            return false;
13035        }
13036        if node.kind() == "linkage_specification" {
13037            break node;
13038        }
13039        current = node.parent();
13040    };
13041    if linkage
13042        .child_by_field_name("value")
13043        .is_none_or(|value| node_text(value, source) != "\"C\"")
13044    {
13045        return false;
13046    }
13047    let Some(body) = linkage.child_by_field_name("body") else {
13048        return false;
13049    };
13050    // Walk the body with cursors: an indexed `named_child(i)` restarts from
13051    // the first child, so indexed loops over a header-sized linkage body made
13052    // each call quadratic, and this runs once per declaration inside it
13053    // (#3397).
13054    let mut cursor = body.walk();
13055    let closes_opening_branch = body
13056        .named_children(&mut cursor)
13057        .take_while(|child| child.end_byte() <= descendant.start_byte())
13058        .any(|child| {
13059            child.kind() == "preproc_call"
13060                && child
13061                    .child_by_field_name("directive")
13062                    .is_some_and(|directive| node_text(directive, source) == "#endif")
13063        });
13064    let mut cursor = body.walk();
13065    let reopens_for_closing_brace = body
13066        .named_children(&mut cursor)
13067        .skip_while(|child| child.start_byte() < descendant.end_byte())
13068        .any(|child| {
13069            matches!(
13070                simple_preprocessor_guard(child, source),
13071                Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
13072            ) && {
13073                let mut tokens = child.walk();
13074                child
13075                    .children(&mut tokens)
13076                    .any(|token| token.kind() == "#endif" && token.is_missing())
13077            }
13078        });
13079    closes_opening_branch && reopens_for_closing_brace
13080}
13081
13082/// The argument list a call-shaped node supplies: `f(args)`, `new T(args)`,
13083/// `T{args}` and the member initializer `: field(args)`, whose grammar gives its
13084/// argument list no field name.
13085pub fn call_arguments_node(node: Node<'_>) -> Option<Node<'_>> {
13086    node.child_by_field_name("arguments")
13087        .or_else(|| node.child_by_field_name("parameters"))
13088        .or_else(|| node.child_by_field_name("value"))
13089        .or_else(|| first_named_child_of_kind(node, "argument_list"))
13090        .or_else(|| first_named_child_of_kind(node, "initializer_list"))
13091}
13092
13093pub fn call_arity(node: Node<'_>) -> usize {
13094    call_arguments_node(node)
13095        .map(|args| argument_children(args).count())
13096        .unwrap_or(0)
13097}
13098
13099pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
13100    let recovered_block_arguments = recovered_block_literal_arguments(node);
13101    (0..node.child_count())
13102        .filter_map(move |index| node.child(index))
13103        .filter(|child| child.is_named() && !child.is_extra())
13104        // The `^(` opening a Clang block literal argument survives the
13105        // macro-declaration grammar's bogus function envelope as an ERROR in
13106        // the parameter list. It only opens the block: the parameter
13107        // declaration that follows it already stands for the block argument.
13108        .filter(move |child| node.kind() != "parameter_list" || !block_literal_open_error(*child))
13109        .flat_map(move |child| {
13110            if let Some((raw, left, right)) = recovered_block_arguments
13111                && child == raw
13112            {
13113                [Some(left), Some(right)]
13114            } else {
13115                [Some(child), None]
13116            }
13117        })
13118        .flatten()
13119}
13120
13121/// The `^(` token pair that opens a Clang block literal argument, retained by
13122/// the bogus function envelope's parameter list as an `ERROR`. The block's
13123/// parameter declaration follows it and counts as the one logical argument.
13124fn block_literal_open_error(node: Node<'_>) -> bool {
13125    if node.kind() != "ERROR" || node.is_missing() {
13126        return false;
13127    }
13128    let mut cursor = node.walk();
13129    let tokens = node.children(&mut cursor).collect::<Vec<_>>();
13130    matches!(tokens.as_slice(), [caret, open] if caret.kind() == "^" && open.kind() == "(")
13131}
13132
13133/// Recover the two ordinary C values that the C++ grammar folds into one
13134/// `new_expression` for `callee(new, trailing)`.
13135///
13136/// The malformed node has an exact grammar-owned shape: the anonymous `new`
13137/// token, an extra `ERROR` containing only the comma token, and the trailing
13138/// value in the `type` field.  The caller supplies the compilation-dialect
13139/// proof from [`reference_uses_c_semantics`]; a C++ source therefore never
13140/// reinterprets a real new-expression through this path.
13141pub fn recovered_c_new_expression_arguments(
13142    node: Node<'_>,
13143    uses_c_semantics: bool,
13144) -> Option<[Node<'_>; 2]> {
13145    if !uses_c_semantics || node.kind() != "new_expression" {
13146        return None;
13147    }
13148    let parent = node.parent()?;
13149    if parent.kind() != "argument_list" {
13150        return None;
13151    }
13152    let keyword = node.child(0)?;
13153    let error = node.child(1)?;
13154    let trailing = node.child(2)?;
13155    if node.child(3).is_some()
13156        || keyword.kind() != "new"
13157        || keyword.is_named()
13158        || keyword.child_count() != 0
13159        || error.kind() != "ERROR"
13160        || !error.is_extra()
13161        || error.child_count() != 1
13162        || error.child(0).is_none_or(|comma| comma.kind() != ",")
13163        || node.child_by_field_name("type") != Some(trailing)
13164        || trailing.kind() != "type_identifier"
13165    {
13166        return None;
13167    }
13168    Some([keyword, trailing])
13169}
13170
13171/// The recovered C value covering one focused source range, starting from any
13172/// node within the malformed new-expression.
13173pub fn recovered_c_new_expression_argument_at(
13174    mut node: Node<'_>,
13175    start_byte: usize,
13176    end_byte: usize,
13177    uses_c_semantics: bool,
13178) -> Option<Node<'_>> {
13179    // The recovery only exists for C sources, and the climb below reaches the
13180    // root before it can answer no. `Node::parent` re-descends from the root on
13181    // every step, so leaving the check inside the loop made every scanned node
13182    // of a C++ file pay a full quadratic ancestor walk for an answer settled by
13183    // the file's language (#3097).
13184    if !uses_c_semantics {
13185        return None;
13186    }
13187    loop {
13188        if let Some(arguments) = recovered_c_new_expression_arguments(node, uses_c_semantics) {
13189            return arguments.into_iter().find(|argument| {
13190                argument.start_byte() <= start_byte && end_byte <= argument.end_byte()
13191            });
13192        }
13193        node = node.parent()?;
13194    }
13195}
13196
13197fn recovered_c_keyword_argument_count(
13198    file: &ProjectFile,
13199    call: Node<'_>,
13200    arguments: Node<'_>,
13201    source: &str,
13202) -> usize {
13203    // A C identifier that is a C++ keyword can be displaced twice by the C++
13204    // grammar: first into a direct parameter-list `ERROR(keyword)`, then into
13205    // a direct argument-list `ERROR(',', keyword)`. Match those CST tokens in
13206    // the enclosing C function before restoring the otherwise dropped slot;
13207    // ABI15's collapsed function-head shape is handled by the fallback below.
13208    if !is_c_source_file(file) || arguments.kind() != "argument_list" {
13209        return 0;
13210    }
13211    let mut ancestor = Some(call);
13212    let function = loop {
13213        let Some(current) = ancestor else {
13214            return recovered_c_keyword_argument_count_from_collapsed_function(call, arguments)
13215                .unwrap_or(0);
13216        };
13217        if current.kind() == "function_definition" {
13218            break current;
13219        }
13220        ancestor = current.parent();
13221    };
13222    let Some(parameters) = function
13223        .child_by_field_name("declarator")
13224        .and_then(|declarator| declarator.child_by_field_name("parameters"))
13225    else {
13226        return 0;
13227    };
13228    let displaced_parameter_keywords = (0..parameters.child_count())
13229        .filter_map(|index| parameters.child(index))
13230        .filter(|error| error.kind() == "ERROR")
13231        .filter_map(|error| {
13232            let parameter = error.prev_named_sibling()?;
13233            if parameter.kind() != "parameter_declaration"
13234                || parameter.end_byte() != error.start_byte()
13235                || extract_variable_name(parameter, source).is_some()
13236            {
13237                return None;
13238            }
13239            let mut children = (0..error.child_count())
13240                .filter_map(|index| error.child(index))
13241                .filter(|child| !child.is_extra() && !child.is_missing());
13242            let keyword = children.next()?;
13243            (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
13244                .then_some(keyword)
13245        })
13246        .collect::<Vec<_>>();
13247    if displaced_parameter_keywords.is_empty() {
13248        return 0;
13249    }
13250
13251    (0..arguments.child_count())
13252        .filter_map(|index| arguments.child(index))
13253        .filter(|error| error.kind() == "ERROR" && error.is_extra())
13254        .filter(|error| {
13255            let mut children = (0..error.child_count())
13256                .filter_map(|index| error.child(index))
13257                .filter(|child| !child.is_extra() && !child.is_missing());
13258            let Some(comma) = children.next() else {
13259                return false;
13260            };
13261            let Some(keyword) = children.next() else {
13262                return false;
13263            };
13264            children.next().is_none()
13265                && comma.kind() == ","
13266                && !keyword.is_named()
13267                && keyword.child_count() == 0
13268                && displaced_parameter_keywords
13269                    .iter()
13270                    .any(|parameter| parameter.kind_id() == keyword.kind_id())
13271        })
13272        .count()
13273}
13274
13275/// ABI15 can collapse a C function head into nested `ERROR` nodes when its
13276/// parameter name is a C++ keyword. The call still has a structured body, and
13277/// the outer error retains the function type, name, and opening parenthesis;
13278/// recover the displaced parameter only from those CST relationships. Text
13279/// matching would also accept an unbound call after the collapsed body.
13280fn recovered_c_keyword_argument_count_from_collapsed_function(
13281    call: Node<'_>,
13282    arguments: Node<'_>,
13283) -> Option<usize> {
13284    let argument_keywords = children_iter(arguments)
13285        .filter(|error| error.kind() == "ERROR" && error.is_extra())
13286        .filter_map(|error| {
13287            let mut children =
13288                children_iter(error).filter(|child| !child.is_extra() && !child.is_missing());
13289            let comma = children.next()?;
13290            let keyword = children.next()?;
13291            (children.next().is_none()
13292                && comma.kind() == ","
13293                && !keyword.is_named()
13294                && keyword.child_count() == 0)
13295                .then_some(keyword)
13296        })
13297        .collect::<Vec<_>>();
13298    if argument_keywords.is_empty() {
13299        return Some(0);
13300    }
13301
13302    // The collapsed function keeps its body under the parameter ERROR, while
13303    // ordinary control-flow blocks add compound_statement layers below it.
13304    // Stop before crossing a real callable body, whose parameters are a
13305    // separate binding scope.
13306    let mut ancestor = call.parent();
13307    let body = loop {
13308        let current = ancestor?;
13309        if matches!(current.kind(), "function_definition" | "lambda_expression") {
13310            return None;
13311        }
13312        if current.kind() == "compound_statement"
13313            && current
13314                .parent()
13315                .is_some_and(|parent| parent.kind() == "ERROR")
13316        {
13317            break current;
13318        }
13319        ancestor = current.parent();
13320    };
13321    let parameter_error = body.parent().filter(|parent| parent.kind() == "ERROR")?;
13322    let function_error = parameter_error
13323        .parent()
13324        .filter(|parent| parent.kind() == "ERROR")?;
13325    let function_children = children_iter(function_error).collect::<Vec<_>>();
13326    let parameter_error_index = function_children
13327        .iter()
13328        .position(|child| same_node(*child, parameter_error))?;
13329    // ERROR nodes expose their retained field labels by child index, while
13330    // child_by_field_name does not return their type child in runtime 0.25.
13331    let has_return_type = function_children[..parameter_error_index.saturating_sub(2)]
13332        .iter()
13333        .enumerate()
13334        .any(|(index, child)| {
13335            function_error.field_name_for_child(index as u32) == Some("type")
13336                && !child.is_extra()
13337                && !child.is_missing()
13338                && matches!(
13339                    child.kind(),
13340                    "primitive_type" | "sized_type_specifier" | "type_identifier"
13341                )
13342        });
13343    if parameter_error_index < 2
13344        || !has_return_type
13345        || function_children[parameter_error_index - 1].kind() != "("
13346        || function_children[parameter_error_index - 2].kind() != "identifier"
13347    {
13348        return Some(0);
13349    }
13350
13351    let parameter_children = children_iter(parameter_error).collect::<Vec<_>>();
13352    let body_index = parameter_children
13353        .iter()
13354        .position(|child| same_node(*child, body))?;
13355    let close_parenthesis_index = parameter_children[..body_index]
13356        .iter()
13357        .position(|child| child.kind() == ")")?;
13358    let parameter_keywords = parameter_children[..close_parenthesis_index]
13359        .iter()
13360        .enumerate()
13361        .filter(|(index, child)| {
13362            !child.is_named()
13363                && !child.is_extra()
13364                && !child.is_missing()
13365                && child.child_count() == 0
13366                && argument_keywords
13367                    .iter()
13368                    .any(|keyword| keyword.kind_id() == child.kind_id())
13369                && collapsed_c_parameter_name_position(&parameter_children, *index)
13370        })
13371        .map(|(_, child)| *child)
13372        .collect::<Vec<_>>();
13373    Some(
13374        argument_keywords
13375            .iter()
13376            .filter(|keyword| {
13377                parameter_keywords
13378                    .iter()
13379                    .any(|parameter| parameter.kind_id() == keyword.kind_id())
13380            })
13381            .count(),
13382    )
13383}
13384
13385/// The ABI15 collapsed head retains a parameter's type and pointer declarator
13386/// as direct children. Require that exact segment before accepting an
13387/// anonymous keyword as its name; a keyword after an `=` or attribute must not
13388/// borrow the same recovery.
13389fn collapsed_c_parameter_name_position(children: &[Node<'_>], keyword_index: usize) -> bool {
13390    let segment_start = children[..keyword_index]
13391        .iter()
13392        .rposition(|child| matches!(child.kind(), "(" | ","))
13393        .map_or(0, |index| index + 1);
13394    let mut saw_type = false;
13395    let mut saw_pointer = false;
13396    for child in &children[segment_start..keyword_index] {
13397        if child.kind() == "*" && !child.is_extra() && !child.is_missing() {
13398            saw_pointer = true;
13399            continue;
13400        }
13401        if saw_pointer {
13402            return false;
13403        }
13404        if !child.is_named() || child.is_extra() || child.is_missing() {
13405            return false;
13406        }
13407        if child.kind() == "type_qualifier" {
13408            continue;
13409        }
13410        if matches!(
13411            child.kind(),
13412            "class_specifier"
13413                | "dependent_type"
13414                | "enum_specifier"
13415                | "identifier"
13416                | "primitive_type"
13417                | "qualified_identifier"
13418                | "scoped_identifier"
13419                | "struct_specifier"
13420                | "template_type"
13421                | "type_identifier"
13422                | "union_specifier"
13423        ) {
13424            saw_type = true;
13425            continue;
13426        }
13427        return false;
13428    }
13429    saw_type && saw_pointer
13430}
13431
13432fn recovered_block_literal_arguments<'tree>(
13433    arguments: Node<'tree>,
13434) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
13435    if arguments.kind() != "argument_list" {
13436        return None;
13437    }
13438    let mut raw_arguments = (0..arguments.child_count())
13439        .filter_map(|index| arguments.child(index))
13440        .filter(|child| child.is_named() && !child.is_extra());
13441    let raw = raw_arguments.next()?;
13442    if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
13443        return None;
13444    }
13445
13446    let left = raw.child_by_field_name("left")?;
13447    if left.is_missing() || left.start_byte() == left.end_byte() {
13448        return None;
13449    }
13450    let right = raw.child_by_field_name("right")?;
13451    if right.kind() != "compound_literal_expression"
13452        || right.is_missing()
13453        || right
13454            .child_by_field_name("type")
13455            .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
13456        || right
13457            .child_by_field_name("value")
13458            .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
13459    {
13460        return None;
13461    }
13462    let has_intervening_error = (0..raw.child_count())
13463        .filter_map(|index| raw.child(index))
13464        .any(|child| {
13465            child.kind() == "ERROR"
13466                && !child.is_missing()
13467                && child.start_byte() >= left.end_byte()
13468                && child.end_byte() <= right.start_byte()
13469        });
13470    has_intervening_error.then_some((raw, left, right))
13471}
13472
13473pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
13474    match node.kind() {
13475        "new_expression" => node
13476            .child_by_field_name("type")
13477            .or_else(|| node.named_child(0)),
13478        "compound_literal_expression" => node.child_by_field_name("type"),
13479        "call_expression" => node.child_by_field_name("function"),
13480        _ => None,
13481    }
13482}
13483
13484/// The structured type named by a C-style cast expression.
13485///
13486/// Tree-sitter wraps the actual type syntax in a `type_descriptor`.  Return its
13487/// structured `type` field so resolution retains qualified and nested syntax
13488/// without reparsing source text.
13489pub fn cast_expression_type_node(node: Node<'_>) -> Option<Node<'_>> {
13490    if node.kind() != "cast_expression" {
13491        return None;
13492    }
13493    let descriptor = node.child_by_field_name("type")?;
13494    if descriptor.kind() == "type_descriptor" {
13495        descriptor.child_by_field_name("type")
13496    } else {
13497        Some(descriptor)
13498    }
13499}
13500
13501/// The expression a field access reads its member from.
13502///
13503/// That is normally the node in the `argument` field. A preprocessor
13504/// conditional inside a call's argument list defeats the grammar: tree-sitter
13505/// ends the argument list at the `#if` line, then reads the text that follows
13506/// `#endif` as a field access on the whole call expression and leaves the real
13507/// receiver in an `ERROR` node between the `argument` child and the operator.
13508/// mbedtls writes exactly that at `library/ssl_tls.c:4999`, where
13509/// `ssl->session->encrypt_then_mac` is one conditionally compiled call argument
13510/// (#2982).
13511///
13512/// `.` and `->` read whatever ends immediately before them in either tree, so
13513/// answer from the operator's position instead of trusting the `argument`
13514/// field, and unwrap the single expression a displaced `ERROR` holds.
13515pub fn cpp_field_expression_receiver(field: Node<'_>) -> Option<Node<'_>> {
13516    debug_assert_eq!(field.kind(), "field_expression");
13517    let operator = field.child_by_field_name("operator")?;
13518    let mut cursor = field.walk();
13519    let receiver = field
13520        .named_children(&mut cursor)
13521        .filter(|child| child.end_byte() <= operator.start_byte())
13522        .last()?;
13523    if receiver.kind() != "ERROR" {
13524        return Some(receiver);
13525    }
13526    (receiver.named_child_count() == 1)
13527        .then(|| receiver.named_child(0))
13528        .flatten()
13529}
13530
13531pub fn field_initializer_constructs_target(
13532    node: Node<'_>,
13533    ctx: &ScanCtx<'_>,
13534    owner: &CodeUnit,
13535) -> bool {
13536    // A qualified name in a constructor initializer denotes a base
13537    // subobject constructor (`namespace::Base(args)`), not a member field.  The
13538    // field-initializer grammar exposes the qualified name as one structured
13539    // `qualified_identifier`; resolve its owner through the same lexical type
13540    // machinery used for ordinary C++ type references before considering the
13541    // initializer a hit.  This keeps an unrelated `namespace::Other(...)`, a
13542    // qualified non-constructor member, and an unresolved owner out of the
13543    // target constructor's inverse usage set.
13544    if first_named_child_of_kind(node, "qualified_identifier").is_some() {
13545        return qualified_base_initializer_constructs_target(node, ctx, owner);
13546    }
13547    let Some(name) = node
13548        .child_by_field_name("name")
13549        .or_else(|| first_named_child_of_kind(node, "field_identifier"))
13550        .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
13551    else {
13552        return false;
13553    };
13554    let field_name = node_text(name, ctx.source);
13555    ctx.visibility
13556        .visible_identifier_candidates(ctx.file, field_name)
13557        .filter(|unit| unit.is_field() && unit.identifier() == field_name)
13558        .any(|unit| field_declares_type(unit, ctx, owner) && field_holds_subobject(unit, ctx))
13559}
13560
13561/// Whether a member initializer for `unit` runs a constructor of the type the
13562/// member declares.
13563///
13564/// Only a member that holds the type by value does. `Owner* p` initialized as
13565/// `p(nullptr)` binds a pointer and `Owner& r` as `r(other)` binds a
13566/// reference; neither runs an `Owner` constructor, even though both declare
13567/// `Owner` as the receiver type a member access through them resolves (#3286).
13568/// A member whose declaration the index cannot decode keeps the by-value
13569/// reading, which is what the declared-type match already assumed.
13570fn field_holds_subobject(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
13571    field_initializer_holds_subobject(&ctx.analyzer, ctx.visibility, unit)
13572}
13573
13574fn qualified_base_initializer_constructs_target(
13575    node: Node<'_>,
13576    ctx: &ScanCtx<'_>,
13577    owner: &CodeUnit,
13578) -> bool {
13579    let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
13580        return false;
13581    };
13582    let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
13583        return false;
13584    };
13585    let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
13586        return false;
13587    };
13588    let resolves_target = |components: &[String]| {
13589        matches!(
13590            ctx.visibility.resolve_type_components_lexically_for_target(
13591                &ctx.analyzer,
13592                ctx.file,
13593                components,
13594                is_globally_qualified_cpp_name(qualified),
13595                &lexical_scope,
13596                owner,
13597            ),
13598            LexicalTypeResolution::Resolved { unit, .. }
13599                if same_visible_symbol(&unit, owner)
13600        )
13601    };
13602    if resolves_target(&components) {
13603        return true;
13604    }
13605
13606    // Some real-world code spells a base mem-initializer as
13607    // `Base::Base(args)`. In that structured path the final component repeats
13608    // the constructor name; resolve the preceding type path. The terminal
13609    // identity check prevents an arbitrary qualified member from taking this
13610    // route.
13611    components
13612        .last()
13613        .is_some_and(|terminal| terminal == owner.identifier())
13614        && resolves_target(&components[..components.len() - 1])
13615}
13616
13617fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
13618    ctx.visibility
13619        .field_declared_type_fact(&ctx.analyzer, unit)
13620        .is_some_and(|fact| {
13621            ctx.visibility
13622                .resolves_to_type(&ctx.analyzer, ctx.file, &fact.type_text, owner)
13623                || ctx.visibility.resolves_to_type(
13624                    &ctx.analyzer,
13625                    ctx.file,
13626                    &normalize_field_type_text(&fact.type_text),
13627                    owner,
13628                )
13629        })
13630}
13631
13632pub fn field_declared_binding(
13633    analyzer: &CppGraphSource<'_>,
13634    visibility: &VisibilityIndex<'_>,
13635    visible_from: &ProjectFile,
13636    field: &CodeUnit,
13637) -> Option<CppScanBinding> {
13638    let (normalized, resolved, indirection) =
13639        field_declared_type_binding(analyzer, visibility, visible_from, field)?;
13640    let resolved = resolved
13641        .or_else(|| anonymous_aggregate_field_owner(analyzer, visibility, visible_from, field));
13642    Some(CppScanBinding::from_type_name(
13643        normalized,
13644        resolved,
13645        indirection,
13646    ))
13647}
13648
13649/// Whether a member-initializer `field(args)` runs the declared type's
13650/// constructor: only a member that holds the type by value does. `Owner* p`
13651/// initialized as `p(nullptr)` binds a pointer and `Owner& r` as `r(other)`
13652/// binds a reference; neither runs an `Owner` constructor (#3286). A member
13653/// whose declaration the index cannot decode keeps the by-value reading, the
13654/// same assumption the usage scan's declared-type match makes.
13655pub fn field_initializer_holds_subobject(
13656    analyzer: &CppGraphSource<'_>,
13657    visibility: &VisibilityIndex<'_>,
13658    field: &CodeUnit,
13659) -> bool {
13660    visibility
13661        .field_declared_type_fact(analyzer, field)
13662        .is_none_or(|fact| !fact.binds_indirectly)
13663}
13664
13665/// Resolve the receiver type minted for a named declarator on an anonymous C
13666/// aggregate, such as `struct { int r; } c`. The declaration index preserves
13667/// the aggregate as the nested class `Owner$c`, but the field's type fact has
13668/// no spelling that can name that class. Confirm the anonymous aggregate from
13669/// its parsed declaration, then use the owner's structured child relationship
13670/// to recover the one corresponding receiver type.
13671fn anonymous_aggregate_field_owner(
13672    analyzer: &CppGraphSource<'_>,
13673    visibility: &VisibilityIndex<'_>,
13674    visible_from: &ProjectFile,
13675    field: &CodeUnit,
13676) -> Option<CodeUnit> {
13677    let owner = type_owner_of(analyzer, field)?;
13678    if !owner.is_class() {
13679        return None;
13680    }
13681    let declaration = analyzer.get_source(field, false)?;
13682    let mut parser = Parser::new();
13683    parser
13684        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13685        .ok()?;
13686    let tree = parser.parse(&declaration, None)?;
13687    let mut stack = vec![tree.root_node()];
13688    while let Some(node) = stack.pop() {
13689        if matches!(node.kind(), "declaration" | "field_declaration")
13690            && let Some(type_node) = node
13691                .child_by_field_name("type")
13692                .or_else(|| first_type_child(node))
13693            && matches!(type_node.kind(), "struct_specifier" | "union_specifier")
13694            && type_node.child_by_field_name("name").is_none()
13695            && declared_name_binding(node, type_node, field.identifier(), &declaration).is_some()
13696        {
13697            let matches = visibility
13698                .visible_members_for_owner_name(visible_from, &owner, field.identifier())
13699                .into_iter()
13700                .filter(|child| child.is_class() && child.identifier() == field.identifier())
13701                .collect::<Vec<_>>();
13702            return match matches.as_slice() {
13703                [child] => Some((*child).clone()),
13704                _ => None,
13705            };
13706        }
13707        let mut cursor = node.walk();
13708        stack.extend(node.named_children(&mut cursor));
13709    }
13710    None
13711}
13712
13713/// Resolve an anonymous C aggregate's generated owner from its declaration
13714/// range. Anonymous local structs and unions have no type name to enter into
13715/// the visibility index; declaration extraction gives them a structured class
13716/// identity keyed by the aggregate node's exact CST range instead. Matching
13717/// that range keeps nested aggregates and unrelated generated owners out of
13718/// the result without inspecting source text.
13719pub fn anonymous_aggregate_owner(
13720    analyzer: &CppGraphSource<'_>,
13721    file: &ProjectFile,
13722    node: Node<'_>,
13723) -> Option<CodeUnit> {
13724    if !matches!(node.kind(), "struct_specifier" | "union_specifier")
13725        || node.child_by_field_name("name").is_some()
13726    {
13727        return None;
13728    }
13729    let mut candidates = analyzer
13730        .declarations(file)
13731        .into_iter()
13732        .filter(|candidate| {
13733            candidate.is_class()
13734                && analyzer.ranges(candidate).into_iter().any(|range| {
13735                    range.start_byte == node.start_byte() && range.end_byte == node.end_byte()
13736                })
13737        })
13738        .collect::<Vec<_>>();
13739    candidates.sort_by_key(|candidate| candidate.fq_name());
13740    candidates.dedup();
13741    match candidates.as_slice() {
13742        [candidate] => Some(candidate.clone()),
13743        _ => None,
13744    }
13745}
13746
13747/// The one logical type the candidates name, or why they do not name one.
13748fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
13749    let Some(first) = candidates.first() else {
13750        return Err(TypeCandidateFailure::Unresolvable);
13751    };
13752    if candidates
13753        .iter()
13754        .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
13755    {
13756        Ok((*first).clone())
13757    } else {
13758        Err(TypeCandidateFailure::Ambiguous)
13759    }
13760}
13761
13762fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
13763    logical_type_candidate(candidates).ok()
13764}
13765
13766fn unique_type_candidate_preserving_alias(
13767    analyzer: &CppGraphSource<'_>,
13768    file: &ProjectFile,
13769    candidates: &[&CodeUnit],
13770) -> Option<CodeUnit> {
13771    let first = *candidates.first()?;
13772    if declared_type_alias(analyzer, first) {
13773        return candidates
13774            .iter()
13775            .all(|candidate| {
13776                declared_type_alias(analyzer, candidate)
13777                    && candidate.kind() == first.kind()
13778                    && candidate.fq_name() == first.fq_name()
13779                    && candidate.source() == first.source()
13780            })
13781            .then(|| first.clone());
13782    }
13783    if analyzer.reference_uses_c_semantics(file)
13784        && first.is_class()
13785        && indexed_c_tag_kind(analyzer, first).is_some()
13786    {
13787        let mut full_source = None;
13788        let mut tag_kind = None;
13789        for candidate in candidates.iter().copied() {
13790            let candidate_tag_kind = indexed_c_tag_kind(analyzer, candidate)?;
13791            if tag_kind
13792                .replace(candidate_tag_kind)
13793                .is_some_and(|existing| existing != candidate_tag_kind)
13794            {
13795                return None;
13796            }
13797            if cpp_class_declaration_strength(analyzer, candidate)
13798                == CppClassDeclarationStrength::Full
13799                && full_source
13800                    .replace(candidate.source())
13801                    .is_some_and(|existing| existing != candidate.source())
13802            {
13803                return None;
13804            }
13805        }
13806    }
13807    candidates
13808        .iter()
13809        .all(|candidate| {
13810            !declared_type_alias(analyzer, candidate)
13811                && candidate.kind() == first.kind()
13812                && candidate.fq_name() == first.fq_name()
13813        })
13814        .then(|| first.clone())
13815}
13816
13817pub(crate) fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
13818    brokk_bifrost_core::analyzer::capabilities::provider_declares_type_alias(
13819        analyzer.type_alias_provider(),
13820        unit,
13821    )
13822}
13823
13824pub fn field_declared_type_binding(
13825    analyzer: &CppGraphSource<'_>,
13826    visibility: &VisibilityIndex<'_>,
13827    visible_from: &ProjectFile,
13828    field: &CodeUnit,
13829) -> Option<(String, Option<CodeUnit>, i32)> {
13830    let fact = visibility.field_declared_type_fact(analyzer, field)?;
13831    let normalized = normalize_field_type_text(&fact.type_text);
13832    let primary = visibility.resolve_unique_canonical_type_for_declaration(
13833        analyzer,
13834        visible_from,
13835        field,
13836        &normalized,
13837    );
13838    let resolved = match (primary, fact.template_arguments.as_deref()) {
13839        (Some(primary), Some(arguments)) => visibility
13840            .resolve_template_arguments(visible_from, primary, arguments)
13841            .ok(),
13842        (resolved, None) => resolved,
13843        (None, Some(_)) => None,
13844    };
13845    Some((normalized, resolved, fact.indirection))
13846}
13847
13848fn decode_field_declared_type_fact(
13849    analyzer: &CppGraphSource<'_>,
13850    field: &CodeUnit,
13851) -> Option<DeclaredFieldTypeFact> {
13852    if let Some(facts) = analyzer.declaration_source_properties(field)
13853        && let Some(first) = facts.first().and_then(|fact| fact.field_type.as_ref())
13854        && facts
13855            .iter()
13856            .all(|fact| fact.field_type.as_ref() == Some(first))
13857    {
13858        return Some(first.clone());
13859    }
13860    let Some(declaration) = analyzer.get_source(field, false) else {
13861        return decode_indexed_field_declared_type_fact(analyzer, field);
13862    };
13863    let mut parser = Parser::new();
13864    parser
13865        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13866        .ok()?;
13867    // A field's indexed source is stored without its surrounding class body.
13868    // Give tree-sitter that grammatical context before checking recovery-only
13869    // field shapes such as `PyObject_HEAD Imaging image;`.
13870    let contextual = parse_sentinel(
13871        "struct __bifrost_field_context { ",
13872        SentinelInput::region(&declaration),
13873        " };",
13874    );
13875    let (contextual_declaration, contextual_tree) = (contextual.source, contextual.tree);
13876    let mut stack = vec![contextual_tree.root_node()];
13877    while let Some(node) = stack.pop() {
13878        if let Some(recovered) = recovered_pyobject_head_field(node, &contextual_declaration)
13879            && node_text(recovered.name, &contextual_declaration) == field.identifier()
13880        {
13881            return Some(DeclaredFieldTypeFact {
13882                type_text: node_text(recovered.type_node, &contextual_declaration).to_string(),
13883                indirection: recovered.pointer_depth(),
13884                binds_indirectly: recovered.pointer_depth() > 0,
13885                template_arguments: None,
13886            });
13887        }
13888        if let Some(recovered) =
13889            recovered_function_like_field_declarator(node, &contextual_declaration)
13890            && node_text(recovered.name, &contextual_declaration) == field.identifier()
13891        {
13892            let type_node = node
13893                .child_by_field_name("type")
13894                .or_else(|| first_type_child(node))?;
13895            return Some(DeclaredFieldTypeFact {
13896                type_text: node_text(type_node, &contextual_declaration).to_string(),
13897                indirection: recovered.pointer_depth(),
13898                // A function-like declarator names a callable member, never a
13899                // subobject of the declared return type.
13900                binds_indirectly: true,
13901                template_arguments: cpp_template_reference_arguments(
13902                    type_node,
13903                    &contextual_declaration,
13904                ),
13905            });
13906        }
13907        if let Some(fact) =
13908            decode_declared_field_type_node(node, field.identifier(), &contextual_declaration)
13909        {
13910            return Some(fact);
13911        }
13912        let mut cursor = node.walk();
13913        stack.extend(node.named_children(&mut cursor));
13914    }
13915    let tree = parser.parse(&declaration, None)?;
13916    let mut stack = vec![tree.root_node()];
13917    while let Some(node) = stack.pop() {
13918        if let Some(fact) = decode_declared_field_type_node(node, field.identifier(), &declaration)
13919        {
13920            return Some(fact);
13921        }
13922        let mut cursor = node.walk();
13923        stack.extend(node.named_children(&mut cursor));
13924    }
13925    None
13926}
13927
13928/// Decode a field whose generated owner prevents the ordinary source lookup
13929/// from selecting a standalone declaration. The indexed ranges still point
13930/// into the physical syntax tree, so recover the enclosing declaration from
13931/// that structure and apply the same declarator decoder to it.
13932fn decode_indexed_field_declared_type_fact(
13933    analyzer: &CppGraphSource<'_>,
13934    field: &CodeUnit,
13935) -> Option<DeclaredFieldTypeFact> {
13936    let cpp = analyzer.cpp?;
13937    let prepared = cpp.prepared_syntax(analyzer.token, field.source())?;
13938    let source = prepared.source();
13939    let root = prepared.tree().root_node();
13940    for range in analyzer.ranges(field) {
13941        let end = range.start_byte.saturating_add(1).min(source.len());
13942        let mut current = root.descendant_for_byte_range(range.start_byte, end);
13943        while let Some(node) = current {
13944            if matches!(node.kind(), "declaration" | "field_declaration")
13945                && let Some(fact) =
13946                    decode_declared_field_type_node(node, field.identifier(), source)
13947            {
13948                return Some(fact);
13949            }
13950            current = node.parent();
13951        }
13952    }
13953    None
13954}
13955
13956fn decode_declared_field_type_node(
13957    node: Node<'_>,
13958    field_name: &str,
13959    source: &str,
13960) -> Option<DeclaredFieldTypeFact> {
13961    if !matches!(node.kind(), "declaration" | "field_declaration") {
13962        return None;
13963    }
13964    let type_node = node
13965        .child_by_field_name("type")
13966        .or_else(|| first_type_child(node))?;
13967    cpp_declared_field_type_fact(node, type_node, field_name, source)
13968}
13969
13970pub(crate) fn cpp_declared_field_type_fact(
13971    node: Node<'_>,
13972    type_node: Node<'_>,
13973    field_name: &str,
13974    source: &str,
13975) -> Option<DeclaredFieldTypeFact> {
13976    let binding = declared_name_binding(node, type_node, field_name, source)?;
13977    let declared_type = if matches!(
13978        type_node.kind(),
13979        "class_specifier" | "struct_specifier" | "union_specifier"
13980    ) {
13981        type_node.child_by_field_name("name")
13982    } else {
13983        Some(type_node)
13984    };
13985    Some(DeclaredFieldTypeFact {
13986        type_text: declared_type.map_or_else(
13987            || field_name.to_string(),
13988            |declared_type| node_text(declared_type, source).to_string(),
13989        ),
13990        indirection: binding.pointer_depth,
13991        binds_indirectly: binding.indirect,
13992        template_arguments: declared_type
13993            .and_then(|declared_type| cpp_template_reference_arguments(declared_type, source)),
13994    })
13995}
13996
13997/// True when an alias declarator names a function type.
13998///
13999/// The declarator chain is walked through the `declarator` field, so the
14000/// parameter list -- a sibling field -- is never entered and a parameter's own
14001/// function declarator cannot be mistaken for the alias's.
14002pub(crate) fn declarator_names_function_type(declarator: Node<'_>) -> bool {
14003    let mut current = Some(declarator);
14004    while let Some(node) = current {
14005        match node.kind() {
14006            "function_declarator" | "abstract_function_declarator" => return true,
14007            "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
14008                current = node.named_child(0);
14009            }
14010            _ => current = node.child_by_field_name("declarator"),
14011        }
14012    }
14013    false
14014}
14015
14016fn decode_structured_alias_target(
14017    analyzer: &CppGraphSource<'_>,
14018    unit: &CodeUnit,
14019) -> Option<StructuredAliasTarget> {
14020    let facts = analyzer.declaration_source_properties(unit)?;
14021    let first = facts.first()?.alias_target.as_ref()?;
14022    facts
14023        .iter()
14024        .all(|fact| fact.alias_target.as_ref() == Some(first))
14025        .then(|| first.clone())
14026}
14027
14028pub(crate) fn structured_alias_type_target(
14029    mut type_node: Node<'_>,
14030    source: &str,
14031) -> Option<StructuredAliasTarget> {
14032    while type_node.kind() == "type_descriptor" {
14033        type_node = type_node.child_by_field_name("type")?;
14034    }
14035    if type_node.kind() == "primitive_type" {
14036        return Some(StructuredAliasTarget::Builtin);
14037    }
14038    if matches!(
14039        type_node.kind(),
14040        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
14041    ) {
14042        type_node = type_node.child_by_field_name("name")?;
14043    }
14044    let global = type_node.child_by_field_name("scope").is_none()
14045        && type_node.child(0).is_some_and(|child| child.kind() == "::");
14046    let mut components = Vec::new();
14047    append_structured_type_components(type_node, source, &mut components)?;
14048    let arguments = cpp_template_reference_arguments(type_node, source);
14049    (!components.is_empty()).then_some(StructuredAliasTarget::Named {
14050        components,
14051        global,
14052        arguments,
14053    })
14054}
14055
14056fn append_structured_type_components(
14057    node: Node<'_>,
14058    source: &str,
14059    out: &mut Vec<String>,
14060) -> Option<()> {
14061    match node.kind() {
14062        "identifier" | "namespace_identifier" | "type_identifier" => {
14063            out.push(node_text(node, source).to_string());
14064            Some(())
14065        }
14066        "template_type" | "template_function" => {
14067            append_structured_type_components(node.child_by_field_name("name")?, source, out)
14068        }
14069        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
14070            let (scope, name) = qualified_name_parts(node)?;
14071            if let Some(scope) = scope {
14072                append_structured_type_components(scope, source, out)?;
14073            }
14074            append_structured_type_components(name, source, out)
14075        }
14076        _ => None,
14077    }
14078}
14079
14080/// How the declarator that names a declared entity binds the declaration's
14081/// type.
14082pub(crate) struct DeclaredNameBinding {
14083    /// Pointer declarators between the type and the name.
14084    pub pointer_depth: i32,
14085    /// The name binds the declared type through a pointer or a reference, so
14086    /// the declared entity holds no subobject of that type. An array of the
14087    /// type does hold subobjects, so it is not indirect.
14088    pub indirect: bool,
14089}
14090
14091pub(crate) fn declared_name_binding(
14092    declaration: Node<'_>,
14093    type_node: Node<'_>,
14094    field_name: &str,
14095    source: &str,
14096) -> Option<DeclaredNameBinding> {
14097    let mut stack = Vec::new();
14098    let mut cursor = declaration.walk();
14099    stack.extend(
14100        declaration
14101            .named_children(&mut cursor)
14102            .filter(|child| !same_node(*child, type_node)),
14103    );
14104    while let Some(node) = stack.pop() {
14105        if matches!(node.kind(), "identifier" | "field_identifier")
14106            && node_text(node, source) == field_name
14107        {
14108            let mut binding = DeclaredNameBinding {
14109                pointer_depth: 0,
14110                indirect: false,
14111            };
14112            let mut current = node.parent();
14113            while let Some(parent) = current {
14114                if same_node(parent, declaration) {
14115                    return Some(binding);
14116                }
14117                match parent.kind() {
14118                    "pointer_declarator" => {
14119                        binding.pointer_depth += 1;
14120                        binding.indirect = true;
14121                    }
14122                    "reference_declarator" => binding.indirect = true,
14123                    _ => {}
14124                }
14125                current = parent.parent();
14126            }
14127            return None;
14128        }
14129        let mut cursor = node.walk();
14130        stack.extend(node.named_children(&mut cursor));
14131    }
14132    None
14133}
14134
14135fn normalize_field_type_text(type_text: &str) -> String {
14136    const FIELD_SPECIFIERS: [&str; 8] = [
14137        "extern ",
14138        "static ",
14139        "mutable ",
14140        "constexpr ",
14141        "constinit ",
14142        "inline ",
14143        "volatile ",
14144        "const ",
14145    ];
14146
14147    let mut normalized = normalize_type_text(type_text);
14148    loop {
14149        let Some(stripped) = FIELD_SPECIFIERS
14150            .iter()
14151            .find_map(|specifier| normalized.strip_prefix(specifier))
14152        else {
14153            return normalized;
14154        };
14155        normalized = normalize_type_text(stripped);
14156    }
14157}
14158
14159pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
14160    let Some(type_node) = node.child_by_field_name("type") else {
14161        return false;
14162    };
14163    ctx.visibility.resolves_to_type(
14164        &ctx.analyzer,
14165        ctx.file,
14166        node_text(type_node, ctx.source),
14167        owner,
14168    )
14169}
14170
14171pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
14172    !ctx.analyzer
14173        .declarations(ctx.file)
14174        .into_iter()
14175        .filter(|unit| unit.is_function())
14176        .any(|unit| {
14177            ctx.analyzer.ranges(&unit).iter().any(|range| {
14178                node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
14179            })
14180        })
14181}
14182
14183/// How a `T var ...;` declaration initializes its object.
14184pub enum DeclarationConstructorInitializer<'tree> {
14185    /// Direct initialization, `T var(args)` or `T var{args}`: the argument list
14186    /// the declaration hands the constructor.
14187    Arguments(Node<'tree>),
14188    /// Copy initialization from one expression, `T var = expr`, which supplies a
14189    /// single constructor argument without spelling an argument list.
14190    Expression(Node<'tree>),
14191    /// `T var;`, which names no constructor argument at all.
14192    Empty,
14193}
14194
14195pub fn declaration_constructor_initializer(
14196    node: Node<'_>,
14197) -> DeclarationConstructorInitializer<'_> {
14198    let mut cursor = node.walk();
14199    for child in node.named_children(&mut cursor) {
14200        if child.kind() == "init_declarator" {
14201            let Some(value) = child
14202                .child_by_field_name("value")
14203                .or_else(|| first_named_child_of_kind(child, "initializer_list"))
14204                .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
14205            else {
14206                return DeclarationConstructorInitializer::Empty;
14207            };
14208            return match value.kind() {
14209                "argument_list" | "initializer_list" => {
14210                    DeclarationConstructorInitializer::Arguments(value)
14211                }
14212                "compound_literal_expression" => call_arguments_node(value)
14213                    .map_or(DeclarationConstructorInitializer::Empty, |arguments| {
14214                        DeclarationConstructorInitializer::Arguments(arguments)
14215                    }),
14216                _ => DeclarationConstructorInitializer::Expression(value),
14217            };
14218        }
14219        if let Some(declarator) = declaration_declarator(node, child) {
14220            return declarator_parameters(declarator)
14221                .map_or(DeclarationConstructorInitializer::Empty, |parameters| {
14222                    DeclarationConstructorInitializer::Arguments(parameters)
14223                });
14224        }
14225    }
14226    DeclarationConstructorInitializer::Empty
14227}
14228
14229pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
14230    match declaration_constructor_initializer(node) {
14231        DeclarationConstructorInitializer::Arguments(arguments) => {
14232            argument_children(arguments).count()
14233        }
14234        DeclarationConstructorInitializer::Expression(_) => 1,
14235        DeclarationConstructorInitializer::Empty => 0,
14236    }
14237}
14238
14239/// The parameter list of the innermost declarator, which is where a
14240/// `T var(args)` declaration parsed as a function declarator keeps the
14241/// constructor arguments.
14242fn declarator_parameters(node: Node<'_>) -> Option<Node<'_>> {
14243    let mut current = node;
14244    loop {
14245        if let Some(parameters) = current.child_by_field_name("parameters") {
14246            return Some(parameters);
14247        }
14248        current = current.child_by_field_name("declarator")?;
14249    }
14250}
14251
14252fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
14253    brokk_bifrost_core::analyzer::tree_walk::first_named_descendant_of_kind_inclusive(root, kind)
14254}
14255
14256fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
14257    if node.kind() == "identifier" {
14258        return true;
14259    }
14260    if node.kind() == "parenthesized_expression" {
14261        return false;
14262    }
14263    if node.kind() == "call_expression" {
14264        return node
14265            .child_by_field_name("function")
14266            .is_some_and(|function| function.kind() == "identifier");
14267    }
14268    let mut stack = vec![node];
14269    while let Some(descendant) = stack.pop() {
14270        if descendant != node && descendant.kind() == "parenthesized_expression" {
14271            continue;
14272        }
14273        if descendant.kind() == "identifier" {
14274            return true;
14275        }
14276        if descendant.kind() == "call_expression" {
14277            if descendant
14278                .child_by_field_name("function")
14279                .is_some_and(|function| function.kind() == "identifier")
14280            {
14281                return true;
14282            }
14283            continue;
14284        }
14285        push_named_children_reversed(descendant, &mut stack);
14286    }
14287    false
14288}
14289
14290fn macro_expansion_shape_is_safe(
14291    node: Node<'_>,
14292    source: &str,
14293    parameters: &[String],
14294    environment: &MacroEnvironment,
14295) -> bool {
14296    if matches!(node.kind(), "identifier" | "parenthesized_expression") {
14297        return true;
14298    }
14299    if node.kind() == "call_expression" {
14300        let Some(function) = node.child_by_field_name("function") else {
14301            return true;
14302        };
14303        if function.kind() != "identifier" {
14304            return true;
14305        }
14306        let function_name = node_text(function, source);
14307        if parameters
14308            .iter()
14309            .any(|parameter| parameter == function_name)
14310        {
14311            return false;
14312        }
14313        if !environment.may_bind(function_name) {
14314            return true;
14315        }
14316        let Some(arguments) = node.child_by_field_name("arguments") else {
14317            return false;
14318        };
14319        return argument_children(arguments).all(|argument| {
14320            if argument.kind() == "identifier"
14321                && parameters
14322                    .iter()
14323                    .any(|parameter| parameter == node_text(argument, source))
14324            {
14325                return false;
14326            }
14327            macro_expansion_shape_is_safe(argument, source, parameters, environment)
14328        });
14329    }
14330    let mut stack = vec![node];
14331    while let Some(descendant) = stack.pop() {
14332        if descendant != node {
14333            if descendant.kind() == "parenthesized_expression" {
14334                continue;
14335            }
14336            if descendant.kind() == "call_expression" {
14337                let expands = descendant
14338                    .child_by_field_name("function")
14339                    .filter(|function| function.kind() == "identifier")
14340                    .is_some_and(|function| environment.may_bind(node_text(function, source)));
14341                if expands {
14342                    return false;
14343                }
14344                continue;
14345            }
14346        }
14347        if descendant.kind() == "identifier" {
14348            let identifier = node_text(descendant, source);
14349            if parameters.iter().any(|parameter| parameter == identifier)
14350                || environment.may_bind(identifier)
14351            {
14352                return false;
14353            }
14354        }
14355        push_named_children_reversed(descendant, &mut stack);
14356    }
14357    true
14358}
14359
14360fn collect_structured_include_facts(prepared: &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]> {
14361    let source = prepared.source();
14362    let mut facts = Vec::new();
14363    let mut nodes = vec![prepared.tree().root_node()];
14364    while let Some(node) = nodes.pop() {
14365        if node.kind() == "preproc_include" {
14366            let Some(path) = node
14367                .child_by_field_name("path")
14368                .and_then(|path| crate::imports::include_path_literal_value(path, source))
14369                .map(str::to_owned)
14370            else {
14371                continue;
14372            };
14373            facts.push(StructuredIncludeFact {
14374                start_byte: node.start_byte(),
14375                end_byte: node.end_byte(),
14376                path,
14377            });
14378            continue;
14379        }
14380        push_named_children_reversed(node, &mut nodes);
14381    }
14382    Arc::from(facts.into_boxed_slice())
14383}
14384
14385fn has_unresolved_include_visible_before_in_prepared(
14386    file: &ProjectFile,
14387    prepared: &PreparedSyntaxTree,
14388    include_targets: &IncludeTargetIndex,
14389    facts: &[StructuredIncludeFact],
14390    before_byte: usize,
14391) -> bool {
14392    let guards = OnceCell::new();
14393    let reference = CallableReferenceContext {
14394        file,
14395        position: Some(CallableReferencePosition {
14396            prepared,
14397            byte: before_byte,
14398            guards: &guards,
14399        }),
14400    };
14401    let root = prepared.tree().root_node();
14402    facts
14403        .iter()
14404        .filter(|fact| fact.end_byte <= before_byte)
14405        .any(|fact| {
14406            let node = root
14407                .descendant_for_byte_range(fact.start_byte, fact.end_byte)
14408                .expect("structured include fact range must be in prepared tree");
14409            assert_eq!(
14410                node.kind(),
14411                "preproc_include",
14412                "structured include fact range must identify its include node"
14413            );
14414            callable_preprocessor_context_is_visible_for_reference(
14415                node,
14416                prepared.source(),
14417                &reference,
14418            ) && resolve_include_targets_with_index(file, &fact.path, include_targets).is_empty()
14419        })
14420}
14421
14422fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
14423    let descendant = node;
14424    while let Some(parent) = node.parent() {
14425        if is_cpp_preprocessor_conditional(parent)
14426            && !is_file_covering_include_guard(parent, source)
14427            && preprocessor_conditional_contains_descendant(parent, descendant)
14428        {
14429            return true;
14430        }
14431        node = parent;
14432    }
14433    false
14434}
14435
14436/// The [`OwningPreprocessorConditionals`] of a macro event at `event`.
14437///
14438/// The descendant this walks up from is the one
14439/// [`VisibilityIndex::macro_event_condition_value`] starts from -- the
14440/// innermost node at the event's first byte, not the event node itself --
14441/// because containment compares that descendant's end against the recovered
14442/// conditional boundary, and the two nodes end in different places. An event
14443/// that [`has_preprocessor_conditional_ancestor`] rejects owns nothing: that
14444/// predicate is what has always decided whether an event is conditional at
14445/// all, and answering it first also skips the walk for the ordinary
14446/// unconditional event.
14447fn owning_preprocessor_conditionals(
14448    root: Node<'_>,
14449    event: Node<'_>,
14450    source: &str,
14451) -> OwningPreprocessorConditionals {
14452    if !has_preprocessor_conditional_ancestor(event, source) {
14453        return OwningPreprocessorConditionals::default();
14454    }
14455    let start = event.start_byte();
14456    let descendant = root
14457        .descendant_for_byte_range(start, start.saturating_add(1).min(source.len()))
14458        .expect("a byte inside the parsed tree names a descendant");
14459    let mut owners = Vec::new();
14460    let mut current = descendant.parent();
14461    while let Some(conditional) = current {
14462        if is_cpp_preprocessor_conditional(conditional)
14463            && !is_file_covering_include_guard(conditional, source)
14464            && preprocessor_conditional_contains_descendant(conditional, descendant)
14465        {
14466            owners.push(conditional.start_byte());
14467        }
14468        current = conditional.parent();
14469    }
14470    owners.into_boxed_slice()
14471}
14472
14473fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
14474    node.parent()
14475        .filter(|parent| parent.kind() == "translation_unit")
14476        .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
14477        && is_canonical_include_guard(node, source)
14478}
14479
14480fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
14481    if node.kind() != "preproc_ifdef"
14482        || node
14483            .child(0)
14484            .is_none_or(|directive| directive.kind() != "#ifndef")
14485        || node.child_by_field_name("alternative").is_some()
14486    {
14487        return false;
14488    }
14489    let Some(guard_name) = node.child_by_field_name("name") else {
14490        return false;
14491    };
14492    let mut cursor = node.walk();
14493    node.named_children(&mut cursor)
14494        .find(|child| *child != guard_name && child.kind() != "comment")
14495        .filter(|child| child.kind() == "preproc_def")
14496        .and_then(|definition| definition.child_by_field_name("name"))
14497        .is_some_and(|defined_name| {
14498            node_text(defined_name, source) == node_text(guard_name, source)
14499        })
14500}
14501
14502fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
14503    let mut guard = None;
14504    for child in named_children_iter(root) {
14505        if child.kind() == "comment" || is_pragma_once(child, source) {
14506            continue;
14507        }
14508        if guard.is_none() && is_canonical_include_guard(child, source) {
14509            guard = Some(child);
14510        } else {
14511            return None;
14512        }
14513    }
14514    guard
14515        .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
14516        .map(|name| node_text(name, source).to_string())
14517}
14518
14519fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
14520    if (0..root.named_child_count())
14521        .filter_map(|index| root.named_child(index))
14522        .any(|child| is_pragma_once(child, source))
14523    {
14524        return MacroIncludeProtection::PragmaOnce;
14525    }
14526    top_level_canonical_include_guard_name(root, source)
14527        .map(MacroIncludeProtection::MacroGuard)
14528        .unwrap_or(MacroIncludeProtection::None)
14529}
14530
14531fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
14532    node.kind() == "preproc_call"
14533        && node
14534            .child_by_field_name("directive")
14535            .is_some_and(|directive| node_text(directive, source) == "#pragma")
14536        && node
14537            .child_by_field_name("argument")
14538            .is_some_and(|argument| node_text(argument, source).trim() == "once")
14539}
14540
14541fn parse_preproc_identifier(argument: &str) -> Option<String> {
14542    let parsed = parse_sentinel(
14543        "void __bifrost_undef() { ",
14544        SentinelInput::replacement(argument),
14545        "; }",
14546    );
14547    let (sentinel, tree) = (parsed.source, parsed.tree);
14548    if tree.root_node().has_error() {
14549        return None;
14550    }
14551    let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
14552    // A line comment before the synthetic terminator is a named extra, not
14553    // another expression in the directive argument.
14554    let identifier = single_named_child_matching(statement, |child| !child.is_extra())?;
14555    (identifier.kind() == "identifier").then(|| node_text(identifier, &sentinel).to_string())
14556}
14557
14558pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
14559    match node.kind() {
14560        "identifier" | "field_identifier" => {
14561            let name = node_text(node, source).trim();
14562            (!name.is_empty()).then(|| name.to_string())
14563        }
14564        "abstract_array_declarator"
14565        | "abstract_function_declarator"
14566        | "abstract_parenthesized_declarator"
14567        | "abstract_pointer_declarator"
14568        | "abstract_reference_declarator" => None,
14569        "function_declarator" => node
14570            .child_by_field_name("declarator")
14571            .or_else(|| node.child_by_field_name("name"))
14572            .and_then(|child| extract_variable_name(child, source)),
14573        _ => node
14574            .child_by_field_name("declarator")
14575            .or_else(|| node.child_by_field_name("name"))
14576            .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
14577            .and_then(|child| extract_variable_name(child, source)),
14578    }
14579}
14580
14581/// Whether `file` is proven to use plain-C source semantics.
14582///
14583/// `Language::Cpp` intentionally serves both C and C++. Headers do not carry a
14584/// compilation dialect on their own, so only an exact `.c` source extension is
14585/// sufficient to reinterpret C++-grammar keyword nodes such as `this` as C
14586/// identifiers.
14587///
14588/// The exact-lowercase-`.c` rule itself lives in [`LanguageDialect::for_path`],
14589/// which extraction reads too (a `.c` file is extracted with C tag scope), so
14590/// the doctrine has exactly one definition.
14591pub fn is_c_source_file(file: &ProjectFile) -> bool {
14592    LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
14593}
14594
14595/// Whether tree-sitter parsed the operand of C `sizeof(T)` as an expression
14596/// identifier even though `T` may denote a typedef.
14597///
14598/// The grammar cannot distinguish `sizeof(value)` from `sizeof(Type)` without
14599/// semantic information. Keep this helper structural and narrow; callers must
14600/// still prove a visible type and reject an active ordinary-namespace shadow.
14601pub fn is_c_sizeof_expression_type_candidate(file: &ProjectFile, node: Node<'_>) -> bool {
14602    if !is_c_source_file(file) || node.kind() != "identifier" {
14603        return false;
14604    }
14605    let mut operand = node;
14606    while let Some(parent) = operand.parent().filter(|parent| {
14607        parent.kind() == "parenthesized_expression"
14608            && parent.named_child_count() == 1
14609            && parent.named_child(0) == Some(operand)
14610    }) {
14611        operand = parent;
14612    }
14613    operand.parent().is_some_and(|parent| {
14614        parent.kind() == "sizeof_expression" && parent.child_by_field_name("value") == Some(operand)
14615    })
14616}
14617
14618/// Return the type and member leaves of a C `offsetof` member designator.
14619///
14620/// `offsetof_expression` is a dedicated tree-sitter node, so its two operands
14621/// must be interpreted through their named fields.  In particular, do not
14622/// infer the aggregate from the enclosing lexical scope: an `offsetof` can
14623/// name a member of an unrelated aggregate, including a field promoted from
14624/// an anonymous union.  A missing or unsupported operand is deliberately
14625/// rejected so callers can keep the reference unresolved.
14626pub fn c_offsetof_member_parts(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
14627    if !is_c_offsetof_member_node(node) {
14628        return None;
14629    }
14630    let expression = node.parent()?;
14631    if expression.has_error() {
14632        return None;
14633    }
14634    let closing = expression.child(expression.child_count().saturating_sub(1))?;
14635    if closing.kind() != ")" || closing.is_missing() {
14636        return None;
14637    }
14638    let type_descriptor = expression.child_by_field_name("type")?;
14639    if type_descriptor.kind() != "type_descriptor"
14640        || type_descriptor.is_missing()
14641        || type_descriptor.has_error()
14642    {
14643        return None;
14644    }
14645    let type_specifier = type_descriptor.child_by_field_name("type")?;
14646    if type_specifier.is_missing() || type_specifier.has_error() {
14647        return None;
14648    }
14649    let type_reference = match type_specifier.kind() {
14650        "class_specifier" | "struct_specifier" | "union_specifier" => {
14651            type_specifier.child_by_field_name("name")?
14652        }
14653        _ => type_specifier,
14654    };
14655    (!type_reference.is_missing() && !type_reference.has_error()).then_some((type_reference, node))
14656}
14657
14658/// Whether `node` is the member leaf of an `offsetof_expression`, including a
14659/// malformed type operand.  Callers use this guard to prevent the ordinary
14660/// field-name heuristics from guessing an owner after structured resolution
14661/// has failed.
14662pub fn is_c_offsetof_member_node(node: Node<'_>) -> bool {
14663    node.kind() == "field_identifier"
14664        && node.parent().is_some_and(|parent| {
14665            parent.kind() == "offsetof_expression"
14666                && parent.child_by_field_name("member") == Some(node)
14667        })
14668}
14669
14670/// Admit an ABI 15 template-function shape to type resolution only through
14671/// the structured argument list of a type descriptor. This is syntax evidence;
14672/// callers must still resolve a visible type binding before proving identity.
14673pub fn cpp_template_function_has_type_owner(node: Node<'_>) -> bool {
14674    if node.kind() != "template_function" {
14675        return false;
14676    }
14677    let Some(arguments) = node
14678        .parent()
14679        .filter(|parent| parent.kind() == "template_argument_list")
14680    else {
14681        return false;
14682    };
14683    let Some(owner) = arguments.parent().filter(|parent| {
14684        parent.kind() == "template_type"
14685            && parent.child_by_field_name("arguments") == Some(arguments)
14686    }) else {
14687        return false;
14688    };
14689
14690    let mut current = owner;
14691    while let Some(parent) = current.parent() {
14692        if parent.kind() == "type_descriptor" && parent.child_by_field_name("type") == Some(current)
14693        {
14694            return true;
14695        }
14696        if parent.kind() == "qualified_identifier"
14697            && parent.child_by_field_name("name") == Some(current)
14698        {
14699            current = parent;
14700            continue;
14701        }
14702        return false;
14703    }
14704    false
14705}
14706
14707/// Whether `node` is a template argument name that tree-sitter spelled with
14708/// type syntax.
14709///
14710/// The grammar cannot tell a type argument from a non-type (value) argument, so
14711/// it gives both the same shape:
14712/// `template_argument_list -> type_descriptor -> type_identifier`. In
14713/// `std::array<W, N>` the type `W` and the constant `N` parse identically, and
14714/// so do `std::span<const uint8_t, ED448_LEN>`'s length and a nested type
14715/// member used as a real type argument.
14716///
14717/// This helper reports only the syntactic position. A caller must still prove
14718/// which namespace explains the spelling: forward navigation asks the type
14719/// namespace first and reads the leaf as a value only when no type explains it,
14720/// and the inverse field scan admits the leaf only when no visible type does
14721/// (#2556).
14722pub fn is_type_shaped_template_argument_name(node: Node<'_>) -> bool {
14723    if node.kind() != "type_identifier" {
14724        return false;
14725    }
14726    let Some(descriptor) = node
14727        .parent()
14728        .filter(|parent| parent.kind() == "type_descriptor")
14729    else {
14730        return false;
14731    };
14732    if descriptor.child_by_field_name("type") != Some(node) {
14733        return false;
14734    }
14735    let Some(arguments) = descriptor
14736        .parent()
14737        .filter(|parent| parent.kind() == "template_argument_list")
14738    else {
14739        return false;
14740    };
14741    arguments.parent().is_some_and(|owner| {
14742        matches!(
14743            owner.kind(),
14744            "template_type" | "template_function" | "template_method"
14745        ) && owner.child_by_field_name("arguments") == Some(arguments)
14746    })
14747}
14748
14749/// Whether a reference written in `file` reads C++ source with C semantics.
14750///
14751/// [`is_c_source_file`] answers the half a path settles on its own. The other
14752/// half is a header, which has no dialect of its own: it is read as C exactly
14753/// when every workspace translation unit that provably compiles it compiles it
14754/// as C ([`CppSource::header_uses_c_semantics`], issue #1970).
14755///
14756/// This is the gate for anything that is really about the compilation
14757/// language of the code being read -- which reading of an included header's
14758/// declarations is in scope, whether `this` is an ordinary identifier. It is
14759/// NOT the gate for a question that is genuinely about a `.c` file on disk;
14760/// those keep calling [`is_c_source_file`].
14761pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
14762    is_c_source_file(file) || cpp.header_uses_c_semantics(file)
14763}
14764
14765pub fn is_declarator_node(node: Node<'_>) -> bool {
14766    matches!(
14767        node.kind(),
14768        "identifier"
14769            | "field_identifier"
14770            | "qualified_identifier"
14771            | "scoped_identifier"
14772            | "pointer_declarator"
14773            | "reference_declarator"
14774            | "array_declarator"
14775            | "parenthesized_declarator"
14776            | "function_declarator"
14777    )
14778}
14779
14780/// Returns the declarator represented by one direct child of a declaration.
14781///
14782/// Tree-sitter exposes the first declarator through the `declarator` field,
14783/// but subsequent comma-separated declarators are unfielded direct children:
14784/// `int first, second;` therefore has an `identifier` child for `first` and a
14785/// second, otherwise identical, `identifier` child for `second`. Consumers
14786/// must inspect both shapes or a later declarator can be mistaken for a use of
14787/// an unrelated indexed symbol.
14788pub fn declaration_declarator<'tree>(
14789    declaration: Node<'tree>,
14790    child: Node<'tree>,
14791) -> Option<Node<'tree>> {
14792    if !matches!(
14793        declaration.kind(),
14794        "declaration"
14795            | "field_declaration"
14796            | "parameter_declaration"
14797            | "optional_parameter_declaration"
14798            | "function_definition"
14799            | "type_definition"
14800            | "alias_declaration"
14801            | "template_instantiation"
14802    ) {
14803        return None;
14804    }
14805    if declaration
14806        .child_by_field_name("type")
14807        .is_some_and(|type_node| same_node(type_node, child))
14808    {
14809        return None;
14810    }
14811    if child.kind() == "init_declarator" {
14812        return child.child_by_field_name("declarator");
14813    }
14814    let field = field_name_in_parent(declaration, child);
14815    if (is_declarator_node(child) && matches!(field, Some("declarator") | None))
14816        || (declaration.kind() == "type_definition"
14817            && child.kind() == "type_identifier"
14818            && matches!(field, Some("declarator") | None))
14819    {
14820        Some(child)
14821    } else {
14822        None
14823    }
14824}
14825
14826/// Read the name, declarator, and optional type of a typed local binding.
14827pub fn cpp_binding_parts<'tree>(
14828    node: Node<'tree>,
14829    source: &str,
14830) -> Option<(String, Node<'tree>, Option<Node<'tree>>)> {
14831    let declarator = node.child_by_field_name("declarator")?;
14832    let name = extract_variable_name(declarator, source)?;
14833    let type_node = node
14834        .child_by_field_name("type")
14835        .or_else(|| first_type_child(node));
14836    Some((name, declarator, type_node))
14837}
14838
14839/// One run of a container's children whose parsed namespaces differ from
14840/// their lexical namespaces, with the complete lexical namespace path.
14841#[derive(Clone, Debug, PartialEq, Eq)]
14842pub struct RecoveredNamespaceRegion {
14843    /// Start byte of the first child in the run.
14844    pub start: usize,
14845    /// End byte of the last child in the run.
14846    pub end: usize,
14847    /// The complete enclosing namespace path of the run, outermost first.
14848    /// This can be empty when a parsed namespace extends past its real close.
14849    pub components: Vec<String>,
14850    /// The `namespace Name { ... }` construct that opens each of `components`,
14851    /// parallel to it, from the `namespace` keyword through the close the index
14852    /// proved for its `{`. A proven surviving namespace definition uses its
14853    /// body's direct close; a recovered head, or a namespace body exposed by
14854    /// malformed member recovery, uses the file-global brace stack. A level
14855    /// whose close the index could not prove -- the file never closes it, or
14856    /// the file's brace token stream lost an opening token (#3328) -- carries
14857    /// the head alone. This is the range the declaration walk records for the
14858    /// Module, so a namespace whose head collapsed into an `ERROR` reports
14859    /// where it is written rather than where its first member is (#3309).
14860    pub component_ranges: Vec<Range>,
14861    /// The name token each of `components` is written as, parallel to it. The
14862    /// declaration walk interns this subspan as the Module's name occurrence:
14863    /// the node the walk is visiting when it declares the level belongs to an
14864    /// unrelated construct inside the region, and deriving the name from it
14865    /// paired the level's range with that construct's name token (#4292).
14866    pub component_name_ranges: Vec<Range>,
14867    /// The close the index proved for each level's `{`, parallel to
14868    /// `components`. A node after that close is outside the level (#3633).
14869    /// `None` when the file never closes the level or the walk proved no
14870    /// pairing in the file (#3328); the level then applies to the whole region
14871    /// as a best-effort scope.
14872    pub component_closes: Vec<Option<Range>>,
14873}
14874
14875/// The namespaces C++ parse recovery drops from a file's tree.
14876///
14877/// When tree-sitter cannot parse a construct inside a namespace body it closes
14878/// an inner scope with a MISSING brace, or skips an opening brace into an
14879/// ERROR node. Every real `}` after that then closes one scope too early: a
14880/// class body's `}` closes the namespace, the namespace's own `}` closes its
14881/// parent, and the outermost real closes land in a trailing ERROR node. The
14882/// declarations between a stolen close and the real one keep their byte
14883/// positions but lose their `namespace_definition` ancestors (Catch2's
14884/// `catch_matchers_templated.hpp`, issue #1537).
14885///
14886/// A file-global stack over real AST brace tokens restores both lost and
14887/// overextended namespaces. Missing braces do nothing; error-free subtrees are
14888/// balanced and can be skipped. For each child of a damaged container, compare
14889/// the namespaces still open on that stack with the parsed ancestor path. A
14890/// difference yields a region carrying the complete lexical path, including
14891/// an empty path when recovery swallowed file-scope declarations. Consecutive
14892/// children sharing that path form one [`RecoveredNamespaceRegion`]. The same
14893/// pass retains matching brace positions for declaration partitioning (#3087).
14894/// A file without parse errors needs neither correction nor a brace index.
14895///
14896/// The stack restores the source's nesting only while the tree's brace tokens
14897/// are the source's braces. Severe recovery can break that even when the real
14898/// brace counts balance: in simdjson's `compile_time_json-inl.h`, ABI 15 keeps
14899/// the reflection case labels as `character` nodes but loses the outer
14900/// namespace's closing token. The global stack then cascades the function and
14901/// inner namespace closes into the wrong owners. A surviving
14902/// `namespace_definition` can supply stronger provenance when its body has a
14903/// genuine declaration boundary: its direct real `}` belongs to that
14904/// namespace, while a missing direct close proves that the namespace has no
14905/// close to claim. A body whose direct boundary is itself recovered from a
14906/// class/member error stays with the global fallback. The proven boundaries
14907/// override the global stack only when the file has no unpaired real close. An
14908/// unpaired close proves the stream lost an open at a position the stack cannot
14909/// recover, so all pairing in that file remains invalid:
14910/// [`Self::matching_close_brace`] then answers nothing and each restored level
14911/// keeps the head that writes it. The namespace paths remain, as the
14912/// best-effort structure recovery they are.
14913///
14914/// Every consumer reads a node's namespace path through one authority rule,
14915/// shared by [`Self::enclosing_namespace_levels`] and
14916/// [`Self::restore_enclosing_namespaces`], so declaration collection and both
14917/// lookup directions agree:
14918///
14919/// - Outside every region, the node's parsed `namespace_definition` chain is
14920///   the whole answer.
14921/// - Inside a region, the region's restored levels come first and only the
14922///   parsed ancestors that start inside the region follow them. The region
14923///   overrides a parsed ancestor that starts before it, which is how a
14924///   namespace the tree kept open past its real close gives up the
14925///   declarations after that close (#3087).
14926/// - A restored level stops applying at the close the stack paired with its
14927///   `{`, and so does every level inside it. The walk compares a child with
14928///   the stack before it descends into that child, so a run can reach past a
14929///   close inside its last child. In simdjson's `tests/dom/basictests.cpp` the
14930///   collapsed `namespace type_tests {` on line 1467 closes on line 1811, and
14931///   recovery put that `}` inside a `preproc_else` that runs to line 2505: the
14932///   region that restores `type_tests` covers the whole `preproc_else`, and
14933///   without this check every namespace written after the close inherited
14934///   `type_tests` (#3633). A level whose close the stack did not prove applies
14935///   to the whole region, the best-effort answer #3328 leaves.
14936/// - A region whose restored path is empty keeps exactly the parsed ancestors
14937///   whose own `{` and `}` are both real tokens, wherever they start, and drops
14938///   the others. This holds whether or not the file's pairing is proven.
14939#[derive(Clone, Debug, Default)]
14940pub struct OrphanedNamespaceScopeIndex {
14941    regions: Vec<RecoveredNamespaceRegion>,
14942    /// Open-brace start byte to the close the file-global stack paired with it.
14943    /// Empty when the walk proved the brace token stream incomplete.
14944    brace_closes: HashMap<usize, Range>,
14945}
14946
14947impl OrphanedNamespaceScopeIndex {
14948    pub fn build(root: Node<'_>, source: &str) -> Self {
14949        if !root.has_error() {
14950            return Self::default();
14951        }
14952        /// One namespace level open on the brace stack: the `namespace Name {`
14953        /// head it is written as, and the `{`'s start byte, which is the
14954        /// `brace_closes` key its close lands under. The close is not known
14955        /// while the level is open, so the final ranges are assembled once the
14956        /// walk has paired every brace.
14957        #[derive(Clone, Copy)]
14958        struct OpenHead {
14959            head: Range,
14960            open: usize,
14961        }
14962        /// A [`RecoveredNamespaceRegion`] whose levels still name their open
14963        /// braces rather than their closes.
14964        struct PendingRegion {
14965            start: usize,
14966            end: usize,
14967            components: Vec<String>,
14968            heads: Vec<OpenHead>,
14969            name_ranges: Vec<Range>,
14970        }
14971        struct Frame<'tree> {
14972            node: Node<'tree>,
14973            children: Vec<Node<'tree>>,
14974            next: usize,
14975            parsed_scope: Vec<String>,
14976            run: Option<PendingRegion>,
14977        }
14978        fn frame<'tree>(
14979            node: Node<'tree>,
14980            mut parsed_scope: Vec<String>,
14981            source: &str,
14982        ) -> Frame<'tree> {
14983            if node.kind() == "namespace_definition"
14984                && let Some(name) = node.child_by_field_name("name")
14985            {
14986                let mut components = Vec::new();
14987                if append_cpp_name_components(name, source, &mut components).is_some() {
14988                    parsed_scope.extend(components);
14989                }
14990            }
14991            let mut cursor = node.walk();
14992            Frame {
14993                node,
14994                children: node.children(&mut cursor).collect(),
14995                next: 0,
14996                parsed_scope,
14997                run: None,
14998            }
14999        }
15000        let mut regions = Vec::new();
15001        let mut brace_closes = HashMap::default();
15002        // A surviving namespace_definition may own a direct brace boundary of
15003        // its declaration_list. Keep that evidence separate from the file-
15004        // global stack: recovery can lose an outer namespace close while
15005        // retaining balanced braces from nested constructs, so token order
15006        // alone can assign a real close to the wrong namespace. The helper
15007        // rejects body boundaries exposed by malformed class/member recovery.
15008        let mut namespace_brace_proofs = Vec::new();
15009        // Real closes that arrived with nothing open. Each one proves the tree
15010        // is missing a scope-opening token the source writes, which is what
15011        // makes every pairing in the file unusable as an extent (#3328). The
15012        // complete set is kept, not a count, so a walk that has to be diagnosed
15013        // shows every position the stream went out of sync.
15014        let mut unpaired_closes: Vec<Range> = Vec::new();
15015        // The brace stack belongs to the file, not to a parser frame. A real
15016        // close inside a damaged child can close its parent's namespace. The
15017        // following children must see that removal immediately (#3087).
15018        let mut open = Vec::new();
15019        let mut lexical_scope = Vec::new();
15020        // One head per entry of `lexical_scope`: `namespace a::b {` opens two
15021        // levels and both are written by that one head, exactly as the
15022        // ordinary namespace visit records one range for every level of the
15023        // C++17 shorthand (#1878).
15024        let mut lexical_heads: Vec<OpenHead> = Vec::new();
15025        // One name token per entry of `lexical_scope`: unlike the shared head,
15026        // each level of a `namespace a::b {` shorthand has its own token.
15027        let mut lexical_name_ranges: Vec<Range> = Vec::new();
15028        let mut frames = vec![frame(root, Vec::new(), source)];
15029        // A frame's node is the previous frame's direct child, so the frame
15030        // below answers what Node::parent would without re-descending from the
15031        // root; per-node parent or sibling climbs are quadratic over a wide
15032        // translation unit (#3141).
15033        while !frames.is_empty() {
15034            let parent_node = frames.len().checked_sub(2).map(|index| frames[index].node);
15035            let current = frames.last_mut().expect("frames is non-empty");
15036            if current.next == current.children.len() {
15037                regions.extend(frames.pop().expect("the frame just borrowed").run);
15038                continue;
15039            }
15040            let child = current.children[current.next];
15041            current.next += 1;
15042            match child.kind() {
15043                "{" if !child.is_missing() => {
15044                    regions.extend(current.run.take());
15045                    if let Some(namespace) = parent_node.filter(|parent| {
15046                        parent.kind() == "namespace_definition"
15047                            && parent.child_by_field_name("body") == Some(current.node)
15048                            && child.start_byte() == current.node.start_byte()
15049                    }) && let Some(proof) = namespace_body_brace_proof(current.node)
15050                    {
15051                        namespace_brace_proofs.push((child.start_byte(), namespace, proof));
15052                    }
15053                    // The head is the construct that writes the namespace: the
15054                    // `namespace_definition` node when one survived, and the
15055                    // bare `namespace` keyword when recovery collapsed the head
15056                    // into flat `ERROR` children (#3084).
15057                    let mut named = parent_node.and_then(|parent| {
15058                        namespace_body_name_components(parent, current.node, source)
15059                    });
15060                    if named.is_none() {
15061                        named = recovered_namespace_open_components(
15062                            &current.children[..current.next - 1],
15063                            source,
15064                        );
15065                    }
15066                    open.push((child.start_byte(), lexical_scope.len()));
15067                    if let Some((written_at, components)) = named {
15068                        let head = OpenHead {
15069                            head: Range {
15070                                start_byte: written_at.start_byte(),
15071                                end_byte: child.end_byte(),
15072                                start_line: written_at.start_position().row + 1,
15073                                end_line: child.end_position().row + 1,
15074                            },
15075                            open: child.start_byte(),
15076                        };
15077                        lexical_heads.extend(std::iter::repeat_n(head, components.len()));
15078                        lexical_name_ranges
15079                            .extend(components.iter().map(|(_, name_range)| *name_range));
15080                        lexical_scope
15081                            .extend(components.into_iter().map(|(component, _)| component));
15082                    }
15083                    continue;
15084                }
15085                "}" if !child.is_missing() => {
15086                    regions.extend(current.run.take());
15087                    let close = Range {
15088                        start_byte: child.start_byte(),
15089                        end_byte: child.end_byte(),
15090                        start_line: child.start_position().row + 1,
15091                        end_line: child.end_position().row + 1,
15092                    };
15093                    match open.pop() {
15094                        Some((start, namespace_len)) => {
15095                            debug_assert!(
15096                                start < close.start_byte,
15097                                "a close pairs with an open before it: {start} then {close:?}"
15098                            );
15099                            lexical_scope.truncate(namespace_len);
15100                            lexical_heads.truncate(namespace_len);
15101                            lexical_name_ranges.truncate(namespace_len);
15102                            brace_closes.insert(start, close);
15103                        }
15104                        None => unpaired_closes.push(close),
15105                    }
15106                    continue;
15107                }
15108                _ => {}
15109            }
15110            // The namespace head and body precede their opening brace; their
15111            // own parsed namespace is not yet on the lexical stack. Compare
15112            // the items inside the body, not this header bookkeeping.
15113            if current.node.kind() != "namespace_definition"
15114                && lexical_scope != current.parsed_scope
15115            {
15116                match &mut current.run {
15117                    Some(run) if run.components == lexical_scope => run.end = child.end_byte(),
15118                    run => {
15119                        regions.extend(run.take());
15120                        *run = Some(PendingRegion {
15121                            start: child.start_byte(),
15122                            end: child.end_byte(),
15123                            components: lexical_scope.clone(),
15124                            heads: lexical_heads.clone(),
15125                            name_ranges: lexical_name_ranges.clone(),
15126                        });
15127                    }
15128                }
15129            } else {
15130                regions.extend(current.run.take());
15131            }
15132            // Error-free subtrees are balanced. Keeping them intact also
15133            // leaves their valid nested namespaces to the ordinary ancestry
15134            // walk in restore_enclosing_namespaces. A clean
15135            // namespace-sentinel wrapper (the macro-declaration grammar reads
15136            // it as a function_definition whose type is the literal namespace
15137            // keyword) is the one exception: its body is a statement-context
15138            // compound whose members need the recovered scope.
15139            let descend = child.has_error()
15140                || (child.kind() == "function_definition"
15141                    && child.child_by_field_name("body").is_some_and(|body| {
15142                        namespace_body_name_components(child, body, source).is_some()
15143                    }));
15144            if descend {
15145                let parsed_scope = current.parsed_scope.clone();
15146                frames.push(frame(child, parsed_scope, source));
15147            }
15148        }
15149        // A close with nothing open is only possible when a `{` the source
15150        // writes is not a `{` token of the tree. The stack then paired some
15151        // earlier open with a close that belongs to an inner scope, and
15152        // nothing in the token stream says which pairs absorbed the loss: the
15153        // lost token sits at an unknown position before the close that
15154        // exposed it. Keep no pairing rather than an unprovable extent. An
15155        // open the file never closes is the opposite case and stays: it makes
15156        // no claim, so the levels it writes keep their heads either way.
15157        if !unpaired_closes.is_empty() {
15158            brace_closes.clear();
15159        } else {
15160            // Use AST ownership when the global walk has no unpaired close. A missing
15161            // direct close removes a misleading stack pairing; a real direct
15162            // close replaces any cascade from a damaged descendant. Apply this
15163            // only to the namespace chain containing a proven missing boundary;
15164            // unrelated or ambiguous namespaces retain the global fallback.
15165            let missing_namespaces = namespace_brace_proofs
15166                .iter()
15167                .filter_map(|(_, namespace, proof)| {
15168                    matches!(proof, NamespaceBodyBraceProof::Missing).then_some(namespace.id())
15169                })
15170                .collect::<HashSet<_>>();
15171            for (open, namespace, proof) in namespace_brace_proofs {
15172                if !std::iter::successors(Some(namespace), |node| node.parent())
15173                    .any(|ancestor| missing_namespaces.contains(&ancestor.id()))
15174                {
15175                    continue;
15176                }
15177                match proof {
15178                    NamespaceBodyBraceProof::Real(close) => {
15179                        brace_closes.insert(open, close);
15180                    }
15181                    NamespaceBodyBraceProof::Missing => {
15182                        brace_closes.remove(&open);
15183                    }
15184                }
15185            }
15186        }
15187        // Every surviving brace is paired now, so each level's head can grow to
15188        // the construct it writes. A level whose `{` never closes keeps its
15189        // head: the file ends inside the namespace and there is no real close
15190        // to reach for.
15191        let regions = regions
15192            .into_iter()
15193            .map(|region| {
15194                debug_assert_eq!(
15195                    region.components.len(),
15196                    region.heads.len(),
15197                    "every recovered namespace level carries the head that writes it: {:?}",
15198                    region.components
15199                );
15200                debug_assert_eq!(
15201                    region.components.len(),
15202                    region.name_ranges.len(),
15203                    "every recovered namespace level carries the name token that writes it: {:?}",
15204                    region.components
15205                );
15206                let component_closes: Vec<_> = region
15207                    .heads
15208                    .iter()
15209                    .map(|level| brace_closes.get(&level.open).copied())
15210                    .collect();
15211                let component_ranges = region
15212                    .heads
15213                    .iter()
15214                    .zip(&component_closes)
15215                    .map(|(level, close)| match close {
15216                        Some(close) => Range {
15217                            start_byte: level.head.start_byte,
15218                            end_byte: close.end_byte,
15219                            start_line: level.head.start_line,
15220                            end_line: close.end_line,
15221                        },
15222                        None => level.head,
15223                    })
15224                    .collect();
15225                RecoveredNamespaceRegion {
15226                    start: region.start,
15227                    end: region.end,
15228                    components: region.components,
15229                    component_ranges,
15230                    component_name_ranges: region.name_ranges,
15231                    component_closes,
15232                }
15233            })
15234            .collect();
15235        Self {
15236            regions,
15237            brace_closes,
15238        }
15239    }
15240
15241    /// The real AST brace matching an opening brace in a damaged subtree.
15242    /// Missing tokens and braces inside comments or literals do not participate.
15243    ///
15244    /// `None` when this open has no close in the file, and for every open in a
15245    /// file whose brace token stream lost an opening token: a caller cannot
15246    /// treat a pairing the walk could not prove as an enclosing extent (#3328).
15247    pub fn matching_close_brace(&self, open: usize) -> Option<Range> {
15248        self.brace_closes.get(&open).copied()
15249    }
15250
15251    pub fn is_empty(&self) -> bool {
15252        self.regions.is_empty()
15253    }
15254
15255    /// The bytes this index holds, for the analyzer cache's weight.
15256    pub fn approximate_size(&self) -> usize {
15257        self.regions.iter().fold(
15258            self.brace_closes.len() * std::mem::size_of::<(usize, Range)>(),
15259            |total, region| {
15260                total
15261                    .saturating_add(std::mem::size_of::<RecoveredNamespaceRegion>())
15262                    .saturating_add(region.components.iter().map(String::len).sum::<usize>())
15263                    .saturating_add(region.component_ranges.len() * std::mem::size_of::<Range>())
15264                    .saturating_add(
15265                        region.component_name_ranges.len() * std::mem::size_of::<Range>(),
15266                    )
15267                    .saturating_add(
15268                        region.component_closes.len() * std::mem::size_of::<Option<Range>>(),
15269                    )
15270            },
15271        )
15272    }
15273
15274    /// The innermost recovered region containing `byte`.
15275    pub fn region_at(&self, byte: usize) -> Option<&RecoveredNamespaceRegion> {
15276        self.regions
15277            .iter()
15278            .filter(|region| region.start <= byte && byte < region.end)
15279            .min_by_key(|region| region.end - region.start)
15280    }
15281
15282    /// The enclosing namespaces of `node`, outermost first, restoring the ones
15283    /// parse recovery dropped from its ancestor chain. The one answer both
15284    /// lookup directions and declaration collection use (issue #1537).
15285    pub fn enclosing_namespace_components(&self, node: Node<'_>, source: &str) -> Vec<String> {
15286        self.enclosing_namespace_levels(node, source)
15287            .into_iter()
15288            .map(|(component, ..)| component)
15289            .collect()
15290    }
15291
15292    /// [`Self::enclosing_namespace_components`] with the construct that writes
15293    /// each level and the level's own name token, for the declaration walk
15294    /// that has to record both ranges.
15295    ///
15296    /// A level the brace stack restored carries the ranges this index recorded
15297    /// for its head and name; a surviving `namespace_definition` ancestor
15298    /// carries its own node range and name token, which is what the ordinary
15299    /// namespace visit records.
15300    pub fn enclosing_namespace_levels(
15301        &self,
15302        node: Node<'_>,
15303        source: &str,
15304    ) -> Vec<(String, Range, Range)> {
15305        let mut parsed = Vec::new();
15306        let mut current = node.parent();
15307        while let Some(parent) = current {
15308            if parent.kind() == "namespace_definition"
15309                && let Some(name) = parent.child_by_field_name("name")
15310                && let Some(components) = cpp_name_component_ranges(name, source)
15311            {
15312                let range = node_range(parent);
15313                parsed.push((
15314                    parent.start_byte(),
15315                    components
15316                        .into_iter()
15317                        .map(|(component, name_range)| (component, range, name_range))
15318                        .collect(),
15319                    namespace_definition_has_real_braces(parent),
15320                ));
15321            }
15322            current = parent.parent();
15323        }
15324        parsed.reverse();
15325        self.splice_recovered_path(parsed, node.start_byte(), |region, active_components| {
15326            region
15327                .components
15328                .iter()
15329                .take(active_components)
15330                .cloned()
15331                .zip(
15332                    region
15333                        .component_ranges
15334                        .iter()
15335                        .take(active_components)
15336                        .copied(),
15337                )
15338                .zip(
15339                    region
15340                        .component_name_ranges
15341                        .iter()
15342                        .take(active_components)
15343                        .copied(),
15344                )
15345                .map(|((component, range), name_range)| (component, range, name_range))
15346                .collect()
15347        })
15348    }
15349
15350    /// [`Self::enclosing_namespace_components`] for a caller that has already
15351    /// climbed the ancestor chain: `parsed` lists the node's named
15352    /// `namespace_definition` ancestors outermost first, each with its start
15353    /// byte and whether its own opening and closing braces survived parsing.
15354    /// The answer follows the authority rule on [`OrphanedNamespaceScopeIndex`].
15355    pub fn restore_enclosing_namespaces(
15356        &self,
15357        parsed: Vec<(usize, Vec<String>, bool)>,
15358        node_start: usize,
15359    ) -> Vec<String> {
15360        self.splice_recovered_path(parsed, node_start, |region, active_components| {
15361            region
15362                .components
15363                .iter()
15364                .take(active_components)
15365                .cloned()
15366                .collect()
15367        })
15368    }
15369
15370    /// Splice a node's parsed namespace ancestors onto the path a recovered
15371    /// region restores, by the authority rule on [`OrphanedNamespaceScopeIndex`].
15372    /// `recovered` receives the region and the number of its outermost levels
15373    /// still open at the node, and reads those levels in whatever shape the
15374    /// caller needs.
15375    fn splice_recovered_path<T>(
15376        &self,
15377        parsed: Vec<(usize, Vec<T>, bool)>,
15378        node_start: usize,
15379        recovered: impl FnOnce(&RecoveredNamespaceRegion, usize) -> Vec<T>,
15380    ) -> Vec<T> {
15381        let Some(region) = self.region_at(node_start) else {
15382            return parsed
15383                .into_iter()
15384                .flat_map(|(_, levels, _)| levels)
15385                .collect();
15386        };
15387        debug_assert_eq!(
15388            region.components.len(),
15389            region.component_closes.len(),
15390            "every recovered namespace component carries its close proof: {region:?}"
15391        );
15392        debug_assert_eq!(
15393            region.components.len(),
15394            region.component_name_ranges.len(),
15395            "every recovered namespace component carries its name token: {region:?}"
15396        );
15397        if region.components.is_empty() {
15398            return parsed
15399                .into_iter()
15400                .filter(|(_, _, complete)| *complete)
15401                .flat_map(|(_, levels, _)| levels)
15402                .collect();
15403        }
15404        let active_components = region
15405            .component_closes
15406            .iter()
15407            .take_while(|close| {
15408                close
15409                    .as_ref()
15410                    .is_none_or(|close| node_start < close.end_byte)
15411            })
15412            .count();
15413        recovered(region, active_components)
15414            .into_iter()
15415            .chain(
15416                parsed
15417                    .into_iter()
15418                    .filter(|(start, _, _)| *start >= region.start)
15419                    .flat_map(|(_, levels, _)| levels),
15420            )
15421            .collect()
15422    }
15423}
15424
15425/// Whether a parsed namespace owns real AST tokens for both of its boundary
15426/// braces. Recovery may damage its body while leaving this boundary usable as
15427/// lexical ownership evidence.
15428pub(super) fn namespace_definition_has_real_braces(namespace: Node<'_>) -> bool {
15429    debug_assert_eq!(namespace.kind(), "namespace_definition");
15430    let Some(body) = namespace.child_by_field_name("body") else {
15431        return false;
15432    };
15433    let mut opening = false;
15434    let mut closing = false;
15435    let mut cursor = body.walk();
15436    for child in body.children(&mut cursor) {
15437        match child.kind() {
15438            "{" if !child.is_missing() => opening = true,
15439            "}" if !child.is_missing() => closing = true,
15440            _ => {}
15441        }
15442    }
15443    opening && closing
15444}
15445
15446/// The `namespace` keyword and name components of the namespace a stray `{`
15447/// opens, or `None` when the brace does not follow a namespace head.
15448///
15449/// When a namespace body holds a construct tree-sitter cannot parse, recovery
15450/// can collapse the whole `namespace Name { ... }` into one `ERROR` instead of
15451/// a `namespace_definition`: the head survives as the node's own `namespace`
15452/// keyword, name and `{` tokens, in that order, and everything the namespace
15453/// declares becomes a flat sibling of them (Catch2's `catch_decomposer.hpp`,
15454/// issue #3084). Read the head from those siblings so the brace stack names
15455/// the scope the brace opens. An anonymous namespace has no representable
15456/// name and keeps an opaque scope.
15457///
15458/// `preceding` is the open brace's preceding siblings, nearest last. The
15459/// caller already holds the container's child list; `Node::prev_sibling`
15460/// would re-descend from the root for each step (#3141).
15461fn recovered_namespace_open_components<'tree>(
15462    preceding: &[Node<'tree>],
15463    source: &str,
15464) -> Option<(Node<'tree>, Vec<(String, Range)>)> {
15465    let mut head = Vec::new();
15466    for &sibling in preceding.iter().rev() {
15467        if sibling.kind() != "comment" {
15468            head.push(sibling);
15469            if head.len() == 2 {
15470                break;
15471            }
15472        }
15473    }
15474    let [name, keyword] = head[..] else {
15475        return None;
15476    };
15477    if keyword.kind() != "namespace" {
15478        return None;
15479    }
15480    let components = cpp_name_component_ranges(name, source)?;
15481    (!components.is_empty()).then_some((keyword, components))
15482}
15483
15484/// The written head and name-token ranges of the namespace opened by `body`.
15485/// Ordinary namespace bodies and the two damaged literal heads share the
15486/// existing brace-stack lifetime; a recovered compound body's end is NOT a
15487/// namespace close proof (#5868).
15488fn namespace_body_name_components<'tree>(
15489    parent: Node<'tree>,
15490    body: Node<'tree>,
15491    source: &str,
15492) -> Option<(Node<'tree>, Vec<(String, Range)>)> {
15493    if parent.child_by_field_name("body") != Some(body) {
15494        return None;
15495    }
15496    let (written_at, name) = match (parent.kind(), body.kind()) {
15497        ("namespace_definition", "declaration_list") => {
15498            (parent, parent.child_by_field_name("name")?)
15499        }
15500        ("function_definition", "compound_statement") => {
15501            // The two wrapper shapes are mutually exclusive on child kinds: a
15502            // recovered head has an ERROR where the clean macro-modifier head
15503            // has the name. Recovery inside the body still flags has_error on
15504            // the clean shape, so the shape checks, not has_error, must pick
15505            // the reader.
15506            recovered_literal_namespace_head(parent, body, source)
15507                .or_else(|| macro_modifier_namespace_head(parent, body, source))?
15508        }
15509        _ => return None,
15510    };
15511    let components = cpp_name_component_ranges(name, source)?;
15512    (!components.is_empty()).then_some((written_at, components))
15513}
15514
15515/// The written head and name of the literal namespace a damaged wrapper opens,
15516/// when recovery read the `namespace` keyword as a function declarator.
15517fn recovered_literal_namespace_head<'tree>(
15518    parent: Node<'tree>,
15519    body: Node<'tree>,
15520    source: &str,
15521) -> Option<(Node<'tree>, Node<'tree>)> {
15522    // An opaque prefix can make the parser read the literal namespace
15523    // keyword as a function declarator. Account for every direct token:
15524    // no parameters, attributes, extra names or punctuation may compete.
15525    let mut cursor = parent.walk();
15526    let children = parent
15527        .children(&mut cursor)
15528        .filter(|child| child.kind() != "comment")
15529        .collect::<Vec<_>>();
15530    let [prefix, keyword, error, actual_body] = children.as_slice() else {
15531        return None;
15532    };
15533    if *actual_body != body
15534        || parent.child_by_field_name("type") != Some(*prefix)
15535        || parent.child_by_field_name("declarator") != Some(*keyword)
15536        || prefix.kind() != "type_identifier"
15537        || prefix.is_missing()
15538        || prefix.has_error()
15539        || prefix.child_count() != 0
15540        || keyword.kind() != "identifier"
15541        || keyword.is_missing()
15542        || keyword.has_error()
15543        || keyword.child_count() != 0
15544        || node_text(*keyword, source) != "namespace"
15545        || error.kind() != "ERROR"
15546        || error.is_missing()
15547        || body.is_missing()
15548    {
15549        return None;
15550    }
15551    let mut cursor = error.walk();
15552    let names = error
15553        .children(&mut cursor)
15554        .filter(|child| child.kind() != "comment")
15555        .collect::<Vec<_>>();
15556    let [name] = names.as_slice() else {
15557        return None;
15558    };
15559    if !matches!(name.kind(), "identifier" | "namespace_identifier")
15560        || name.is_missing()
15561        || name.has_error()
15562        || name.child_count() != 0
15563    {
15564        return None;
15565    }
15566    let open = body.child(0)?;
15567    if open.kind() != "{"
15568        || open.is_missing()
15569        || open.has_error()
15570        || open.start_byte() != body.start_byte()
15571    {
15572        return None;
15573    }
15574    // Keep the literal keyword's provenance, never the unrelated prefix
15575    // or the prematurely closed function envelope (#3309, #4292).
15576    Some((*keyword, *name))
15577}
15578
15579/// The written head and name of the literal namespace a macro-modifier
15580/// wrapper opens, when the macro-declaration grammar read the same construct
15581/// without recovery.
15582fn macro_modifier_namespace_head<'tree>(
15583    parent: Node<'tree>,
15584    body: Node<'tree>,
15585    source: &str,
15586) -> Option<(Node<'tree>, Node<'tree>)> {
15587    // The macro-declaration grammar reads the same construct without
15588    // recovery: the opaque prefix is a `macro_modifier`, the literal
15589    // `namespace` keyword occupies the type field, and the namespace
15590    // name is the declarator. The keyword cannot be a type, so its
15591    // presence in the type field is the recovery evidence the older
15592    // grammar's `ERROR` carried; account for every direct token the
15593    // same way.
15594    let mut cursor = parent.walk();
15595    let children = parent
15596        .children(&mut cursor)
15597        .filter(|child| child.kind() != "comment")
15598        .collect::<Vec<_>>();
15599    let [prefix, keyword, name, actual_body] = children.as_slice() else {
15600        return None;
15601    };
15602    if *actual_body != body
15603        || parent.child_by_field_name("type") != Some(*keyword)
15604        || parent.child_by_field_name("declarator") != Some(*name)
15605        || prefix.kind() != "macro_modifier"
15606        || prefix.is_missing()
15607        || prefix.has_error()
15608        || macro_modifier_identifier(*prefix).is_none()
15609        || keyword.kind() != "type_identifier"
15610        || keyword.is_missing()
15611        || keyword.has_error()
15612        || keyword.child_count() != 0
15613        || node_text(*keyword, source) != "namespace"
15614        || !matches!(name.kind(), "identifier" | "namespace_identifier")
15615        || name.is_missing()
15616        || name.has_error()
15617        || name.child_count() != 0
15618        || body.is_missing()
15619    {
15620        return None;
15621    }
15622    let open = body.child(0)?;
15623    if open.kind() != "{"
15624        || open.is_missing()
15625        || open.has_error()
15626        || open.start_byte() != body.start_byte()
15627    {
15628        return None;
15629    }
15630    // Keep the literal keyword's provenance, never the unrelated prefix
15631    // or the prematurely closed function envelope (#3309, #4292).
15632    Some((*keyword, *name))
15633}
15634
15635#[derive(Clone, Copy)]
15636enum NamespaceBodyBraceProof {
15637    Real(Range),
15638    Missing,
15639}
15640
15641/// Prove a namespace body's boundary from its direct AST children. Require the
15642/// body's boundary as well as direct ownership, so a stray brace inside a
15643/// recovered body cannot become namespace ownership evidence. Unknown is
15644/// distinct from a proven missing token: unknown leaves the global pairing in
15645/// place, while a direct MISSING `}` removes a misleading pairing.
15646fn namespace_body_brace_proof(body: Node<'_>) -> Option<NamespaceBodyBraceProof> {
15647    if body.kind() != "declaration_list" {
15648        return None;
15649    }
15650    let mut cursor = body.walk();
15651    let children = body.children(&mut cursor).collect::<Vec<_>>();
15652    let close_index = children
15653        .iter()
15654        .rposition(|child| child.kind() == "}" && child.end_byte() == body.end_byte())?;
15655    let close = children[close_index];
15656    if close.is_missing() {
15657        return Some(NamespaceBodyBraceProof::Missing);
15658    }
15659    let namespace_terminator_sibling = body
15660        .parent()
15661        .filter(|namespace| namespace.kind() == "namespace_definition")
15662        .and_then(|namespace| namespace.next_sibling())
15663        .is_some_and(|sibling| {
15664            sibling.kind() == ";"
15665                || (sibling.kind() == "expression_statement"
15666                    && sibling.child_count() == 1
15667                    && sibling.child(0).is_some_and(|child| child.kind() == ";"))
15668        });
15669    // A recovered class close can become the namespace body boundary while
15670    // its class terminator remains as a sibling semicolon. A malformed class
15671    // can also leak its final expression or label directly into the namespace
15672    // body. Neither shape proves that this `}` belongs to the namespace.
15673    if namespace_terminator_sibling
15674        || children
15675            .get(close_index + 1)
15676            .is_some_and(|sibling| sibling.kind() == ";")
15677        || children[..close_index]
15678            .iter()
15679            .rev()
15680            .find(|child| !child.is_extra() && child.kind() != "{")
15681            .is_some_and(|previous| {
15682                matches!(
15683                    previous.kind(),
15684                    "expression_statement" | "labeled_statement"
15685                )
15686            })
15687    {
15688        return None;
15689    }
15690    Some(NamespaceBodyBraceProof::Real(node_range(close)))
15691}
15692
15693#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15694pub enum RecoveredDeclaratorTypeContext {
15695    Declaration,
15696    FunctionDefinition,
15697    Parameter,
15698}
15699
15700/// Recognize a real type displaced into a qualified declarator by parser
15701/// recovery.
15702///
15703/// Tree-sitter parses `API Result *make(Arg);` as if `API` were the declared
15704/// type and `Result` were the scope of a qualified declarator with a missing
15705/// `::`. A template return such as `API Result<T> make()` uses a
15706/// `template_type` for the same recovered scope. The same recovery occurs for
15707/// macro-prefixed definitions, extern variables, and macro-decorated
15708/// parameters (`f(MACRO T* p)`, where the parameter's own `type` field takes
15709/// the macro). Keep this intentionally structural: the recovered scope must
15710/// have the grammar's missing separator, the qualified node must occupy the
15711/// declaration's declarator chain, a separate nonempty type must occupy the
15712/// normal type field, and the recovered name must unwrap to a real declarator
15713/// name.
15714pub fn recovered_macro_decorated_declarator_type(
15715    node: Node<'_>,
15716) -> Option<RecoveredDeclaratorTypeContext> {
15717    recovered_macro_decorated_type_node(node).map(|(_, context)| context)
15718}
15719
15720/// Return the declaration/function `type` displaced by a macro-shaped
15721/// qualified declarator, together with the enclosing declaration context.
15722/// Callers use the macro scope only as structural admission evidence; the
15723/// returned node is the real type reference to resolve and record.
15724pub fn recovered_macro_decorated_type_node(
15725    node: Node<'_>,
15726) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
15727    if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
15728        return None;
15729    }
15730    let qualified = node.parent()?;
15731    if qualified.kind() != "qualified_identifier"
15732        || qualified.child_by_field_name("scope") != Some(node)
15733        || !(0..qualified.child_count())
15734            .filter_map(|index| qualified.child(index))
15735            .any(|child| child.kind() == "::" && child.is_missing())
15736    {
15737        return None;
15738    }
15739    if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
15740        return None;
15741    }
15742
15743    let (declaration, context) = recovered_declarator_container(qualified)?;
15744    let type_node = declaration
15745        .child_by_field_name("type")
15746        .filter(|type_node| {
15747            *type_node != qualified
15748                && !type_node.is_missing()
15749                && type_node.start_byte() != type_node.end_byte()
15750        })?;
15751    Some((type_node, context))
15752}
15753
15754/// The macro-declaration grammar's clean reading of the head
15755/// [`recovered_macro_decorated_type_node`] recovers: `API static inline T
15756/// make()` parses as a leading `macro_modifier` and `T` as the declaration's
15757/// `type`. Accepts the same type kinds and declaration containers as the
15758/// recovered form, so a rule keyed on that macro-decorated head keeps its
15759/// scope across both grammars.
15760pub fn macro_modifier_decorated_type_node(node: Node<'_>) -> bool {
15761    matches!(node.kind(), "type_identifier" | "template_type")
15762        && node.parent().is_some_and(|declaration| {
15763            matches!(
15764                declaration.kind(),
15765                "declaration"
15766                    | "function_definition"
15767                    | "parameter_declaration"
15768                    | "optional_parameter_declaration"
15769            ) && declaration.child_by_field_name("type") == Some(node)
15770                && declaration.child_by_field_name("declarator").is_some()
15771                && crate::declarations::cpp_declaration_type_follows_macro_modifier(
15772                    declaration,
15773                    node,
15774                )
15775        })
15776}
15777
15778fn recovered_declarator_container(
15779    mut declarator: Node<'_>,
15780) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
15781    loop {
15782        let parent = declarator.parent()?;
15783        if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
15784            return Some((
15785                parent
15786                    .parent()
15787                    .filter(|declaration| declaration.kind() == "declaration")?,
15788                RecoveredDeclaratorTypeContext::Declaration,
15789            ));
15790        }
15791        if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
15792            return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
15793        }
15794        if parent.kind() == "function_definition"
15795            && has_field_child(parent, "declarator", declarator)
15796        {
15797            return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
15798        }
15799        // `f(MACRO T* p)` recovers exactly like `MACRO T *make(...)` does, one
15800        // level down: the parameter's `type` field takes the macro token and
15801        // the real type `T` becomes the recovered scope of the declarator.
15802        // Declining here left every xxhash `XXH_NOESCAPE` parameter with no
15803        // candidate at all (#1830).
15804        if matches!(
15805            parent.kind(),
15806            "parameter_declaration" | "optional_parameter_declaration"
15807        ) && has_field_child(parent, "declarator", declarator)
15808        {
15809            return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
15810        }
15811        if !matches!(
15812            parent.kind(),
15813            "array_declarator"
15814                | "function_declarator"
15815                | "parenthesized_declarator"
15816                | "pointer_declarator"
15817                | "pointer_type_declarator"
15818                | "reference_declarator"
15819        ) || !has_field_child(parent, "declarator", declarator)
15820        {
15821            return None;
15822        }
15823        declarator = parent;
15824    }
15825}
15826
15827fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
15828    brokk_bifrost_core::analyzer::structural::adapter_helpers::is_field_of(parent, target, field)
15829}
15830
15831fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
15832    loop {
15833        if node.is_missing() || node.start_byte() == node.end_byte() {
15834            return false;
15835        }
15836        match node.kind() {
15837            "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
15838                return true;
15839            }
15840            "array_declarator"
15841            | "function_declarator"
15842            | "parenthesized_declarator"
15843            | "pointer_declarator"
15844            | "pointer_type_declarator"
15845            | "reference_declarator" => {
15846                let Some(declarator) = node.child_by_field_name("declarator") else {
15847                    return false;
15848                };
15849                node = declarator;
15850            }
15851            _ => return false,
15852        }
15853    }
15854}
15855
15856/// Aggregate-owner proof for a structurally recognized designated initializer.
15857pub enum DesignatedInitializerOwner {
15858    Resolved(CodeUnit),
15859    Unresolved,
15860}
15861
15862enum InitializerOwnerStep {
15863    Field(String),
15864    AggregateWrapper,
15865}
15866
15867/// Recognize a designated-initializer field and, when possible, resolve its
15868/// aggregate owner.
15869///
15870/// Covers both the grammar's ordinary `field_designator` shape and the exact
15871/// recovery used for `.field = value` after a preprocessor-split array
15872/// initializer. Ordered multi-component designators follow each preceding
15873/// field's declared aggregate type. `None` means the node is not a designator
15874/// at all; an unresolved designator remains classified so callers cannot fall
15875/// through to unrelated global/member heuristics.
15876pub fn designated_initializer_owner(
15877    analyzer: &CppGraphSource<'_>,
15878    visibility: &VisibilityIndex<'_>,
15879    file: &ProjectFile,
15880    source: &str,
15881    node: Node<'_>,
15882) -> Option<DesignatedInitializerOwner> {
15883    if let Some(designator) = node
15884        .parent()
15885        .filter(|parent| parent.kind() == "field_designator")
15886    {
15887        let pair = designator.parent()?;
15888        if pair.kind() != "initializer_pair" {
15889            return None;
15890        }
15891        let mut cursor = pair.walk();
15892        let designators = pair
15893            .children_by_field_name("designator", &mut cursor)
15894            .collect::<Vec<_>>();
15895        let position = designators
15896            .iter()
15897            .position(|candidate| same_node(*candidate, designator))?;
15898        let initializer = pair.parent()?;
15899        if initializer.kind() != "initializer_list" {
15900            return None;
15901        }
15902        let mut owner = initializer_list_owner(analyzer, visibility, file, source, initializer);
15903        for prior in &designators[..position] {
15904            let field = prior
15905                .child_by_field_name("field")
15906                .or_else(|| first_named_child_of_kind(*prior, "field_identifier"))?;
15907            owner = owner.and_then(|owner| {
15908                initializer_field_owner(analyzer, visibility, file, owner, node_text(field, source))
15909            });
15910        }
15911        return Some(classified_designated_owner(owner));
15912    }
15913
15914    let init_declarator = node.parent()?;
15915    if init_declarator.child_by_field_name("declarator") != Some(node)
15916        || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
15917    {
15918        return None;
15919    }
15920    Some(classified_designated_owner(declaration_owner(
15921        analyzer,
15922        visibility,
15923        file,
15924        source,
15925        init_declarator.parent()?,
15926    )))
15927}
15928
15929fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
15930    owner.map_or(
15931        DesignatedInitializerOwner::Unresolved,
15932        DesignatedInitializerOwner::Resolved,
15933    )
15934}
15935
15936fn initializer_list_owner(
15937    analyzer: &CppGraphSource<'_>,
15938    visibility: &VisibilityIndex<'_>,
15939    file: &ProjectFile,
15940    source: &str,
15941    initializer: Node<'_>,
15942) -> Option<CodeUnit> {
15943    let mut current = initializer;
15944    let mut steps = Vec::new();
15945    loop {
15946        let parent = current.parent()?;
15947        match parent.kind() {
15948            "initializer_pair" if parent.child_by_field_name("value") == Some(current) => {
15949                let designator = parent.child_by_field_name("designator")?;
15950                let step = designator
15951                    .child_by_field_name("field")
15952                    .or_else(|| first_named_child_of_kind(designator, "field_identifier"))
15953                    .map(|field| InitializerOwnerStep::Field(node_text(field, source).to_string()))
15954                    .unwrap_or(InitializerOwnerStep::AggregateWrapper);
15955                steps.push(step);
15956                current = parent.parent()?;
15957            }
15958            "initializer_list" => {
15959                current = parent;
15960            }
15961            "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
15962                let declaration = parent.parent()?;
15963                let owner = declaration_owner(analyzer, visibility, file, source, declaration)?;
15964                return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
15965            }
15966            "compound_literal_expression"
15967                if parent.child_by_field_name("value") == Some(current) =>
15968            {
15969                let type_node = parent.child_by_field_name("type")?;
15970                let owner =
15971                    resolve_designated_owner_type(analyzer, visibility, file, source, type_node)?;
15972                return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
15973            }
15974            "ERROR" => current = parent,
15975            _ => return None,
15976        }
15977    }
15978}
15979
15980fn apply_initializer_owner_steps(
15981    analyzer: &CppGraphSource<'_>,
15982    visibility: &VisibilityIndex<'_>,
15983    file: &ProjectFile,
15984    mut owner: CodeUnit,
15985    steps: Vec<InitializerOwnerStep>,
15986) -> Option<CodeUnit> {
15987    for step in steps.into_iter().rev() {
15988        if let InitializerOwnerStep::Field(field_name) = step {
15989            owner = initializer_field_owner(analyzer, visibility, file, owner, &field_name)?;
15990        }
15991    }
15992    Some(owner)
15993}
15994
15995fn initializer_field_owner(
15996    analyzer: &CppGraphSource<'_>,
15997    visibility: &VisibilityIndex<'_>,
15998    file: &ProjectFile,
15999    owner: CodeUnit,
16000    field_name: &str,
16001) -> Option<CodeUnit> {
16002    let fields = visibility
16003        .visible_members_for_owner_name(file, &owner, field_name)
16004        .into_iter()
16005        .filter(|field| field.is_field())
16006        .collect::<Vec<_>>();
16007    let field = match fields.as_slice() {
16008        [field] => *field,
16009        _ => return None,
16010    };
16011    field_declared_binding(analyzer, visibility, file, field)?.unit
16012}
16013
16014fn declaration_owner(
16015    analyzer: &CppGraphSource<'_>,
16016    visibility: &VisibilityIndex<'_>,
16017    file: &ProjectFile,
16018    source: &str,
16019    declaration: Node<'_>,
16020) -> Option<CodeUnit> {
16021    if !matches!(declaration.kind(), "declaration" | "field_declaration") {
16022        return None;
16023    }
16024    let type_node = declaration
16025        .child_by_field_name("type")
16026        .or_else(|| first_type_child(declaration))?;
16027    resolve_designated_owner_type(analyzer, visibility, file, source, type_node)
16028}
16029
16030fn resolve_designated_owner_type(
16031    analyzer: &CppGraphSource<'_>,
16032    visibility: &VisibilityIndex<'_>,
16033    file: &ProjectFile,
16034    source: &str,
16035    type_node: Node<'_>,
16036) -> Option<CodeUnit> {
16037    if let Some(owner) = anonymous_aggregate_owner(analyzer, file, type_node) {
16038        return Some(owner);
16039    }
16040    let type_name = normalize_type_text(node_text(type_node, source));
16041    visibility
16042        .resolve_type(file, &type_name)
16043        .filter(CodeUnit::is_class)
16044}
16045
16046pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
16047    let mut cursor = node.walk();
16048    node.named_children(&mut cursor).find(|child| {
16049        matches!(
16050            child.kind(),
16051            "type_identifier"
16052                | "primitive_type"
16053                | "qualified_identifier"
16054                | "scoped_type_identifier"
16055                | "struct_specifier"
16056                | "union_specifier"
16057                | "enum_specifier"
16058        )
16059    })
16060}
16061
16062pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
16063    visibility: &VisibilityIndex<'_>,
16064    file: &ProjectFile,
16065    source: &str,
16066    declarator: Node<'_>,
16067    type_text: Option<&str>,
16068    bindings: &LocalInferenceEngine<T>,
16069) -> bool {
16070    if !has_ancestor_kind(declarator, "compound_statement") {
16071        return false;
16072    }
16073    if declarator
16074        .child_by_field_name("declarator")
16075        .is_none_or(|declarator| declarator.kind() != "identifier")
16076    {
16077        return false;
16078    }
16079    if !type_text
16080        .and_then(|text| visibility.resolve_type(file, text))
16081        .is_some_and(|unit| unit.is_class())
16082    {
16083        return false;
16084    }
16085    declarator
16086        .child_by_field_name("parameters")
16087        .is_some_and(|parameters| {
16088            constructor_parameters_look_like_expressions(parameters, source, bindings)
16089        })
16090}
16091
16092fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
16093    parameters: Node<'_>,
16094    source: &str,
16095    bindings: &LocalInferenceEngine<T>,
16096) -> bool {
16097    let mut cursor = parameters.walk();
16098    parameters.named_children(&mut cursor).any(|parameter| {
16099        !matches!(
16100            parameter.kind(),
16101            "parameter_declaration" | "optional_parameter_declaration"
16102        ) || parameter_declaration_is_local_expression(parameter, source, bindings)
16103    })
16104}
16105
16106fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
16107    parameter: Node<'_>,
16108    source: &str,
16109    bindings: &LocalInferenceEngine<T>,
16110) -> bool {
16111    let text = node_text(parameter, source).trim();
16112    if text
16113        .chars()
16114        .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
16115        && bindings.is_shadowed(text)
16116    {
16117        return true;
16118    }
16119
16120    let Some(base) = parameter
16121        .child_by_field_name("type")
16122        .filter(|base| base.kind() == "type_identifier")
16123    else {
16124        return false;
16125    };
16126    let Some(subscript) = parameter
16127        .child_by_field_name("declarator")
16128        .filter(|declarator| declarator.kind() == "abstract_array_declarator")
16129    else {
16130        return false;
16131    };
16132    subscript.child_by_field_name("size").is_some()
16133        && bindings.is_shadowed(node_text(base, source).trim())
16134}
16135
16136pub fn is_declaration_name(node: Node<'_>) -> bool {
16137    let Some(parent) = node.parent() else {
16138        return false;
16139    };
16140    if parent
16141        .child_by_field_name("name")
16142        .is_some_and(|name| same_node(name, node))
16143    {
16144        if matches!(
16145            parent.kind(),
16146            "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
16147        ) {
16148            return cpp_tag_specifier_declares_name(parent);
16149        }
16150        if matches!(
16151            parent.kind(),
16152            "namespace_definition"
16153                | "namespace_alias_definition"
16154                | "alias_declaration"
16155                | "enumerator"
16156        ) {
16157            return true;
16158        }
16159    }
16160
16161    let mut current = Some(parent);
16162    while let Some(ancestor) = current {
16163        let type_definition = ancestor.kind() == "type_definition";
16164        let mut child_cursor = ancestor.walk();
16165        if ancestor.named_children(&mut child_cursor).any(|child| {
16166            declaration_declarator(ancestor, child).is_some_and(|declarator| {
16167                declarator_name_path_contains(declarator, node, type_definition)
16168            })
16169        }) {
16170            return true;
16171        }
16172        if matches!(
16173            ancestor.kind(),
16174            "declaration"
16175                | "field_declaration"
16176                | "parameter_declaration"
16177                | "optional_parameter_declaration"
16178                | "function_definition"
16179                | "type_definition"
16180                | "alias_declaration"
16181                | "template_instantiation"
16182                | "class_specifier"
16183                | "struct_specifier"
16184                | "union_specifier"
16185                | "enum_specifier"
16186        ) {
16187            return false;
16188        }
16189        current = ancestor.parent();
16190    }
16191    false
16192}
16193
16194/// Whether tree-sitter recovered a qualified friend-class type as an ordinary
16195/// declaration's declarator inside a malformed class body.
16196///
16197/// An export macro between `class` and the class name can make the containing
16198/// body parse as a function body. A source declaration such as
16199/// `friend class internal::Friend;` then retains this exact structure:
16200/// `declaration(type: friend, ERROR(class), declarator: internal::Friend)`.
16201/// The declarator is a type reference despite its field role.
16202pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
16203    if !matches!(
16204        node.kind(),
16205        "qualified_identifier" | "scoped_type_identifier"
16206    ) {
16207        return false;
16208    }
16209    let Some(declaration) = node
16210        .parent()
16211        .filter(|parent| parent.kind() == "declaration")
16212    else {
16213        return false;
16214    };
16215    if declaration.child_by_field_name("declarator") != Some(node)
16216        || !declaration
16217            .child_by_field_name("type")
16218            .is_some_and(|friend| {
16219                friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
16220            })
16221    {
16222        return false;
16223    }
16224    let mut cursor = declaration.walk();
16225    let mut errors = declaration
16226        .named_children(&mut cursor)
16227        .filter(|child| child.kind() == "ERROR");
16228    let Some(error) = errors.next() else {
16229        return false;
16230    };
16231    errors.next().is_none()
16232        && error.named_child_count() == 1
16233        && error.named_child(0).is_some_and(|class| {
16234            class.kind() == "identifier" && node_text(class, source) == "class"
16235        })
16236}
16237
16238pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
16239    if !matches!(node.kind(), "identifier" | "field_identifier") {
16240        return false;
16241    }
16242    if let Some(parent) = node.parent() {
16243        if parent.kind() == "call_expression"
16244            && parent.child_by_field_name("function") == Some(node)
16245        {
16246            return false;
16247        }
16248        if matches!(parent.kind(), "labeled_statement" | "goto_statement")
16249            && parent.child_by_field_name("label") == Some(node)
16250        {
16251            return false;
16252        }
16253    }
16254    if is_declaration_name(node) {
16255        return false;
16256    }
16257    let mut current = node.parent();
16258    while let Some(ancestor) = current {
16259        match ancestor.kind() {
16260            "preproc_ifdef" | "preproc_ifndef" => {
16261                if ancestor
16262                    .child_by_field_name("name")
16263                    .is_some_and(|name| node_range_contains(name, node))
16264                {
16265                    return false;
16266                }
16267            }
16268            "preproc_if" | "preproc_elif" => {
16269                if ancestor
16270                    .child_by_field_name("condition")
16271                    .is_some_and(|condition| node_range_contains(condition, node))
16272                {
16273                    return false;
16274                }
16275            }
16276            "preproc_else" => {}
16277            kind if kind.starts_with("preproc_") => return false,
16278            _ => {}
16279        }
16280        if matches!(
16281            ancestor.kind(),
16282            "translation_unit" | "function_definition" | "compound_statement"
16283        ) {
16284            break;
16285        }
16286        current = ancestor.parent();
16287    }
16288    true
16289}
16290
16291fn recovered_c_reference_node(
16292    visibility: &VisibilityIndex<'_>,
16293    file: &ProjectFile,
16294    node: Node<'_>,
16295    source: &str,
16296) -> bool {
16297    if node.start_byte() >= node.end_byte()
16298        || node.is_error()
16299        || node.is_missing()
16300        || !matches!(
16301            node.kind(),
16302            "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
16303        )
16304        || recovered_c_macro_binding_role(node)
16305        || recovered_c_label_role(node)
16306    {
16307        return false;
16308    }
16309    // A declaration name can be an identifier child of a recovered ERROR
16310    // (for example `EFI_STATUS Encode()` in C files parsed as C++). Do not
16311    // let a same-named macro turn that binder into a reference. An
16312    // assignment whose C callee follows the recovered `explicit` token is
16313    // the one expression-shaped exception: the generic declarator walk sees
16314    // its function_declarator as a declaration path, but the ERROR sibling
16315    // proves it is a call.
16316    let name = node_text(node, source);
16317    let recovered_function_call = recovered_c_function_call(visibility, file, node, name);
16318    let recovered_macro_call = recovered_c_function_declarator_invocation(node)
16319        && visibility.macro_name_may_be_bound_at(file, name, node.start_byte());
16320    let recovered_parenthesized_reference = recovered_c_parenthesized_declarator_reference(node);
16321    if is_declaration_name(node)
16322        && !recovered_c_explicit_assignment_callee(visibility, file, node, name)
16323        && !recovered_function_call
16324        && !recovered_macro_call
16325        && !recovered_parenthesized_reference
16326    {
16327        return false;
16328    }
16329
16330    if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
16331        return true;
16332    }
16333    if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
16334        return true;
16335    }
16336    if recovered_parenthesized_reference {
16337        return true;
16338    }
16339    if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
16340        if recovered_function_call {
16341            return true;
16342        }
16343        return visibility
16344            .visible_identifier_candidates(file, name)
16345            .any(|candidate| candidate.is_class() || candidate.is_module());
16346    }
16347    let visible = visibility
16348        .visible_identifier_candidates(file, name)
16349        .next()
16350        .is_some();
16351    visible
16352        && (recovered_c_reference_anchor(node)
16353            || recovered_c_error_expression_leaf(node)
16354            || recovered_function_call)
16355}
16356
16357fn push_recovered_c_range(
16358    ranges: &mut Vec<Range>,
16359    seen: &mut HashSet<(usize, usize)>,
16360    start_byte: usize,
16361    end_byte: usize,
16362    node: Node<'_>,
16363    limit: usize,
16364) -> bool {
16365    if start_byte >= end_byte || !seen.insert((start_byte, end_byte)) {
16366        return true;
16367    }
16368    if ranges.len() >= limit {
16369        return false;
16370    }
16371    ranges.push(Range {
16372        start_byte,
16373        end_byte,
16374        start_line: node.start_position().row,
16375        end_line: node.end_position().row,
16376    });
16377    true
16378}
16379
16380/// A reference leaf can sit directly beneath an ERROR while its ERROR parent
16381/// is still attached to a real expression (most often a recovered macro call
16382/// argument). The expression parent is the structured proof; an unindexed
16383/// identifier beneath a bare recovery envelope has no such proof.
16384fn recovered_c_error_expression_leaf(node: Node<'_>) -> bool {
16385    let mut current = node.parent();
16386    while let Some(parent) = current {
16387        if parent.is_error() {
16388            let Some(anchor) = parent.parent() else {
16389                return false;
16390            };
16391            return anchor.kind().ends_with("_expression")
16392                || matches!(
16393                    anchor.kind(),
16394                    "argument_list"
16395                        | "return_statement"
16396                        | "expression_statement"
16397                        | "case_statement"
16398                        | "initializer_list"
16399                        | "field_designator"
16400                        | "enumerator"
16401                );
16402        }
16403        if matches!(
16404            parent.kind(),
16405            "translation_unit" | "function_definition" | "compound_statement"
16406        ) {
16407            return false;
16408        }
16409        current = parent.parent();
16410    }
16411    false
16412}
16413
16414/// C recovery may represent a call as `identifier > function_declarator >
16415/// function_declarator > ERROR > compound_statement`. This shape is only a
16416/// call when the malformed declarator is attached to a real function body and
16417/// the name is an indexed visible callable. A declaration's
16418/// `ERROR > function_declarator > declaration` shape deliberately fails this
16419/// test.
16420fn recovered_c_function_call(
16421    visibility: &VisibilityIndex<'_>,
16422    file: &ProjectFile,
16423    node: Node<'_>,
16424    name: &str,
16425) -> bool {
16426    if !matches!(
16427        node.kind(),
16428        "identifier" | "field_identifier" | "type_identifier"
16429    ) {
16430        return false;
16431    }
16432    // C++ keywords used as C arguments can leave only the call prefix under
16433    // ERROR, with the remaining arguments in a following expression statement.
16434    // Require a statement boundary in a real block, an opening parenthesis,
16435    // and a recovered argument. A bare ERROR identifier supplies no call role.
16436    let error_call_prefix = node.parent().is_some_and(|error| {
16437        error.is_error()
16438            && error
16439                .parent()
16440                .is_some_and(|parent| parent.kind() == "compound_statement")
16441    }) && node
16442        .prev_sibling()
16443        .is_none_or(|previous| previous.kind() == ";")
16444        && node.next_sibling().is_some_and(|open| {
16445            open.kind() == "("
16446                && open.next_named_sibling().is_some_and(|argument| {
16447                    argument.kind() == "parameter_declaration" && !argument.has_error()
16448                })
16449        });
16450    (error_call_prefix || recovered_c_function_declarator_invocation(node))
16451        && visibility
16452            .visible_identifier_candidates(file, name)
16453            .any(CodeUnit::is_function)
16454}
16455
16456/// The bogus `function_definition` the macro-declaration grammar builds for a
16457/// C call whose argument list a Clang block literal breaks: `f(first, ^(T b)
16458/// { ... })` reads as a definition whose declarator ends at the block's `(`,
16459/// with the block body as the function body. The older grammar kept the
16460/// `call_expression` and left the damage inside its argument list. The
16461/// declarator must carry the damage, and the envelope must sit in statement
16462/// context: a clean type-less definition is a real one (a constructor).
16463pub fn recovered_c_block_call_envelope(declarator: Node<'_>) -> Option<Node<'_>> {
16464    if declarator.kind() != "function_declarator" || !declarator.has_error() {
16465        return None;
16466    }
16467    let definition = declarator.parent().filter(|parent| {
16468        parent.kind() == "function_definition"
16469            && parent.child_by_field_name("declarator") == Some(declarator)
16470    })?;
16471    if definition.child_by_field_name("type").is_some()
16472        || definition.named_child(0) != Some(declarator)
16473        || definition.child_by_field_name("body").is_none()
16474        || definition.parent()?.kind() != "compound_statement"
16475    {
16476        return None;
16477    }
16478    Some(definition)
16479}
16480
16481/// Return whether a C identifier belongs to a call-shaped declarator that the
16482/// C++ grammar put under an `ERROR` node.
16483///
16484/// The malformed call can be direct (`f(arg)`) or nested in a parameter
16485/// declaration when one of its arguments looks like a type (`f(TYPE, value)`).
16486/// In both cases the CST retains the function-declarator and its enclosing
16487/// recovery envelope. We walk only those declarator/parameter nodes and stop
16488/// at a real expression-bearing boundary; declarations therefore cannot pass
16489/// this predicate merely because they have a parameter list.
16490fn recovered_c_function_declarator_invocation(node: Node<'_>) -> bool {
16491    // The macro-declaration grammar keeps a function-like macro invocation at
16492    // declaration scope as a `macro_type_specifier` (a function-like macro
16493    // wrapping a type), admitted only where the parse would otherwise contain
16494    // an error. Its name is the invocation.
16495    let mut current = if node.parent().is_some_and(|parent| {
16496        parent.kind() == "macro_type_specifier" && parent.child_by_field_name("name") == Some(node)
16497    }) {
16498        node.parent().expect("checked macro_type_specifier parent")
16499    } else {
16500        let mut function_declarator = if node.parent().is_some_and(|parent| {
16501            parent.kind() == "function_declarator"
16502                && parent.child_by_field_name("declarator") == Some(node)
16503        }) {
16504            node.parent().expect("checked function declarator parent")
16505        } else {
16506            let Some(parameter) = node.parent().filter(|parent| {
16507                parent.kind() == "parameter_declaration"
16508                    && parent.child_by_field_name("type") == Some(node)
16509            }) else {
16510                return false;
16511            };
16512            if !parameter
16513                .child_by_field_name("declarator")
16514                .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
16515            {
16516                return false;
16517            }
16518            let Some(parameters) = parameter
16519                .parent()
16520                .filter(|parent| parent.kind() == "parameter_list")
16521            else {
16522                return false;
16523            };
16524            let Some(function_declarator) = parameters
16525                .parent()
16526                .filter(|parent| parent.kind() == "function_declarator")
16527            else {
16528                return false;
16529            };
16530            function_declarator
16531        };
16532
16533        // Recovery may absorb the next statement's parenthesized cast into a
16534        // second function declarator. Follow only the original declarator chain.
16535        while let Some(parent) = function_declarator.parent().filter(|parent| {
16536            parent.kind() == "function_declarator"
16537                && parent.child_by_field_name("declarator") == Some(function_declarator)
16538        }) {
16539            function_declarator = parent;
16540        }
16541        // The macro-declaration grammar can attach the malformed call's
16542        // declarator directly to the bogus `function_definition` (a block
16543        // literal argument reads as the body) without the older grammar's
16544        // ERROR wrapper.
16545        let Some(current) = function_declarator.parent().filter(|parent| {
16546            parent.is_error() || recovered_c_block_call_envelope(function_declarator).is_some()
16547        }) else {
16548            return false;
16549        };
16550        current
16551    };
16552    loop {
16553        let Some(parent) = current.parent() else {
16554            return false;
16555        };
16556        if matches!(
16557            parent.kind(),
16558            "translation_unit"
16559                | "compound_statement"
16560                | "preproc_if"
16561                | "preproc_ifdef"
16562                | "preproc_ifndef"
16563                | "preproc_else"
16564                | "preproc_elif"
16565        ) {
16566            return true;
16567        }
16568        if parent.kind() == "function_definition"
16569            && parent.child_by_field_name("declarator") == Some(current)
16570            && parent.named_child(0) == Some(current)
16571            && parent.child_by_field_name("body").is_some()
16572        {
16573            return true;
16574        }
16575        if parent.is_error()
16576            || matches!(
16577                parent.kind(),
16578                "parameter_declaration"
16579                    | "parameter_list"
16580                    | "function_declarator"
16581                    | "abstract_function_declarator"
16582                    | "parenthesized_declarator"
16583            )
16584        {
16585            current = parent;
16586            continue;
16587        }
16588        return false;
16589    }
16590}
16591
16592/// C permits an identifier named `typename`. The C++ grammar can recover an
16593/// assignment using that identifier as a declaration whose declarator is a
16594/// parenthesized argument list, for example `typename = f(ctx, value)`. Only
16595/// the argument retained beneath the nested `ERROR` is a reference; sibling
16596/// declarator identifiers remain binders/grammar artifacts.
16597fn recovered_c_parenthesized_declarator_reference(node: Node<'_>) -> bool {
16598    // The macro-declaration grammar keeps the same line as a `dependent_type`
16599    // whose `macro_type_specifier` argument list retains the first argument
16600    // beneath the nested `ERROR`; that argument's `type_descriptor` is the
16601    // reference, and the specifier's own `type` field is the following
16602    // sibling, never a reference.
16603    if node
16604        .parent()
16605        .is_some_and(|parent| parent.kind() == "type_descriptor")
16606    {
16607        let Some(type_descriptor) = node.parent() else {
16608            return false;
16609        };
16610        let Some(error) = type_descriptor.parent().filter(|parent| parent.is_error()) else {
16611            return false;
16612        };
16613        if error.named_child_count() != 1 || error.named_child(0) != Some(type_descriptor) {
16614            return false;
16615        }
16616        let Some(dependent) = error
16617            .parent()
16618            .and_then(|parent| parent.parent())
16619            .filter(|parent| parent.kind() == "dependent_type")
16620        else {
16621            return false;
16622        };
16623        return dependent
16624            .child(0)
16625            .is_some_and(|keyword| keyword.kind() == "typename");
16626    }
16627    let Some(error) = node.parent().filter(|parent| parent.is_error()) else {
16628        return false;
16629    };
16630    if error.named_child_count() != 1 || error.named_child(0) != Some(node) {
16631        return false;
16632    }
16633    let Some(declarator) = error
16634        .parent()
16635        .filter(|parent| parent.kind() == "parenthesized_declarator")
16636    else {
16637        return false;
16638    };
16639    let Some(declaration) = declarator
16640        .parent()
16641        .filter(|parent| parent.kind() == "declaration")
16642    else {
16643        return false;
16644    };
16645    if declaration.child_by_field_name("declarator") != Some(declarator) {
16646        return false;
16647    }
16648    let Some(type_node) = declaration.child_by_field_name("type") else {
16649        return false;
16650    };
16651    type_node.kind() == "dependent_type"
16652        && type_node
16653            .child(0)
16654            .is_some_and(|keyword| keyword.kind() == "typename")
16655}
16656
16657fn recovered_c_explicit_assignment_callee(
16658    visibility: &VisibilityIndex<'_>,
16659    file: &ProjectFile,
16660    node: Node<'_>,
16661    name: &str,
16662) -> bool {
16663    let mut current = node;
16664    let error = loop {
16665        let Some(parent) = current.parent() else {
16666            return false;
16667        };
16668        if parent.is_error() {
16669            break parent;
16670        }
16671        current = parent;
16672    };
16673    let mut cursor = error.walk();
16674    let explicit_recovery_precedes_callee = error
16675        .named_children(&mut cursor)
16676        .take_while(|child| child.start_byte() < node.start_byte())
16677        .any(|child| child.kind() == "explicit_function_specifier");
16678    if !explicit_recovery_precedes_callee {
16679        return false;
16680    }
16681    visibility
16682        .visible_identifier_candidates(file, name)
16683        .any(CodeUnit::is_function)
16684}
16685
16686fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
16687    while let Some(parent) = node.parent() {
16688        if matches!(
16689            parent.kind(),
16690            "preproc_def" | "preproc_function_def" | "preproc_params"
16691        ) {
16692            return true;
16693        }
16694        if parent.is_error()
16695            || matches!(
16696                parent.kind(),
16697                "translation_unit" | "function_definition" | "compound_statement"
16698            )
16699        {
16700            return false;
16701        }
16702        node = parent;
16703    }
16704    false
16705}
16706
16707fn recovered_c_label_role(node: Node<'_>) -> bool {
16708    node.parent().is_some_and(|parent| {
16709        matches!(parent.kind(), "labeled_statement" | "goto_statement")
16710            && parent.child_by_field_name("label") == Some(node)
16711    })
16712}
16713
16714fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
16715    while let Some(parent) = node.parent() {
16716        if parent.is_error() {
16717            return false;
16718        }
16719        // A C macro call recovered as a function declarator can parse an
16720        // assignment-shaped argument as an optional parameter. Its
16721        // `default_value` field remains an expression role even though the
16722        // surrounding call shape is beneath ERROR.
16723        if parent.kind() == "optional_parameter_declaration"
16724            && parent
16725                .child_by_field_name("default_value")
16726                .is_some_and(|value| node_range_contains(value, node))
16727        {
16728            return true;
16729        }
16730        if parent.kind().ends_with("_expression")
16731            || matches!(
16732                parent.kind(),
16733                "argument_list"
16734                    | "return_statement"
16735                    | "expression_statement"
16736                    | "case_statement"
16737                    | "initializer_list"
16738                    | "init_declarator"
16739                    | "array_declarator"
16740                    | "field_designator"
16741                    | "enumerator"
16742            )
16743        {
16744            return true;
16745        }
16746        if matches!(
16747            parent.kind(),
16748            "translation_unit"
16749                | "function_definition"
16750                | "compound_statement"
16751                | "declaration"
16752                | "field_declaration"
16753                | "parameter_declaration"
16754        ) {
16755            return false;
16756        }
16757        node = parent;
16758    }
16759    false
16760}
16761
16762/// Whether a parameter declaration belongs to the callable scope whose body can
16763/// contain references to it.
16764///
16765/// Error recovery can wrap a macro-decorated class body in a synthetic outer
16766/// `function_definition`. Merely finding any callable ancestor would then leak
16767/// parameters from member prototypes into later member bodies. Require the
16768/// parameter to be inside that definition's own declarator instead.
16769pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
16770    let mut current = parameter.parent();
16771    while let Some(ancestor) = current {
16772        if ancestor.kind() == "lambda_expression" {
16773            return ancestor
16774                .child_by_field_name("declarator")
16775                .is_some_and(|declarator| {
16776                    declarator.start_byte() <= parameter.start_byte()
16777                        && parameter.end_byte() <= declarator.end_byte()
16778                });
16779        }
16780        if ancestor.kind() == "function_definition" {
16781            return ancestor
16782                .child_by_field_name("declarator")
16783                .is_some_and(|declarator| {
16784                    declarator.start_byte() <= parameter.start_byte()
16785                        && parameter.end_byte() <= declarator.end_byte()
16786                });
16787        }
16788        current = ancestor.parent();
16789    }
16790    false
16791}
16792
16793pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
16794    let mut current = node.parent();
16795    while let Some(ancestor) = current {
16796        if matches!(
16797            ancestor.kind(),
16798            "parameter_declaration" | "optional_parameter_declaration"
16799        ) {
16800            return crate::declarations::cpp_type_field_contains_node(ancestor, node);
16801        }
16802        if matches!(
16803            ancestor.kind(),
16804            "function_definition" | "lambda_expression" | "compound_statement"
16805        ) {
16806            return false;
16807        }
16808        current = ancestor.parent();
16809    }
16810    false
16811}
16812
16813/// Walk tag-specifier ancestors until a type-reference context makes its name
16814/// an operand rather than a declaration. Callers retain their own terminal
16815/// declaration boundaries after this shared exclusion scan.
16816pub fn cpp_tag_specifier_ancestors_before_type_reference_context(
16817    specifier: Node<'_>,
16818) -> impl Iterator<Item = Node<'_>> {
16819    std::iter::successors(specifier.parent(), |ancestor| ancestor.parent()).take_while(|ancestor| {
16820        !matches!(
16821            ancestor.kind(),
16822            "type_descriptor"
16823                | "parameter_declaration"
16824                | "optional_parameter_declaration"
16825                | "template_argument_list"
16826                | "cast_expression"
16827        )
16828    })
16829}
16830
16831pub fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
16832    if specifier.child_by_field_name("body").is_some() {
16833        return true;
16834    }
16835    for ancestor in cpp_tag_specifier_ancestors_before_type_reference_context(specifier) {
16836        match ancestor.kind() {
16837            "declaration" | "field_declaration" => {
16838                let mut cursor = ancestor.walk();
16839                return ancestor
16840                    .children_by_field_name("declarator", &mut cursor)
16841                    .next()
16842                    .is_none();
16843            }
16844            "translation_unit" => return true,
16845            _ => {}
16846        }
16847    }
16848    false
16849}
16850
16851pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
16852    match node.kind() {
16853        "identifier"
16854        | "field_identifier"
16855        | "qualified_identifier"
16856        | "scoped_identifier"
16857        | "operator_name"
16858        | "destructor_name"
16859        | "literal_operator_name" => Some(node),
16860        "reference_declarator" | "parenthesized_declarator" => {
16861            node.named_child(0).and_then(declarator_name_node)
16862        }
16863        _ => node
16864            .child_by_field_name("declarator")
16865            .or_else(|| node.child_by_field_name("name"))
16866            .or_else(|| node.child_by_field_name("field"))
16867            .and_then(declarator_name_node),
16868    }
16869}
16870
16871/// Whether `candidate` occurs on the declarator path before its selected name.
16872pub fn declarator_path_contains_before_name(
16873    declarator: Node<'_>,
16874    name: Node<'_>,
16875    candidate: Node<'_>,
16876) -> bool {
16877    let mut current = Some(declarator);
16878    while let Some(node) = current {
16879        if same_node(node, candidate) {
16880            return true;
16881        }
16882        if same_node(node, name) {
16883            return false;
16884        }
16885        current = node
16886            .child_by_field_name("declarator")
16887            .or_else(|| node.child_by_field_name("name"))
16888            .or_else(|| node.child_by_field_name("field"));
16889    }
16890    false
16891}
16892
16893fn declarator_name_path_contains(
16894    declarator: Node<'_>,
16895    candidate: Node<'_>,
16896    allow_type_identifier: bool,
16897) -> bool {
16898    let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
16899        return false;
16900    };
16901    declarator_path_contains_before_name(declarator, name, candidate)
16902}
16903
16904fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
16905    match node.kind() {
16906        "identifier"
16907        | "field_identifier"
16908        | "operator_name"
16909        | "destructor_name"
16910        | "literal_operator_name" => Some(node),
16911        "type_identifier" if allow_type_identifier => Some(node),
16912        _ => node
16913            .child_by_field_name("declarator")
16914            .or_else(|| node.child_by_field_name("name"))
16915            .or_else(|| node.child_by_field_name("field"))
16916            .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
16917    }
16918}
16919
16920/// True when `node` is a component of a larger structured type node whose outer
16921/// range is the single reference surfaced to callers.
16922pub fn is_nested_type_node(node: Node<'_>) -> bool {
16923    node.parent().is_some_and(|parent| {
16924        matches!(
16925            parent.kind(),
16926            "qualified_identifier" | "scoped_type_identifier" | "template_type"
16927        )
16928    })
16929}
16930
16931pub struct OutOfLineMemberDefinitionOwners<'tree> {
16932    pub owners: Vec<(Node<'tree>, CodeUnit)>,
16933    innermost: Option<(Node<'tree>, CodeUnit)>,
16934}
16935
16936impl OutOfLineMemberDefinitionOwners<'_> {
16937    pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
16938        self.innermost.as_ref().map(|(node, owner)| (*node, owner))
16939    }
16940}
16941
16942pub struct QualifiedOwnerComponents<'tree> {
16943    pub nodes: Vec<Node<'tree>>,
16944    pub names: Vec<String>,
16945    pub global: bool,
16946}
16947
16948/// True when each structured qualifier on the callable-name path has a real
16949/// `::` token. A macro-prefixed return type can make tree-sitter insert a
16950/// zero-width missing separator and parse `TYPE Result<T> method()` as the
16951/// false qualified declarator `Result<T>::method`.
16952pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
16953    let mut stack = vec![node];
16954    let mut found_separator = false;
16955    while let Some(current) = stack.pop() {
16956        if !matches!(
16957            current.kind(),
16958            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
16959        ) {
16960            continue;
16961        }
16962        let mut current_has_separator = false;
16963        for child in children_iter(current) {
16964            if child.kind() == "::" {
16965                if child.is_missing() {
16966                    return false;
16967                }
16968                current_has_separator = true;
16969                found_separator = true;
16970            }
16971        }
16972        if !current_has_separator {
16973            return false;
16974        }
16975        for field in ["scope", "name"] {
16976            if let Some(child) = current.child_by_field_name(field)
16977                && matches!(
16978                    child.kind(),
16979                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
16980                )
16981            {
16982                stack.push(child);
16983            }
16984        }
16985    }
16986    found_separator
16987}
16988
16989pub fn qualified_owner_components<'tree>(
16990    node: Node<'tree>,
16991    source: &str,
16992) -> Option<QualifiedOwnerComponents<'tree>> {
16993    if !qualified_name_has_concrete_scope_separators(node) {
16994        return None;
16995    }
16996    let mut nodes = cpp_name_component_nodes(node)?;
16997    nodes.pop()?;
16998    if nodes.is_empty() {
16999        return None;
17000    }
17001    let names = nodes
17002        .iter()
17003        .map(|component| node_text(*component, source).to_string())
17004        .collect();
17005    Some(QualifiedOwnerComponents {
17006        nodes,
17007        names,
17008        global: is_globally_qualified_cpp_name(node),
17009    })
17010}
17011
17012pub fn out_of_line_member_definition_owner<'tree>(
17013    analyzer: &CppGraphSource<'_>,
17014    visibility: &VisibilityIndex<'_>,
17015    file: &ProjectFile,
17016    source: &str,
17017    node: Node<'tree>,
17018) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
17019    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
17020        || !has_ancestor_kind(node, "function_definition")
17021        || !is_function_declarator_name_root(node)
17022    {
17023        return None;
17024    }
17025    let qualified = qualified_owner_components(node, source)?;
17026    let lexical_scope = enclosing_namespace_components(node, source)?;
17027    let mut owners = Vec::new();
17028    let mut innermost = None;
17029
17030    for component_count in 1..=qualified.names.len() {
17031        if let LexicalTypeResolution::Resolved { unit, .. } = visibility
17032            .resolve_type_components_lexically(
17033                analyzer,
17034                file,
17035                &qualified.names[..component_count],
17036                qualified.global,
17037                &lexical_scope,
17038            )
17039            && !owners
17040                .iter()
17041                .any(|(_, existing)| same_visible_symbol(existing, &unit))
17042        {
17043            if component_count == qualified.names.len() {
17044                innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
17045            }
17046            owners.push((qualified.nodes[component_count - 1], unit));
17047        }
17048    }
17049
17050    // The C++ analyzer has already reconciled an indexed out-of-line callable
17051    // against the include-visible class table. Consult that canonical owner
17052    // chain only when ordinary lexical lookup could not recover the innermost
17053    // owner.  A one-segment qualifier is safe here only when the enclosing
17054    // indexed callable has an authoritative class owner and the parser's
17055    // namespace path is a (possibly sparse) subsequence of that owner path.
17056    // The latter is what lets macro-wrapped namespace sentinels recover a
17057    // missing `time_internal`/`cord_internal` component without guessing an
17058    // unrelated short name.
17059    if innermost.is_none() {
17060        let indexed_owner_components = visibility
17061            .indexed_enclosing_owner_scope(analyzer, file, node)
17062            .or_else(|| {
17063                // Retain the legacy rendered-name fallback for the existing
17064                // multi-segment path when an enclosing owner chain is not
17065                // available (for example, cache-loaded units without parent
17066                // links).  One-segment recovery must stay canonical-only.
17067                if qualified.names.len() <= 1 {
17068                    return None;
17069                }
17070                let range = Range {
17071                    start_byte: node.start_byte(),
17072                    end_byte: node.end_byte(),
17073                    start_line: node.start_position().row,
17074                    end_line: node.end_position().row,
17075                };
17076                let start = analyzer.enclosing_code_unit(file, &range)?;
17077                let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
17078                    brokk_bifrost_core::analyzer::Language::Cpp,
17079                    &cpp_name_for(&start),
17080                );
17081                components.pop();
17082                Some(components)
17083            });
17084        if let Some(indexed_owner_components) = indexed_owner_components
17085            && indexed_owner_components.len() > qualified.names.len()
17086            && indexed_owner_components.ends_with(&qualified.names)
17087            && indexed_namespace_path_is_recoverable(
17088                &lexical_scope,
17089                &indexed_owner_components,
17090                qualified.names.len(),
17091            )
17092            // A globally-qualified one-segment owner is an explicit request
17093            // for the top-level binding; do not reinterpret it as a missing
17094            // namespace component.  Existing multi-segment global lookups
17095            // retain their historical indexed recovery.
17096            && (qualified.names.len() > 1 || !qualified.global)
17097        {
17098            let namespace_count = indexed_owner_components.len() - qualified.names.len();
17099            for component_count in 1..=qualified.names.len() {
17100                let expected = &indexed_owner_components[..namespace_count + component_count];
17101                let owner_node = qualified.nodes[component_count - 1];
17102                for owner in visibility
17103                    .visible_identifier_candidates(file, &qualified.names[component_count - 1])
17104                    .filter(|candidate| candidate.is_class())
17105                    .filter(|candidate| {
17106                        canonical_cpp_scope_components(candidate) == expected
17107                            && visibility.external_type_candidate_visible_in_context(
17108                                analyzer, file, candidate, node,
17109                            )
17110                    })
17111                {
17112                    if component_count == qualified.names.len() && innermost.is_none() {
17113                        innermost = Some((owner_node, owner.clone()));
17114                    }
17115                    if !owners
17116                        .iter()
17117                        .any(|(_, existing)| same_symbol(existing, owner))
17118                    {
17119                        owners.push((owner_node, owner.clone()));
17120                    }
17121                }
17122            }
17123        }
17124    }
17125    (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
17126}
17127
17128fn is_function_declarator_name_root(node: Node<'_>) -> bool {
17129    let mut current = node;
17130    while let Some(parent) = current.parent() {
17131        if parent.kind() == "function_declarator" {
17132            return parent.child_by_field_name("declarator") == Some(current);
17133        }
17134        if matches!(
17135            parent.kind(),
17136            "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
17137        ) && parent.child_by_field_name("declarator") == Some(current)
17138        {
17139            current = parent;
17140            continue;
17141        }
17142        return false;
17143    }
17144    false
17145}
17146
17147pub fn append_cpp_name_components(
17148    node: Node<'_>,
17149    source: &str,
17150    out: &mut Vec<String>,
17151) -> Option<()> {
17152    out.extend(
17153        cpp_name_component_nodes(node)?
17154            .into_iter()
17155            .map(|component| node_text(component, source).to_string()),
17156    );
17157    Some(())
17158}
17159
17160/// The components of a C++ name with the source range of the token that
17161/// writes each one, outermost first.
17162fn cpp_name_component_ranges(node: Node<'_>, source: &str) -> Option<Vec<(String, Range)>> {
17163    cpp_name_component_nodes(node).map(|components| {
17164        components
17165            .into_iter()
17166            .map(|component| {
17167                (
17168                    node_text(component, source).to_string(),
17169                    node_range(component),
17170                )
17171            })
17172            .collect()
17173    })
17174}
17175
17176pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
17177    let mut components = Vec::new();
17178    append_cpp_name_components(node, source, &mut components)?;
17179    Some(components)
17180}
17181
17182/// Resolve a structured type spelling from an object-like macro replacement
17183/// when definition-site source order has no answer.
17184///
17185/// Macro replacement tokens are looked up where the macro is expanded, so a
17186/// type declared later in the defining header can still be their destination.
17187/// Without expanding every invocation, accept only one include-visible logical
17188/// class or alias whose structured path ends in the replacement components.
17189/// An ordinary lexical answer always takes precedence at the call site.
17190pub fn unique_macro_replacement_type_candidate(
17191    analyzer: &CppGraphSource<'_>,
17192    visibility: &VisibilityIndex<'_>,
17193    file: &ProjectFile,
17194    components: &[String],
17195) -> Option<CodeUnit> {
17196    let terminal = components.last()?;
17197    let mut candidates = Vec::new();
17198    for candidate in visibility
17199        .visible_identifier_candidates(file, terminal)
17200        .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
17201        .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
17202    {
17203        if !candidates
17204            .iter()
17205            .any(|existing| same_logical_symbol(existing, candidate))
17206        {
17207            candidates.push(candidate.clone());
17208        }
17209    }
17210    (candidates.len() == 1).then(|| candidates.remove(0))
17211}
17212
17213/// Whether a structured using-declaration scope can name `qualified` as an
17214/// ancestor class. The boundary check prevents `Base` from matching
17215/// `OtherBase` while allowing a relative `Base` spelling to match `ns::Base`.
17216pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
17217    qualified == scope
17218        || qualified
17219            .strip_suffix(scope)
17220            .is_some_and(|prefix| prefix.ends_with("::"))
17221}
17222
17223/// Whether `node` is the direct structured type payload of a template
17224/// argument. This role remains meaningful even when a surrounding expression
17225/// is below tree-sitter recovery, because both the `template_argument_list`
17226/// and the `type_descriptor` retain their named fields.
17227pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
17228    let Some(type_descriptor) = node.parent() else {
17229        return false;
17230    };
17231    if type_descriptor.kind() != "type_descriptor"
17232        || type_descriptor.child_by_field_name("type") != Some(node)
17233    {
17234        return false;
17235    }
17236    let Some(arguments) = type_descriptor.parent() else {
17237        return false;
17238    };
17239    if arguments.kind() != "template_argument_list" {
17240        return false;
17241    }
17242    arguments.parent().is_some_and(|parent| {
17243        matches!(parent.kind(), "template_type" | "template_function")
17244            && parent.child_by_field_name("arguments") == Some(arguments)
17245    })
17246}
17247
17248pub fn cpp_template_reference_arguments(
17249    mut node: Node<'_>,
17250    source: &str,
17251) -> Option<Vec<CppTemplateExpression>> {
17252    loop {
17253        match node.kind() {
17254            "template_type" | "template_function" => {
17255                let arguments = node.child_by_field_name("arguments")?;
17256                let mut cursor = arguments.walk();
17257                return Some(
17258                    arguments
17259                        .named_children(&mut cursor)
17260                        .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
17261                        .map(|argument| CppTemplateExpression {
17262                            text: normalize_cpp_whitespace(node_text(argument, source)),
17263                            // One template term from a resolver query; see `ParentIndex::unindexed`.
17264                            term: cpp_template_term(
17265                                argument,
17266                                source,
17267                                &[],
17268                                &ParentIndex::unindexed(),
17269                            ),
17270                        })
17271                        .collect(),
17272                );
17273            }
17274            "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
17275                node = node
17276                    .child_by_field_name("name")
17277                    .or_else(|| node.child_by_field_name("type"))?;
17278            }
17279            _ => return None,
17280        }
17281    }
17282}
17283
17284fn cpp_reconcile_primary_template_parameters(
17285    candidates: &[(&CodeUnit, &CppTemplateMetadata)],
17286    preferred: &CodeUnit,
17287) -> Option<Vec<CppTemplateParameterMetadata>> {
17288    let canonical = candidates
17289        .iter()
17290        .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
17291    let mut merged = canonical
17292        .parameters
17293        .iter()
17294        .map(|parameter| CppTemplateParameterMetadata {
17295            name: parameter.name.clone(),
17296            kind: parameter.kind,
17297            variadic: parameter.variadic,
17298            default: None,
17299        })
17300        .collect::<Vec<_>>();
17301
17302    for (_, metadata) in candidates {
17303        if metadata.parameters.len() != merged.len() {
17304            return None;
17305        }
17306        let rename_bindings = metadata
17307            .parameters
17308            .iter()
17309            .zip(&merged)
17310            .map(|(parameter, canonical)| {
17311                (
17312                    parameter.name.clone(),
17313                    CppTemplateTerm::Parameter(canonical.name.clone()),
17314                )
17315            })
17316            .collect::<HashMap<_, _>>();
17317        for ((parameter, canonical), merged_parameter) in metadata
17318            .parameters
17319            .iter()
17320            .zip(&canonical.parameters)
17321            .zip(&mut merged)
17322        {
17323            if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
17324                return None;
17325            }
17326            let Some(default) = &parameter.default else {
17327                continue;
17328            };
17329            let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
17330            if let Some(existing) = &merged_parameter.default {
17331                if !cpp_template_terms_equal(&existing.term, &normalized_term) {
17332                    return None;
17333                }
17334            } else {
17335                merged_parameter.default = Some(CppTemplateExpression {
17336                    text: default.text.clone(),
17337                    term: normalized_term,
17338                });
17339            }
17340        }
17341    }
17342    Some(merged)
17343}
17344
17345pub fn cpp_bind_template_arguments(
17346    parameters: &[CppTemplateParameterMetadata],
17347    explicit_arguments: &[CppTemplateExpression],
17348) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
17349    let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
17350    if variadic_index.is_some_and(|index| {
17351        index + 1 != parameters.len()
17352            || parameters[index + 1..]
17353                .iter()
17354                .any(|parameter| parameter.variadic)
17355    }) {
17356        return None;
17357    }
17358    let fixed_count = variadic_index.unwrap_or(parameters.len());
17359    if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
17360        return None;
17361    }
17362    let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
17363    let mut expanded = explicit_arguments[..explicit_fixed_count]
17364        .iter()
17365        .map(cpp_clone_template_expression_iterative)
17366        .collect::<Vec<_>>();
17367    let mut bindings = HashMap::default();
17368    for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
17369        bindings.insert(
17370            parameter.name.clone(),
17371            cpp_clone_template_term_iterative(&argument.term),
17372        );
17373    }
17374    for parameter in &parameters[explicit_fixed_count..fixed_count] {
17375        let default = parameter.default.as_ref()?;
17376        let term = cpp_substitute_template_term(&default.term, &bindings)?;
17377        bindings.insert(parameter.name.clone(), term.clone());
17378        expanded.push(CppTemplateExpression {
17379            text: default.text.clone(),
17380            term,
17381        });
17382    }
17383    if let Some(index) = variadic_index {
17384        let packed_arguments = &explicit_arguments[explicit_fixed_count..];
17385        expanded.extend(
17386            packed_arguments
17387                .iter()
17388                .map(cpp_clone_template_expression_iterative),
17389        );
17390        bindings.insert(
17391            parameters[index].name.clone(),
17392            CppTemplateTerm::Node {
17393                kind: "parameter_pack".to_string(),
17394                children: packed_arguments
17395                    .iter()
17396                    .map(|argument| cpp_clone_template_term_iterative(&argument.term))
17397                    .collect(),
17398            },
17399        );
17400    }
17401    Some((expanded, bindings))
17402}
17403
17404fn cpp_specialization_matches(
17405    metadata: &CppTemplateMetadata,
17406    arguments: &[CppTemplateExpression],
17407) -> bool {
17408    cpp_specialization_terms_match(
17409        &metadata.parameters,
17410        &metadata.specialization_arguments,
17411        arguments,
17412    )
17413}
17414
17415fn cpp_specialization_terms_match(
17416    parameters: &[CppTemplateParameterMetadata],
17417    patterns: &[CppTemplateExpression],
17418    arguments: &[CppTemplateExpression],
17419) -> bool {
17420    if patterns.len() != arguments.len() {
17421        return false;
17422    }
17423    let parameter_names = parameters
17424        .iter()
17425        .map(|parameter| parameter.name.as_str())
17426        .collect::<HashSet<_>>();
17427    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
17428    patterns.iter().zip(arguments).all(|(pattern, argument)| {
17429        cpp_unify_template_term(
17430            &pattern.term,
17431            &argument.term,
17432            &parameter_names,
17433            &mut bindings,
17434        )
17435    })
17436}
17437
17438fn cpp_specialization_more_specialized(
17439    candidate: &CppTemplateMetadata,
17440    other: &CppTemplateMetadata,
17441) -> bool {
17442    cpp_specialization_pattern_accepts(other, candidate)
17443        && !cpp_specialization_pattern_accepts(candidate, other)
17444}
17445
17446fn cpp_specialization_pattern_accepts(
17447    broader: &CppTemplateMetadata,
17448    narrower: &CppTemplateMetadata,
17449) -> bool {
17450    cpp_specialization_terms_match(
17451        &broader.parameters,
17452        &broader.specialization_arguments,
17453        &narrower.specialization_arguments,
17454    )
17455}
17456
17457pub fn cpp_substitute_template_term(
17458    term: &CppTemplateTerm,
17459    bindings: &HashMap<String, CppTemplateTerm>,
17460) -> Option<CppTemplateTerm> {
17461    enum Work<'a> {
17462        Visit(&'a CppTemplateTerm),
17463        Build { kind: String, child_count: usize },
17464    }
17465
17466    let mut work = vec![Work::Visit(term)];
17467    let mut substituted = Vec::new();
17468    while let Some(next) = work.pop() {
17469        match next {
17470            Work::Visit(CppTemplateTerm::Parameter(name)) => {
17471                substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
17472            }
17473            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
17474                substituted.push(CppTemplateTerm::Atom {
17475                    kind: kind.clone(),
17476                    text: text.clone(),
17477                });
17478            }
17479            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
17480                work.push(Work::Build {
17481                    kind: kind.clone(),
17482                    child_count: children.len(),
17483                });
17484                work.extend(children.iter().rev().map(Work::Visit));
17485            }
17486            Work::Build { kind, child_count } => {
17487                let children = substituted.split_off(substituted.len() - child_count);
17488                substituted.push(CppTemplateTerm::Node { kind, children });
17489            }
17490        }
17491    }
17492    substituted.pop()
17493}
17494
17495pub fn cpp_substitute_template_arguments(
17496    arguments: &[CppTemplateExpression],
17497    bindings: &HashMap<String, CppTemplateTerm>,
17498) -> Option<Vec<CppTemplateExpression>> {
17499    let mut substituted = Vec::new();
17500    for argument in arguments {
17501        let CppTemplateTerm::Node { kind, children } = &argument.term else {
17502            substituted.push(CppTemplateExpression {
17503                text: argument.text.clone(),
17504                term: cpp_substitute_template_term(&argument.term, bindings)?,
17505            });
17506            continue;
17507        };
17508        if kind != "parameter_pack_expansion" {
17509            substituted.push(CppTemplateExpression {
17510                text: argument.text.clone(),
17511                term: cpp_substitute_template_term(&argument.term, bindings)?,
17512            });
17513            continue;
17514        }
17515        let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
17516            return None;
17517        };
17518        if ellipsis != "..." {
17519            return None;
17520        }
17521
17522        let mut pack_names = Vec::new();
17523        let mut work = vec![pattern];
17524        while let Some(term) = work.pop() {
17525            match term {
17526                CppTemplateTerm::Parameter(name)
17527                    if matches!(
17528                        bindings.get(name),
17529                        Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
17530                    ) =>
17531                {
17532                    if !pack_names.contains(name) {
17533                        pack_names.push(name.clone());
17534                    }
17535                }
17536                CppTemplateTerm::Node { children, .. } => work.extend(children),
17537                CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
17538            }
17539        }
17540        let first_pack = pack_names.first()?;
17541        let CppTemplateTerm::Node {
17542            children: first_elements,
17543            ..
17544        } = bindings.get(first_pack)?
17545        else {
17546            return None;
17547        };
17548        let pack_len = first_elements.len();
17549        for pack_name in &pack_names {
17550            let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
17551                return None;
17552            };
17553            if children.len() != pack_len {
17554                return None;
17555            }
17556        }
17557        for index in 0..pack_len {
17558            let mut element_bindings = bindings.clone();
17559            for pack_name in &pack_names {
17560                let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
17561                    return None;
17562                };
17563                element_bindings.insert(
17564                    pack_name.clone(),
17565                    cpp_clone_template_term_iterative(&children[index]),
17566                );
17567            }
17568            substituted.push(CppTemplateExpression {
17569                text: argument.text.clone(),
17570                term: cpp_substitute_template_term(pattern, &element_bindings)?,
17571            });
17572        }
17573    }
17574    Some(substituted)
17575}
17576
17577fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
17578    enum Work<'a> {
17579        Visit(&'a CppTemplateTerm),
17580        Build { kind: String, child_count: usize },
17581    }
17582
17583    let mut work = vec![Work::Visit(term)];
17584    let mut cloned = Vec::new();
17585    while let Some(next) = work.pop() {
17586        match next {
17587            Work::Visit(CppTemplateTerm::Parameter(name)) => {
17588                cloned.push(CppTemplateTerm::Parameter(name.clone()));
17589            }
17590            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
17591                cloned.push(CppTemplateTerm::Atom {
17592                    kind: kind.clone(),
17593                    text: text.clone(),
17594                });
17595            }
17596            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
17597                work.push(Work::Build {
17598                    kind: kind.clone(),
17599                    child_count: children.len(),
17600                });
17601                work.extend(children.iter().rev().map(Work::Visit));
17602            }
17603            Work::Build { kind, child_count } => {
17604                let children = cloned.split_off(cloned.len() - child_count);
17605                cloned.push(CppTemplateTerm::Node { kind, children });
17606            }
17607        }
17608    }
17609    cloned
17610        .pop()
17611        .expect("template term traversal emits one root")
17612}
17613
17614fn cpp_clone_template_expression_iterative(
17615    expression: &CppTemplateExpression,
17616) -> CppTemplateExpression {
17617    CppTemplateExpression {
17618        text: expression.text.clone(),
17619        term: cpp_clone_template_term_iterative(&expression.term),
17620    }
17621}
17622
17623pub fn cpp_unify_template_term(
17624    pattern: &CppTemplateTerm,
17625    argument: &CppTemplateTerm,
17626    parameters: &HashSet<&str>,
17627    bindings: &mut HashMap<String, CppTemplateTerm>,
17628) -> bool {
17629    let mut work = vec![(pattern, argument)];
17630    while let Some((pattern, argument)) = work.pop() {
17631        match pattern {
17632            CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
17633                if let Some(bound) = bindings.get(name) {
17634                    if !cpp_template_terms_equal(bound, argument) {
17635                        return false;
17636                    }
17637                } else {
17638                    bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
17639                }
17640            }
17641            CppTemplateTerm::Atom {
17642                kind: pattern_kind,
17643                text: pattern_text,
17644            } => {
17645                if !matches!(
17646                    argument,
17647                    CppTemplateTerm::Atom { kind, text }
17648                        if kind == pattern_kind && text == pattern_text
17649                ) {
17650                    return false;
17651                }
17652            }
17653            CppTemplateTerm::Node {
17654                kind: pattern_kind,
17655                children: pattern_children,
17656            } => {
17657                let CppTemplateTerm::Node { kind, children } = argument else {
17658                    return false;
17659                };
17660                if kind != pattern_kind || children.len() != pattern_children.len() {
17661                    return false;
17662                }
17663                work.extend(pattern_children.iter().zip(children).rev());
17664            }
17665            CppTemplateTerm::Parameter(_) => return false,
17666        }
17667    }
17668    true
17669}
17670
17671pub(crate) fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
17672    let mut work = vec![(left, right)];
17673    while let Some((left, right)) = work.pop() {
17674        match (left, right) {
17675            (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
17676                if left != right {
17677                    return false;
17678                }
17679            }
17680            (
17681                CppTemplateTerm::Atom {
17682                    kind: left_kind,
17683                    text: left_text,
17684                },
17685                CppTemplateTerm::Atom {
17686                    kind: right_kind,
17687                    text: right_text,
17688                },
17689            ) => {
17690                if left_kind != right_kind || left_text != right_text {
17691                    return false;
17692                }
17693            }
17694            (
17695                CppTemplateTerm::Node {
17696                    kind: left_kind,
17697                    children: left_children,
17698                },
17699                CppTemplateTerm::Node {
17700                    kind: right_kind,
17701                    children: right_children,
17702                },
17703            ) => {
17704                if left_kind != right_kind || left_children.len() != right_children.len() {
17705                    return false;
17706                }
17707                work.extend(left_children.iter().zip(right_children).rev());
17708            }
17709            _ => return false,
17710        }
17711    }
17712    true
17713}
17714
17715pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
17716    let mut components = Vec::new();
17717    let mut stack = vec![node];
17718    while let Some(current) = stack.pop() {
17719        match current.kind() {
17720            "identifier"
17721            | "field_identifier"
17722            | "namespace_identifier"
17723            | "type_identifier"
17724            | "operator_name"
17725            | "destructor_name" => components.push(current),
17726            "template_type" | "template_function" => {
17727                stack.push(current.child_by_field_name("name")?);
17728            }
17729            "dependent_name" => stack.push(current.named_child(0)?),
17730            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
17731                stack.push(current.child_by_field_name("name")?);
17732                if let Some(scope) = current.child_by_field_name("scope") {
17733                    stack.push(scope);
17734                }
17735            }
17736            "nested_namespace_specifier" => {
17737                for index in (0..current.named_child_count()).rev() {
17738                    stack.push(current.named_child(index)?);
17739                }
17740            }
17741            _ => return None,
17742        }
17743    }
17744    Some(components)
17745}
17746
17747pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
17748    node.child_by_field_name("scope").is_none()
17749        && node.child(0).is_some_and(|child| child.kind() == "::")
17750}
17751
17752fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
17753    let mut namespaces = Vec::new();
17754    for parent in enclosing_cpp_namespace_nodes(node) {
17755        if let Some(name) = parent.child_by_field_name("name") {
17756            let mut components = Vec::new();
17757            append_cpp_name_components(name, source, &mut components)?;
17758            namespaces.push(components);
17759        }
17760    }
17761    Some(namespaces.into_iter().flatten().collect())
17762}
17763
17764/// Whether a parser-derived namespace path can be reconciled with an indexed
17765/// owner scope without inventing an unrelated short-name binding.
17766///
17767/// Macro namespace sentinels can make tree-sitter omit one or more namespace
17768/// definitions from the ancestor chain. Preserve the order of every namespace
17769/// that did survive parsing, but allow indexed components between them. An
17770/// empty path is accepted only when the declarator itself supplies a nested
17771/// owner suffix such as `Outer::Inner`: together with the indexed enclosing
17772/// owner chain, that suffix is structural evidence that a namespace was lost.
17773/// A one-segment owner at the translation-unit root remains insufficient.
17774fn indexed_namespace_path_is_recoverable(
17775    lexical_scope: &[String],
17776    indexed_owner_scope: &[String],
17777    explicit_owner_component_count: usize,
17778) -> bool {
17779    if lexical_scope.is_empty() {
17780        return explicit_owner_component_count > 1;
17781    }
17782    if lexical_scope.len() >= indexed_owner_scope.len() {
17783        return false;
17784    }
17785    super::cpp_namespace_path_is_ordered_subsequence(lexical_scope, indexed_owner_scope)
17786}
17787
17788pub use brokk_bifrost_core::analyzer::tree_walk::has_ancestor_kind;
17789
17790/// Whether a declaration type is initialized with a pointer cast.
17791///
17792/// This structured shape has an independent qualified occurrence in addition
17793/// to the cast descriptor below it. Other declarations must keep their normal
17794/// full-range occurrence only.
17795pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
17796    let mut current = Some(node);
17797    while let Some(candidate) = current {
17798        if candidate.kind() == "declaration" {
17799            if !crate::declarations::cpp_type_field_contains_node(candidate, node) {
17800                return false;
17801            }
17802            let mut cursor = candidate.walk();
17803            return candidate.named_children(&mut cursor).any(|child| {
17804                child.kind() == "init_declarator"
17805                    && child
17806                        .child_by_field_name("value")
17807                        .is_some_and(|value| value.kind() == "cast_expression")
17808            });
17809        }
17810        current = candidate.parent();
17811    }
17812    false
17813}
17814
17815#[derive(Clone, Copy, PartialEq, Eq)]
17816pub(crate) enum QualifiedAliasReferenceKind {
17817    Ordinary,
17818    ConstructorWithExpressionArgument,
17819    ExhaustiveTemplate,
17820}
17821
17822/// Whether a qualified alias reference preserves the requested target.
17823///
17824/// The complete qualified spelling and its terminal identifier are both valid
17825/// occurrences when the visible alias path is structurally proven to name the
17826/// target. Template aliases use their bound arguments; ordinary aliases use
17827/// their structured primary chain.
17828pub(crate) fn qualified_alias_reference_preserves_target(
17829    node: Node<'_>,
17830    target: &CodeUnit,
17831    analyzer: &CppGraphSource<'_>,
17832    visibility: &VisibilityIndex<'_>,
17833    file: &ProjectFile,
17834    source: &str,
17835) -> Option<QualifiedAliasReferenceKind> {
17836    if !matches!(
17837        node.kind(),
17838        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
17839    ) {
17840        return None;
17841    }
17842    let components = cpp_type_name_components(node, source)?;
17843    let name = components.last()?;
17844    analyzer.type_alias_provider().and_then(|provider| {
17845        visibility
17846            .visible_identifier_candidates(file, name)
17847            .find_map(|candidate| {
17848                let proof = provider.is_type_alias(candidate)
17849                    && canonical_cpp_scope_components(candidate) == components
17850                    && visibility.external_type_candidate_visible_in_context(
17851                        analyzer, file, candidate, node,
17852                    )
17853                    && match cpp_template_reference_arguments(node, source) {
17854                        Some(arguments) => visibility.template_alias_arguments_preserve_target(
17855                            analyzer, file, candidate, &arguments, target,
17856                        ),
17857                        None => visibility.structured_alias_primary_preserves_target(
17858                            analyzer, file, candidate, target,
17859                        ),
17860                    };
17861                proof.then(|| {
17862                    if cpp_template_reference_arguments(node, source).is_some()
17863                        && visibility.is_exhaustive_same_fqn_type_declaration_family(
17864                            analyzer, file, candidate,
17865                        )
17866                    {
17867                        QualifiedAliasReferenceKind::ExhaustiveTemplate
17868                    } else if qualified_alias_constructor_has_expression_argument(node)
17869                        || qualified_alias_local_constructor_declaration(node)
17870                    {
17871                        QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
17872                    } else {
17873                        QualifiedAliasReferenceKind::Ordinary
17874                    }
17875                })
17876            })
17877    })
17878}
17879
17880pub(crate) fn qualified_alias_reference_requires_terminal(
17881    reference: Option<QualifiedAliasReferenceKind>,
17882) -> bool {
17883    matches!(
17884        reference,
17885        Some(
17886            QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
17887                | QualifiedAliasReferenceKind::ExhaustiveTemplate
17888        )
17889    )
17890}
17891
17892fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
17893    let Some(declaration) = node.parent().filter(|parent| {
17894        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
17895    }) else {
17896        return false;
17897    };
17898    let mut cursor = declaration.walk();
17899    declaration.named_children(&mut cursor).any(|child| {
17900        child.kind() == "init_declarator"
17901            && child
17902                .child_by_field_name("value")
17903                .filter(|value| value.kind() == "argument_list")
17904                .is_some_and(|arguments| {
17905                    let mut cursor = arguments.walk();
17906                    arguments.named_children(&mut cursor).any(|argument| {
17907                        let is_parameter = matches!(
17908                            argument.kind(),
17909                            "parameter_declaration" | "optional_parameter_declaration"
17910                        );
17911                        if is_parameter {
17912                            argument
17913                                .child_by_field_name("type")
17914                                .is_some_and(|type_node| {
17915                                    type_node.kind() == "type_identifier"
17916                                        && argument.child_by_field_name("declarator").is_none()
17917                                })
17918                        } else {
17919                            !argument.kind().ends_with("_literal")
17920                                && !matches!(argument.kind(), "true" | "false" | "nullptr")
17921                        }
17922                    })
17923                })
17924    })
17925}
17926
17927/// Tree-sitter represents a local C++ direct construction such as
17928/// `Alias value(argument)` as a function declarator. Restrict that recovery to
17929/// declarations inside a compound statement so namespace-scope function
17930/// declarations with the same qualified return type stay full-range only.
17931fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
17932    let Some(declaration) = node.parent().filter(|parent| {
17933        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
17934    }) else {
17935        return false;
17936    };
17937    if declaration
17938        .parent()
17939        .is_none_or(|parent| parent.kind() != "compound_statement")
17940    {
17941        return false;
17942    }
17943    let mut cursor = declaration.walk();
17944    declaration
17945        .named_children(&mut cursor)
17946        .any(|child| child.kind() == "function_declarator")
17947}
17948
17949/// Return the terminal identifier represented by a callable or type callee.
17950///
17951/// Qualified, scoped, template, and field wrappers are traversed through their
17952/// grammar fields so both function calls and type constructions emit the token
17953/// that names the referenced declaration.
17954pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
17955    loop {
17956        let next = match node.kind() {
17957            "qualified_identifier"
17958            | "scoped_identifier"
17959            | "template_method"
17960            | "template_function"
17961            | "template_type" => node.child_by_field_name("name"),
17962            "field_expression" => node.child_by_field_name("field"),
17963            _ => None,
17964        };
17965        let Some(next) = next else {
17966            return node;
17967        };
17968        node = next;
17969    }
17970}
17971
17972#[derive(Clone, Copy)]
17973pub struct RecoveredRelationalTemplateMemberCall<'tree> {
17974    pub receiver: Node<'tree>,
17975    pub member: Node<'tree>,
17976    pub arity: usize,
17977}
17978
17979/// Recover `receiver.member<argument>(call_arguments)` when tree-sitter chose
17980/// nested relational expressions instead of a `template_method` call.
17981///
17982/// The recovery uses only grammar fields: the selected field must be the left
17983/// side of `<`, that expression must be the left side of `>`, and the right
17984/// side of `>` must be the parenthesized call arguments. Semantic callers must
17985/// additionally prove the receiver owner and the member's template status.
17986pub fn recovered_relational_template_member_call(
17987    field: Node<'_>,
17988) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
17989    if field.kind() != "field_expression" {
17990        return None;
17991    }
17992    let receiver = field
17993        .child_by_field_name("argument")
17994        .or_else(|| field.child_by_field_name("object"))?;
17995    let member = field.child_by_field_name("field")?;
17996    let less = field.parent()?;
17997    if less.kind() != "binary_expression"
17998        || less.child_by_field_name("left") != Some(field)
17999        || less
18000            .child_by_field_name("operator")
18001            .is_none_or(|operator| operator.kind() != "<")
18002        || less.child_by_field_name("right").is_none()
18003    {
18004        return None;
18005    }
18006    let greater = less.parent()?;
18007    if greater.kind() != "binary_expression"
18008        || greater.child_by_field_name("left") != Some(less)
18009        || greater
18010            .child_by_field_name("operator")
18011            .is_none_or(|operator| operator.kind() != ">")
18012    {
18013        return None;
18014    }
18015    let arguments = greater.child_by_field_name("right")?;
18016    if arguments.kind() != "parenthesized_expression" {
18017        return None;
18018    }
18019    let arity = parenthesized_call_argument_arity(arguments)?;
18020    Some(RecoveredRelationalTemplateMemberCall {
18021        receiver,
18022        member,
18023        arity,
18024    })
18025}
18026
18027fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
18028    let expression = arguments.named_child(0)?;
18029    if expression.kind() != "comma_expression" {
18030        return Some(1);
18031    }
18032    let mut arity = 0usize;
18033    let mut stack = vec![expression];
18034    while let Some(node) = stack.pop() {
18035        if node.kind() == "comma_expression" {
18036            stack.push(node.child_by_field_name("right")?);
18037            stack.push(node.child_by_field_name("left")?);
18038        } else {
18039            arity += 1;
18040        }
18041    }
18042    Some(arity)
18043}
18044
18045/// Whether `node` is part of a call's callee expression, walking only through
18046/// the grammar wrappers that can structurally contain that callee.
18047pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
18048    while let Some(parent) = node.parent() {
18049        match parent.kind() {
18050            "call_expression" => {
18051                return parent
18052                    .child_by_field_name("function")
18053                    .or_else(|| parent.named_child(0))
18054                    == Some(node);
18055            }
18056            "qualified_identifier"
18057            | "scoped_identifier"
18058            | "template_function"
18059            | "template_type"
18060            | "field_expression" => node = parent,
18061            _ => return false,
18062        }
18063    }
18064    false
18065}
18066
18067/// Expand a C++ type-name component to the complete written reference that
18068/// owns it. Qualified path wrappers that stop before a later component remain
18069/// separate, so a type used as an owner keeps its prefix range.
18070pub fn type_reference_hit_node(mut node: Node<'_>) -> Node<'_> {
18071    loop {
18072        let Some(parent) = node.parent() else {
18073            return node;
18074        };
18075        if parent.kind() == "template_type"
18076            && parent
18077                .child_by_field_name("name")
18078                .is_some_and(|name| same_node(name, node))
18079        {
18080            node = parent;
18081            continue;
18082        }
18083        if matches!(
18084            parent.kind(),
18085            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
18086        ) && (parent
18087            .child_by_field_name("scope")
18088            .is_some_and(|scope| same_node(scope, node))
18089            || parent
18090                .child_by_field_name("name")
18091                .is_some_and(|name| same_node(name, node)))
18092            && parent.end_byte() == node.end_byte()
18093        {
18094            node = parent;
18095            continue;
18096        }
18097        return node;
18098    }
18099}
18100
18101pub fn normalize_type_text(value: &str) -> String {
18102    strip_tag_type_prefix(
18103        normalize_cpp_whitespace(value)
18104            .trim_start_matches("const ")
18105            .trim_end_matches('*')
18106            .trim_end_matches('&')
18107            .trim(),
18108    )
18109    .to_string()
18110}
18111
18112use crate::declarations::strip_tag_type_prefix;
18113
18114pub fn normalize_reference_name(value: &str) -> Option<String> {
18115    let normalized = normalize_cpp_reference_text(value);
18116    (!normalized.is_empty()).then_some(normalized)
18117}
18118
18119pub fn normalize_cpp_reference_text(value: &str) -> String {
18120    let mut text = normalize_cpp_whitespace(value)
18121        .trim_start_matches("new ")
18122        .trim()
18123        .to_string();
18124    if let Some(index) = text.find(['(', '{']) {
18125        text.truncate(index);
18126    }
18127    if let Some(index) = text.find('<') {
18128        text.truncate(index);
18129    }
18130    let normalized = text
18131        .trim()
18132        .trim_start_matches("const ")
18133        .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
18134        .trim_matches(':')
18135        .trim();
18136    strip_tag_type_prefix(normalized).to_string()
18137}
18138
18139pub fn cpp_name_for(unit: &CodeUnit) -> String {
18140    let short = unit.short_name().replace(['.', '$'], "::");
18141    if unit.package_name().is_empty() {
18142        short
18143    } else {
18144        format!("{}::{}", unit.package_name(), short)
18145    }
18146}
18147
18148/// Render an indexed C++ qualified name from its authoritative FqName
18149/// segments. Unlike the legacy `cpp_name_for` renderer, this preserves dots
18150/// that belong to a template argument (for example `Args...`).
18151fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
18152    let fq = unit.fq();
18153    if fq.is_empty() {
18154        return None;
18155    }
18156    let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
18157    Some(
18158        fq.segments()
18159            .iter()
18160            .map(|&segment| interner.resolve(segment).0)
18161            .collect::<Vec<_>>()
18162            .join("::"),
18163    )
18164}
18165
18166fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
18167    canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
18168        || unit.fq().is_empty() && cpp_name_for(unit) == expected
18169}
18170
18171/// Return the indexed C++ owner scope without reparsing its rendered name.
18172///
18173/// Template spellings are opaque within an indexed `FqName` segment.  In
18174/// particular, the ellipsis in a parameter pack (`Args...`) is part of the
18175/// `AtomicHook<...>` type segment; feeding the legacy all-`::` rendering back
18176/// through `parse_symbol_path` would mistake those dots for component
18177/// separators.  Cache-loaded/legacy units may still have an empty structured
18178/// name, so retain the parser only as that explicit fallback.
18179pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
18180    let fq = unit.fq();
18181    if !fq.is_empty() {
18182        let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
18183        let scope = fq
18184            .segments()
18185            .iter()
18186            .filter_map(|&segment| {
18187                let (text, kind) = interner.resolve(segment);
18188                matches!(
18189                    kind,
18190                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
18191                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
18192                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
18193                )
18194                .then(|| text.to_string())
18195            })
18196            .collect();
18197        return scope;
18198    }
18199    brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
18200        brokk_bifrost_core::analyzer::Language::Cpp,
18201        &cpp_name_for(unit),
18202    )
18203}
18204
18205// fqname-M4: the second stage splits on the individual chars '.', '-', '>'
18206// (not the substring "->"), which deliberately reduces an `operator->`-style
18207// terminal segment to an empty tail rather than keeping it intact; the shared
18208// structured splitter's cpp operator-token merge would keep `operator->`
18209// whole instead, changing this function's result — `name_matches_callable`'s
18210// `expected.starts_with("operator")` fallback exists specifically to
18211// compensate for that reduction, and a pinned regression test
18212// (`operator-> must not be reduced with terminal_name-style punctuation
18213// splitting`) asserts today's char-class behavior. Not equivalence-provable;
18214// revisit alongside that pinned test if it is ever relaxed.
18215pub fn terminal_name(value: &str) -> &str {
18216    value
18217        .rsplit("::")
18218        .next()
18219        .unwrap_or(value)
18220        .rsplit(['.', '-', '>'])
18221        .next()
18222        .unwrap_or(value)
18223        .trim()
18224}
18225
18226pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
18227    terminal_name(&normalize_cpp_reference_text(value)) == expected
18228}
18229
18230pub fn name_matches_callable(value: &str, expected: &str) -> bool {
18231    name_matches_terminal(value, expected)
18232        || expected.starts_with("operator")
18233            && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
18234}
18235
18236pub fn name_mentions(value: &str, expected: &str) -> bool {
18237    normalize_cpp_reference_text(value)
18238        .split("::")
18239        .any(|part| part == expected)
18240}
18241
18242pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
18243    let cpp_name = cpp_name_for(unit);
18244    if reference.contains("::") {
18245        return reference == cpp_name;
18246    }
18247    reference == cpp_name
18248        || terminal_name(reference) == unit.identifier()
18249            && (unit.package_name().is_empty() || reference == unit.identifier())
18250}
18251
18252pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
18253    match kind {
18254        TargetKind::Type
18255        | TargetKind::Constructor
18256        | TargetKind::Method
18257        | TargetKind::MemberField => true,
18258        TargetKind::FreeFunction => unit.is_function(),
18259        TargetKind::GlobalField => unit.is_field(),
18260        TargetKind::Macro => unit.is_macro(),
18261    }
18262}
18263
18264fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
18265    let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
18266    let target_name = cpp_name_for(target);
18267    if normalized.contains("::") {
18268        return normalized == target_name;
18269    }
18270    if let Some(namespace) = alias.namespace.as_deref() {
18271        return namespace_prefixes(namespace)
18272            .into_iter()
18273            .any(|prefix| format!("{prefix}::{normalized}") == target_name);
18274    }
18275    target.package_name().is_empty() && normalized == target.identifier()
18276}
18277
18278/// The declared return type text of a C++ function unit, with leading declaration specifiers
18279/// stripped, e.g. `T*` for `T* operator->()`.
18280pub fn cpp_function_return_type_text(
18281    analyzer: &CppGraphSource<'_>,
18282    function: &CodeUnit,
18283) -> Option<String> {
18284    let metadata = analyzer.signature_metadata(function);
18285    let first = metadata.first()?.return_type_text()?;
18286    metadata
18287        .iter()
18288        .all(|metadata| metadata.return_type_text() == Some(first))
18289        .then(|| first.to_owned())
18290}
18291
18292pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
18293    // fqname-M4: `cpp_name_for` is a bespoke all-`::` rendering of the unit's
18294    // name (it replaces every `.`/`$` in `short_name` with `::`), which is NOT
18295    // the same string `default_parent_fq_name`/`fq().parent()` would render:
18296    // the structured `FqName`'s native cpp display deliberately keeps `.` (not
18297    // `::`) between a trailing `Package` segment and a following `Type`
18298    // segment (see `separator` in `fq_name.rs`, landed for issue #1163), so
18299    // popping the unit's own `fq()` segment would NOT reproduce this
18300    // fully-`::`-joined string. Left as a split on the locally-built
18301    // all-colon string rather than the unit's structured name.
18302    cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
18303        namespace
18304            .strip_prefix("anonymous_namespace::")
18305            .unwrap_or(namespace)
18306            .to_string()
18307    })
18308}
18309
18310/// The namespace lookup tiers, from the innermost namespace outward.
18311pub fn namespace_prefixes(namespace: &str) -> Vec<String> {
18312    // `namespace` is built by `cpp_name_for`/`cpp_namespace_for` with every
18313    // non-`::` separator already converted to `::`, so re-tokenizing it with
18314    // the shared structured splitter and progressively popping the last
18315    // component reproduces the `rsplit_once("::")` outward walk exactly (same
18316    // shape as `cpp_qualifier_lookup_tiers`'s namespace-chain walk).
18317    let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
18318        brokk_bifrost_core::analyzer::Language::Cpp,
18319        namespace,
18320    );
18321    let mut prefixes = Vec::new();
18322    while !parts.is_empty() {
18323        prefixes.push(parts.join("::"));
18324        parts.pop();
18325    }
18326    prefixes
18327}
18328
18329fn nearest_namespace_candidates(
18330    candidates: Vec<CodeUnit>,
18331    normalized: &str,
18332    lexical_namespace: Option<&str>,
18333) -> Vec<CodeUnit> {
18334    if normalized.contains("::") {
18335        return candidates;
18336    }
18337    if let Some(namespace) = lexical_namespace {
18338        for prefix in namespace_prefixes(namespace) {
18339            let scoped = candidates
18340                .iter()
18341                .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
18342                .cloned()
18343                .collect::<Vec<_>>();
18344            if !scoped.is_empty() {
18345                return scoped;
18346            }
18347        }
18348    }
18349    candidates
18350        .into_iter()
18351        .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
18352        .collect()
18353}
18354
18355pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
18356    let mut namespaces = Vec::new();
18357    for parent in enclosing_cpp_namespace_nodes(node) {
18358        if let Some(name) = parent.child_by_field_name("name") {
18359            let namespace = normalize_cpp_reference_text(node_text(name, source));
18360            if !namespace.is_empty() {
18361                namespaces.push(namespace);
18362            }
18363        }
18364    }
18365    if namespaces.is_empty() {
18366        None
18367    } else {
18368        Some(namespaces.join("::"))
18369    }
18370}
18371
18372/// Like [`precise_parent_of`], but drops module (namespace) parents. A namespace is a scope, not a
18373/// type or receiver, so namespace-scoped functions and constants resolve as free functions and
18374/// globals rather than members.
18375pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
18376    type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
18377}
18378
18379fn type_owner_resolution(
18380    analyzer: &CppGraphSource<'_>,
18381    code_unit: &CodeUnit,
18382) -> Option<ResolvedTypeOwner> {
18383    precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
18384}
18385
18386fn target_type_owner_resolution(
18387    analyzer: &CppGraphSource<'_>,
18388    code_unit: &CodeUnit,
18389) -> Option<ResolvedTypeOwner> {
18390    match type_owner_resolution(analyzer, code_unit) {
18391        Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
18392        Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
18393    }
18394}
18395
18396/// Recover method identity for an indexed out-of-line definition when the
18397/// ordinary parent edge is absent. Prefer the unique include-visible forward
18398/// declaration, then classify exact-FQN class declarations elsewhere in the
18399/// workspace. A unique complete declaration wins; otherwise multiple forward
18400/// declarations are one owner only when they all share one logical identity.
18401/// The qualified callable FQN proves that owner spelling even when its defining
18402/// header is outside the scan file's include closure, while unknown or competing
18403/// complete declarations remain ambiguous.
18404/// This is deliberately target-only: canonical declaration resolution must
18405/// continue to prefer the callable definition rather than replacing it with
18406/// the recovered owner.
18407fn target_forward_owner_resolution(
18408    analyzer: &CppGraphSource<'_>,
18409    code_unit: &CodeUnit,
18410) -> Option<ResolvedTypeOwner> {
18411    if !code_unit.is_function() {
18412        return None;
18413    }
18414    // A top-level free function has no owner at all, and `FqName::parent`
18415    // answers the empty name rather than `None` for a one-segment identity.
18416    // `default_parent_fq_name`, which this replaced, filtered that case out;
18417    // asking the relational store for the empty name is a batch error that
18418    // fails the whole target frontier.
18419    let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
18420    let cpp = analyzer.cpp?;
18421    let mut visible_files = HashSet::default();
18422    collect_include_closure(
18423        analyzer,
18424        cpp.include_target_index(),
18425        code_unit.source(),
18426        &mut visible_files,
18427        None,
18428    );
18429    let candidates = analyzer.workspace_definitions().exact(&owner_name);
18430    let visible_candidates = candidates
18431        .iter()
18432        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
18433        .cloned()
18434        .collect::<Vec<_>>();
18435    match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
18436        DirectOwnerResolution::UniqueFull(unit) => {
18437            return Some(ResolvedTypeOwner {
18438                unit,
18439                is_forward_declaration: false,
18440            });
18441        }
18442        DirectOwnerResolution::ForwardsOnly(forwards) => {
18443            return (forwards.len() == 1).then(|| ResolvedTypeOwner {
18444                unit: forwards.into_iter().next().unwrap(),
18445                is_forward_declaration: true,
18446            });
18447        }
18448        DirectOwnerResolution::Ambiguous => return None,
18449        DirectOwnerResolution::None => {}
18450    }
18451
18452    let candidates = candidates
18453        .into_iter()
18454        .filter(|candidate| candidate.is_class())
18455        .collect::<Vec<_>>();
18456    let (unit, is_forward_declaration) =
18457        match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
18458            DirectOwnerResolution::UniqueFull(unit) => (unit, false),
18459            DirectOwnerResolution::ForwardsOnly(forwards) => {
18460                (unique_logical_forward_owner(forwards)?, true)
18461            }
18462            DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
18463        };
18464    Some(ResolvedTypeOwner {
18465        unit,
18466        is_forward_declaration,
18467    })
18468}
18469
18470pub fn precise_parent_of(
18471    analyzer: &CppGraphSource<'_>,
18472    visibility: &VisibilityIndex<'_>,
18473    code_unit: &CodeUnit,
18474) -> Option<CodeUnit> {
18475    visibility.cached_precise_parent_of(analyzer, code_unit)
18476}
18477
18478fn precise_parent_resolution(
18479    analyzer: &CppGraphSource<'_>,
18480    code_unit: &CodeUnit,
18481) -> Option<ResolvedTypeOwner> {
18482    #[cfg(any(test, feature = "test-support"))]
18483    if let Some(cpp) = analyzer.cpp {
18484        cpp.record_cpp_parent_resolution_for_test();
18485    }
18486    if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
18487        return Some(ResolvedTypeOwner {
18488            unit,
18489            is_forward_declaration: false,
18490        });
18491    }
18492    let fallback = analyzer.parent_of(code_unit);
18493    if !code_unit.owner_is_type_scope() {
18494        return fallback.map(|unit| ResolvedTypeOwner {
18495            unit,
18496            is_forward_declaration: false,
18497        });
18498    }
18499    let owner_fq = code_unit
18500        .fq()
18501        .parent()
18502        .expect("a unit with an owner identifier has a structured parent");
18503    let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
18504    match same_source_owner(analyzer, code_unit, &owner_candidates) {
18505        DirectOwnerResolution::UniqueFull(owner) => {
18506            return Some(ResolvedTypeOwner {
18507                unit: owner,
18508                is_forward_declaration: false,
18509            });
18510        }
18511        DirectOwnerResolution::Ambiguous => return None,
18512        DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
18513    }
18514    match directly_included_owner(analyzer, code_unit, &owner_candidates) {
18515        DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
18516            unit: owner,
18517            is_forward_declaration: false,
18518        }),
18519        DirectOwnerResolution::Ambiguous => None,
18520        DirectOwnerResolution::ForwardsOnly(forwards) => {
18521            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
18522                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
18523                    unit: owner,
18524                    is_forward_declaration: false,
18525                }),
18526                FullOwnerResolution::None => {
18527                    unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
18528                        unit,
18529                        is_forward_declaration: true,
18530                    })
18531                }
18532                FullOwnerResolution::Ambiguous => None,
18533            }
18534        }
18535        DirectOwnerResolution::None => {
18536            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
18537                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
18538                    unit: owner,
18539                    is_forward_declaration: false,
18540                }),
18541                FullOwnerResolution::Ambiguous => None,
18542                FullOwnerResolution::None => fallback
18543                    .filter(|parent| {
18544                        parent.source() == code_unit.source()
18545                            && parent.fq() == &owner_fq
18546                            && (!parent.is_class()
18547                                || cpp_class_declaration_strength(analyzer, parent)
18548                                    == CppClassDeclarationStrength::Full)
18549                    })
18550                    .map(|unit| ResolvedTypeOwner {
18551                        unit,
18552                        is_forward_declaration: false,
18553                    }),
18554            }
18555        }
18556    }
18557}
18558
18559fn exact_structural_type_parent(
18560    analyzer: &CppGraphSource<'_>,
18561    code_unit: &CodeUnit,
18562) -> Option<CodeUnit> {
18563    if !code_unit.is_function() && !code_unit.is_field() {
18564        return None;
18565    }
18566    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
18567    let cpp = analyzer.cpp?;
18568    let parent = cpp.structural_parent_of(code_unit)?;
18569    (!parent.is_module()
18570        && parent.source() == code_unit.source()
18571        && parent.package_name() == code_unit.package_name()
18572        && parent.short_name() == encoded_owner)
18573        .then_some(parent)
18574}
18575
18576fn same_source_owner(
18577    analyzer: &CppGraphSource<'_>,
18578    code_unit: &CodeUnit,
18579    owner_candidates: &[CodeUnit],
18580) -> DirectOwnerResolution {
18581    let candidates = owner_candidates
18582        .iter()
18583        .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
18584        .cloned()
18585        .collect::<Vec<_>>();
18586    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
18587    classify_direct_owner_candidates(analyzer, candidates.into_iter())
18588}
18589
18590fn visible_full_cpp_owner(
18591    analyzer: &CppGraphSource<'_>,
18592    code_unit: &CodeUnit,
18593    owner_candidates: &[CodeUnit],
18594) -> FullOwnerResolution {
18595    let Some(cpp) = analyzer.cpp else {
18596        return FullOwnerResolution::None;
18597    };
18598    let mut visible_files = HashSet::default();
18599    collect_include_closure(
18600        analyzer,
18601        cpp.include_target_index(),
18602        code_unit.source(),
18603        &mut visible_files,
18604        None,
18605    );
18606    let candidates = owner_candidates
18607        .iter()
18608        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
18609        .cloned()
18610        .collect::<Vec<_>>();
18611    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
18612    match collapse_owner_candidates(candidates.into_iter().map(|candidate| {
18613        let strength = cpp_class_declaration_strength(analyzer, &candidate);
18614        (candidate, strength)
18615    })) {
18616        DirectOwnerResolution::UniqueFull(owner) => FullOwnerResolution::Unique(owner),
18617        DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {
18618            FullOwnerResolution::None
18619        }
18620        DirectOwnerResolution::Ambiguous => FullOwnerResolution::Ambiguous,
18621    }
18622}
18623
18624pub enum DirectOwnerResolution {
18625    None,
18626    ForwardsOnly(Vec<CodeUnit>),
18627    UniqueFull(CodeUnit),
18628    Ambiguous,
18629}
18630
18631enum FullOwnerResolution {
18632    None,
18633    Unique(CodeUnit),
18634    Ambiguous,
18635}
18636
18637fn directly_included_owner(
18638    analyzer: &CppGraphSource<'_>,
18639    code_unit: &CodeUnit,
18640    owner_candidates: &[CodeUnit],
18641) -> DirectOwnerResolution {
18642    let Some(cpp) = analyzer.cpp else {
18643        return DirectOwnerResolution::None;
18644    };
18645    let direct_includes: HashSet<ProjectFile> = analyzer
18646        .canonical_include_paths(code_unit.source())
18647        .unwrap_or_default()
18648        .into_iter()
18649        .flat_map(|include| {
18650            resolve_include_targets_with_index(
18651                code_unit.source(),
18652                &include,
18653                cpp.include_target_index(),
18654            )
18655        })
18656        .collect();
18657    let candidates = owner_candidates
18658        .iter()
18659        .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
18660        .cloned()
18661        .collect::<Vec<_>>();
18662    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
18663    classify_direct_owner_candidates(analyzer, candidates.into_iter())
18664}
18665
18666fn prefer_member_declaring_owners(
18667    analyzer: &CppGraphSource<'_>,
18668    member: &CodeUnit,
18669    candidates: Vec<CodeUnit>,
18670) -> Vec<CodeUnit> {
18671    let matching = candidates
18672        .iter()
18673        .filter(|owner| owner_declares_member(analyzer, owner, member))
18674        .cloned()
18675        .collect::<Vec<_>>();
18676    if matching.is_empty() {
18677        candidates
18678    } else {
18679        matching
18680    }
18681}
18682
18683fn owner_declares_member(
18684    analyzer: &CppGraphSource<'_>,
18685    owner: &CodeUnit,
18686    member: &CodeUnit,
18687) -> bool {
18688    analyzer.direct_children(owner).into_iter().any(|child| {
18689        child.kind() == member.kind()
18690            && child.identifier() == member.identifier()
18691            && child.signature() == member.signature()
18692    })
18693}
18694
18695fn classify_direct_owner_candidates(
18696    analyzer: &CppGraphSource<'_>,
18697    candidates: impl Iterator<Item = CodeUnit>,
18698) -> DirectOwnerResolution {
18699    collapse_owner_candidates(candidates.map(|candidate| {
18700        let strength = cpp_class_declaration_strength(analyzer, &candidate);
18701        (candidate, strength)
18702    }))
18703}
18704
18705pub fn collapse_owner_candidates(
18706    candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
18707) -> DirectOwnerResolution {
18708    let mut full_definition = None;
18709    let mut forwards = Vec::new();
18710    for (candidate, strength) in candidates {
18711        match strength {
18712            CppClassDeclarationStrength::Full if full_definition.is_some() => {
18713                return DirectOwnerResolution::Ambiguous;
18714            }
18715            CppClassDeclarationStrength::Full => full_definition = Some(candidate),
18716            CppClassDeclarationStrength::Forward => forwards.push(candidate),
18717            CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
18718        }
18719    }
18720    if let Some(owner) = full_definition {
18721        DirectOwnerResolution::UniqueFull(owner)
18722    } else if !forwards.is_empty() {
18723        DirectOwnerResolution::ForwardsOnly(forwards)
18724    } else {
18725        DirectOwnerResolution::None
18726    }
18727}
18728
18729#[cfg(any(test, feature = "test-support"))]
18730pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
18731    unique_logical_forward_owner(forwards)
18732}
18733
18734fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
18735    let first = forwards.pop()?;
18736    forwards
18737        .iter()
18738        .all(|forward| same_logical_symbol(forward, &first))
18739        .then_some(first)
18740}
18741
18742pub fn cpp_class_declaration_strength(
18743    analyzer: &CppGraphSource<'_>,
18744    candidate: &CodeUnit,
18745) -> CppClassDeclarationStrength {
18746    // The answer is a pure function of the unit's ranges and its file's tree,
18747    // and the inverse scan asks it once per declaration seed. On a translation
18748    // unit the parser could not fully recover, each ask re-derives the
18749    // export-macro recovery shapes from the file's `ERROR` subtrees, so without
18750    // this memo one file's scan is quadratic in its own size: 97% of Catch2's
18751    // 284 s inverse scan of `extras/catch_amalgamated.cpp` was in this call
18752    // (#1496).
18753    let Some(cpp) = analyzer.cpp else {
18754        return uncached_cpp_class_declaration_strength(analyzer, candidate);
18755    };
18756    if let Some(strength) = cpp.cached_class_declaration_strength(candidate) {
18757        return strength;
18758    }
18759    let strength = uncached_cpp_class_declaration_strength(analyzer, candidate);
18760    cpp.cache_class_declaration_strength(candidate, strength);
18761    strength
18762}
18763
18764fn uncached_cpp_class_declaration_strength(
18765    analyzer: &CppGraphSource<'_>,
18766    candidate: &CodeUnit,
18767) -> CppClassDeclarationStrength {
18768    if let Some(facts) = analyzer.declaration_source_properties(candidate) {
18769        if facts
18770            .iter()
18771            .any(|fact| fact.class_strength == CppClassDeclarationStrength::Full)
18772        {
18773            return CppClassDeclarationStrength::Full;
18774        }
18775        if facts
18776            .iter()
18777            .any(|fact| fact.class_strength == CppClassDeclarationStrength::Forward)
18778        {
18779            return CppClassDeclarationStrength::Forward;
18780        }
18781    }
18782    if let Some(cpp) = analyzer.cpp
18783        && let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source())
18784    {
18785        return cpp_class_declaration_strength_in_tree(
18786            analyzer,
18787            &cpp.recovered_export_class_index(analyzer.token, candidate.source()),
18788            candidate,
18789            prepared.source(),
18790            prepared.tree().root_node(),
18791        );
18792    }
18793    let Some(source) = analyzer.indexed_source(candidate.source()) else {
18794        return CppClassDeclarationStrength::Unknown;
18795    };
18796    let mut parser = Parser::new();
18797    if parser
18798        .set_language(&tree_sitter_cpp::LANGUAGE.into())
18799        .is_err()
18800    {
18801        return CppClassDeclarationStrength::Unknown;
18802    }
18803    let Some(tree) = parser.parse(&source, None) else {
18804        return CppClassDeclarationStrength::Unknown;
18805    };
18806    // This branch reparses a file the analyzer has no prepared tree for, so its
18807    // recovery index is that one tree's and cannot be shared.
18808    let recovered_export_classes =
18809        CppRecoveredExportClassIndex::build(tree.root_node(), source.as_str());
18810    cpp_class_declaration_strength_in_tree(
18811        analyzer,
18812        &recovered_export_classes,
18813        candidate,
18814        &source,
18815        tree.root_node(),
18816    )
18817}
18818
18819fn cpp_class_declaration_strength_in_tree(
18820    analyzer: &CppGraphSource<'_>,
18821    recovered_export_classes: &CppRecoveredExportClassIndex,
18822    candidate: &CodeUnit,
18823    source: &str,
18824    root: Node<'_>,
18825) -> CppClassDeclarationStrength {
18826    let ranges = analyzer.ranges(candidate);
18827    let mut saw_forward = false;
18828    for range in ranges {
18829        // The recovered export-macro shapes answer for their own ranges; only a
18830        // range no recovery claims is read as a plain specifier.
18831        match recovered_class_body_at(
18832            recovered_export_classes,
18833            root,
18834            source,
18835            candidate.identifier(),
18836            &range,
18837        ) {
18838            Some(true) => return CppClassDeclarationStrength::Full,
18839            Some(false) => {
18840                saw_forward = true;
18841                continue;
18842            }
18843            None => {}
18844        }
18845        // Only a node covering the range's start byte can be the specifier for
18846        // this range, so apply that test where nodes enter the stack rather
18847        // than where they leave it. Pushing first meant one ask enqueued every
18848        // sibling at every level it descended, which on a translation unit with
18849        // thousands of top-level declarations is a per-ask cost proportional to
18850        // the file (#1496).
18851        let covers_range_start = |node: &Node<'_>| {
18852            node.start_byte() <= range.start_byte && node.end_byte() >= range.start_byte
18853        };
18854        let mut stack = Vec::new();
18855        if covers_range_start(&root) {
18856            stack.push(root);
18857        }
18858        while let Some(node) = stack.pop() {
18859            if node.start_byte() == range.start_byte
18860                && node.end_byte() == range.end_byte
18861                && matches!(
18862                    node.kind(),
18863                    "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
18864                )
18865            {
18866                if cpp_class_node_has_body(node) {
18867                    return CppClassDeclarationStrength::Full;
18868                }
18869                saw_forward = true;
18870            }
18871            let mut cursor = node.walk();
18872            stack.extend(node.named_children(&mut cursor).filter(covers_range_start));
18873        }
18874    }
18875    if saw_forward {
18876        CppClassDeclarationStrength::Forward
18877    } else {
18878        CppClassDeclarationStrength::Unknown
18879    }
18880}
18881
18882fn cpp_class_node_has_body(node: Node<'_>) -> bool {
18883    child_by_field_or_named_child_of_kinds(
18884        node,
18885        "body",
18886        &[
18887            "declaration_list",
18888            "field_declaration_list",
18889            "enumerator_list",
18890        ],
18891    )
18892    .is_some()
18893}
18894
18895#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18896enum CppCTagKind {
18897    Struct,
18898    Union,
18899}
18900
18901fn indexed_c_tag_kind(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CppCTagKind> {
18902    let declaration = analyzer.get_source(code_unit, false)?;
18903    let mut parser = Parser::new();
18904    parser
18905        .set_language(&tree_sitter_cpp::LANGUAGE.into())
18906        .ok()?;
18907    let tree = parser.parse(&declaration, None)?;
18908    let mut stack = vec![tree.root_node()];
18909    while let Some(node) = stack.pop() {
18910        let kind = match node.kind() {
18911            "struct_specifier" => CppCTagKind::Struct,
18912            "union_specifier" => CppCTagKind::Union,
18913            _ => {
18914                let mut cursor = node.walk();
18915                stack.extend(node.named_children(&mut cursor));
18916                continue;
18917            }
18918        };
18919        if node
18920            .child_by_field_name("name")
18921            .is_some_and(|name| node_text(name, &declaration) == code_unit.identifier())
18922        {
18923            return Some(kind);
18924        }
18925        let mut cursor = node.walk();
18926        stack.extend(node.named_children(&mut cursor));
18927    }
18928    None
18929}
18930
18931pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
18932    if !code_unit.owner_is_type_scope() {
18933        return None;
18934    }
18935    let owner_fq = code_unit.fq().parent()?;
18936    ctx.analyzer
18937        .workspace_definitions()
18938        .exact(&owner_fq)
18939        .into_iter()
18940        .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
18941}
18942
18943pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18944    same_logical_symbol(left, right) && left.source() == right.source()
18945}
18946
18947pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18948    same_symbol(left, right) || same_logical_symbol(left, right)
18949}
18950
18951pub fn same_visible_global_field_symbol(
18952    analyzer: &CppGraphSource<'_>,
18953    internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
18954    left: &CodeUnit,
18955    right: &CodeUnit,
18956) -> bool {
18957    if same_symbol(left, right) {
18958        return true;
18959    }
18960    if !same_logical_symbol(left, right) {
18961        return false;
18962    }
18963    if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
18964        || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
18965    {
18966        left.source() == right.source()
18967    } else {
18968        true
18969    }
18970}
18971
18972fn cpp_global_field_has_internal_linkage_cached(
18973    analyzer: &CppGraphSource<'_>,
18974    cache: &mut HashMap<CodeUnit, bool>,
18975    candidate: &CodeUnit,
18976) -> bool {
18977    if let Some(internal) = cache.get(candidate) {
18978        return *internal;
18979    }
18980    #[cfg(any(test, feature = "test-support"))]
18981    note_cpp_global_field_internal_linkage_classification_for_test();
18982    let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
18983    cache.insert(candidate.clone(), internal);
18984    internal
18985}
18986
18987#[cfg(any(test, feature = "test-support"))]
18988thread_local! {
18989    static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
18990}
18991
18992#[cfg(any(test, feature = "test-support"))]
18993fn note_cpp_global_field_internal_linkage_classification_for_test() {
18994    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
18995        count.set(count.get() + 1);
18996    });
18997}
18998
18999#[cfg(any(test, feature = "test-support"))]
19000pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
19001    body: impl FnOnce() -> T,
19002) -> (T, usize) {
19003    with_thread_local_counter_for_test(
19004        &CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST,
19005        body,
19006    )
19007}
19008
19009#[cfg(any(test, feature = "test-support"))]
19010fn with_thread_local_counter_for_test<T>(
19011    counter: &'static std::thread::LocalKey<Cell<usize>>,
19012    body: impl FnOnce() -> T,
19013) -> (T, usize) {
19014    counter.with(|count| {
19015        count.set(0);
19016        let result = body();
19017        let observed = count.get();
19018        count.set(0);
19019        (result, observed)
19020    })
19021}
19022
19023pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
19024    left.kind() == right.kind()
19025        && left.fq_name() == right.fq_name()
19026        && left.signature() == right.signature()
19027}
19028
19029/// Select one visible spelling from an alias family only when its members
19030/// agree on one structured target. A single candidate needs no target proof.
19031pub fn select_alias_spelling_with_agreed_targets<'a, T: Eq>(
19032    candidates: &[&'a CodeUnit],
19033    mut structured_target: impl FnMut(&CodeUnit) -> Option<T>,
19034) -> Option<&'a CodeUnit> {
19035    let first = *candidates.first()?;
19036    if candidates.len() == 1 {
19037        return Some(first);
19038    }
19039    let first_target = structured_target(first)?;
19040    candidates
19041        .iter()
19042        .skip(1)
19043        .all(|candidate| structured_target(candidate).is_some_and(|target| target == first_target))
19044        .then_some(first)
19045}
19046
19047pub fn cpp_global_field_has_internal_linkage(
19048    analyzer: &CppGraphSource<'_>,
19049    candidate: &CodeUnit,
19050) -> bool {
19051    if !candidate.is_field() || candidate.short_name().contains('.') {
19052        return false;
19053    }
19054    let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
19055        return false;
19056    };
19057    match local_linkage {
19058        CppFieldLinkage::Internal => true,
19059        CppFieldLinkage::External => false,
19060        CppFieldLinkage::InternalUnlessExternalPeer => {
19061            !cpp_global_field_linkage_peers(analyzer, candidate)
19062                .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
19063                .any(|linkage| matches!(linkage, CppFieldLinkage::External))
19064        }
19065    }
19066}
19067
19068fn cpp_global_field_linkage_peers<'a>(
19069    analyzer: &CppGraphSource<'a>,
19070    candidate: &'a CodeUnit,
19071) -> impl Iterator<Item = CodeUnit> + 'a {
19072    let name = candidate.fq().clone();
19073    analyzer
19074        .workspace_definitions()
19075        .exact(&name)
19076        .into_iter()
19077        .filter(move |peer| {
19078            if peer == candidate {
19079                return false;
19080            }
19081            #[cfg(any(test, feature = "test-support"))]
19082            note_cpp_global_field_linkage_peer_inspection_for_test();
19083            same_logical_symbol(peer, candidate)
19084        })
19085}
19086
19087#[cfg(any(test, feature = "test-support"))]
19088thread_local! {
19089    static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
19090}
19091
19092#[cfg(any(test, feature = "test-support"))]
19093fn note_cpp_global_field_linkage_peer_inspection_for_test() {
19094    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
19095        count.set(count.get() + 1);
19096    });
19097}
19098
19099#[cfg(any(test, feature = "test-support"))]
19100pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
19101    body: impl FnOnce() -> T,
19102) -> (T, usize) {
19103    with_thread_local_counter_for_test(&CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST, body)
19104}
19105
19106fn cpp_global_field_declaration_linkage(
19107    analyzer: &CppGraphSource<'_>,
19108    candidate: &CodeUnit,
19109) -> Option<CppFieldLinkage> {
19110    analyzer.cpp_field_linkage(candidate)
19111}
19112
19113/// Agreed alias target spelling captured from the declaring AST type field.
19114pub fn cpp_canonical_alias_target_text(
19115    analyzer: &CppGraphSource<'_>,
19116    unit: &CodeUnit,
19117) -> Option<String> {
19118    let facts = analyzer.declaration_source_properties(unit)?;
19119    let first_fact = facts.first()?;
19120    let first = first_fact.alias_target_text.as_ref()?;
19121    let normalized = normalize_cpp_whitespace(first);
19122    // Alias signatures collapse whitespace, so repeated declarations of one
19123    // unit can retain different raw spellings. Require structural agreement
19124    // too: whitespace inside a literal can change a type expression's value.
19125    facts
19126        .iter()
19127        .all(|fact| {
19128            fact.alias_target_text.as_ref().is_some_and(|target| {
19129                target == first
19130                    || (first_fact.alias_target.is_some()
19131                        && fact.alias_target == first_fact.alias_target
19132                        && normalize_cpp_whitespace(target) == normalized)
19133            })
19134        })
19135        .then(|| first.clone())
19136}
19137
19138#[cfg(test)]
19139mod tests {
19140
19141    #[test]
19142    fn resolver_displaced_conditional_grammar_boundary() {
19143        // With FLAG defined this is an ordinary array declaration. The
19144        // unpreprocessed CST swallows the directive into its array bound and
19145        // incorrectly keeps the following declaration under the conditional.
19146        let source = "#ifdef FLAG\nint values[\n#endif\n1];\nint outside;\n";
19147        let tree = parse_cpp(source);
19148        let conditional = tree.root_node().named_child(0).unwrap();
19149        assert_eq!(conditional.kind(), "preproc_ifdef");
19150        assert!(conditional.has_error());
19151        let boundary = cpp_displaced_preprocessor_boundary(conditional).unwrap();
19152        let directive = crate::grammar_test_support::nodes(conditional)
19153            .into_iter()
19154            .find(|node| node.kind() == "#endif" && !node.is_missing())
19155            .unwrap();
19156        assert_eq!(directive.parent().unwrap().kind(), "ERROR");
19157        assert_eq!(directive.end_byte(), boundary.end_byte);
19158        let inside = named_node_at(&tree, source, "values");
19159        let outside = named_node_at(&tree, source, "outside");
19160        assert!(has_ancestor_kind(inside, "preproc_ifdef"));
19161        assert!(has_ancestor_kind(outside, "preproc_ifdef"));
19162        assert!(inside.end_byte() < directive.start_byte());
19163        assert!(outside.start_byte() > directive.end_byte());
19164        assert!(preprocessor_conditional_contains_descendant(
19165            conditional,
19166            inside
19167        ));
19168        assert!(!preprocessor_conditional_contains_descendant(
19169            conditional,
19170            outside
19171        ));
19172    }
19173
19174    #[test]
19175    fn resolver_recovered_namespace_boundaries() {
19176        let source = STOLEN_BRACE_CASCADE;
19177        let tree = parse_cpp(source);
19178        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
19179        assert!(!index.regions.is_empty(), "fixture must enter recovery");
19180        for (needle, expected) in [
19181            ("First", vec!["app", "matchers", "detail"]),
19182            ("Second", vec!["app", "matchers", "detail"]),
19183            ("tail_function", vec!["app", "matchers"]),
19184            ("TailClass", vec!["app", "matchers"]),
19185            ("AfterAll", vec![]),
19186        ] {
19187            let node = named_node_at(&tree, source, needle);
19188            let mut parsed: Vec<_> = std::iter::successors(node.parent(), |node| node.parent())
19189                .filter(|node| node.kind() == "namespace_definition")
19190                .filter_map(|namespace| {
19191                    let name = namespace.child_by_field_name("name")?;
19192                    let mut components = Vec::new();
19193                    append_cpp_name_components(name, source, &mut components)?;
19194                    Some((
19195                        namespace.start_byte(),
19196                        components,
19197                        namespace_definition_has_real_braces(namespace),
19198                    ))
19199                })
19200                .collect();
19201            parsed.reverse();
19202            assert_eq!(
19203                index.restore_enclosing_namespaces(parsed, node.start_byte()),
19204                expected,
19205                "{needle}"
19206            );
19207        }
19208        // A range owns its bytes, not the first byte following the range.
19209        for region in &index.regions {
19210            assert!(region.start < region.end);
19211            assert!(index.region_at(region.start).is_some());
19212            assert!(
19213                index
19214                    .region_at(region.end)
19215                    .is_none_or(|found| !std::ptr::eq(found, region)),
19216                "region leaked beyond its half-open boundary: {region:?}"
19217            );
19218        }
19219        // Real recovery ERRORs can carry a close and additional tokens. The
19220        // close still terminates lexical scope; it need not be the only token.
19221        let errors: Vec<_> = crate::grammar_test_support::nodes(tree.root_node())
19222            .into_iter()
19223            .filter(|node| node.kind() == "ERROR")
19224            .filter(|node| {
19225                let mut cursor = node.walk();
19226                node.children(&mut cursor).any(|child| child.kind() == "}")
19227            })
19228            .collect();
19229        assert!(
19230            !errors.is_empty(),
19231            "fixture must retain unmatched close tokens"
19232        );
19233        for error in errors {
19234            assert!(direct_unmatched_closing_brace(error), "{error:?}");
19235        }
19236    }
19237
19238    // These tables pin the fields and recovery tokens supplied by the released
19239    // grammar, alongside the role that the resolver reads at that boundary.
19240    #[test]
19241    fn resolver_declarator_grammar_boundary_table() {
19242        for (source, name, kind, binder) in [
19243            ("int first, values[3];", "values", "identifier", true),
19244            (
19245                "void f() { int first, values[3]; }",
19246                "values",
19247                "identifier",
19248                true,
19249            ),
19250            (
19251                "typedef unsigned Underlying; enum Named : Underlying { Value };",
19252                "Underlying {",
19253                "type_identifier",
19254                false,
19255            ),
19256        ] {
19257            let tree = parse_cpp(source);
19258            assert!(!tree.root_node().has_error(), "{source}");
19259            let spelling = name.split_whitespace().next().unwrap();
19260            let start = source.find(name).unwrap();
19261            let node = tree
19262                .root_node()
19263                .named_descendant_for_byte_range(start, start + spelling.len())
19264                .unwrap();
19265            assert_eq!(node.kind(), kind, "{source}");
19266            if spelling == "values" {
19267                let array = node.parent().unwrap();
19268                assert_eq!(array.kind(), "array_declarator");
19269                assert_eq!(array.child_by_field_name("declarator"), Some(node));
19270                assert!(is_declarator_node(array), "{source}");
19271            } else {
19272                let enumeration = node.parent().unwrap();
19273                assert_eq!(enumeration.kind(), "enum_specifier");
19274                assert_eq!(enumeration.child_by_field_name("base"), Some(node));
19275                assert_eq!(
19276                    enumeration.child_by_field_name("name").is_some(),
19277                    source.contains("enum Named")
19278                );
19279            }
19280            assert_eq!(is_declaration_name(node), binder, "{source}");
19281        }
19282    }
19283
19284    #[test]
19285    fn resolver_alias_scope_and_argument_grammar_table() {
19286        for (source, scope) in [
19287            ("template<class T> using Alias = T;", true),
19288            ("namespace n { template<class T> using Alias = T; }", true),
19289            (
19290                "struct Owner { template<class T> using Alias = T; };",
19291                false,
19292            ),
19293            ("void f() { using Alias = int; }", false),
19294        ] {
19295            let tree = parse_cpp(source);
19296            assert!(!tree.root_node().has_error(), "{source}");
19297            let alias = crate::grammar_test_support::nodes(tree.root_node())
19298                .into_iter()
19299                .find(|node| node.kind() == "alias_declaration")
19300                .unwrap();
19301            assert_eq!(
19302                node_text(alias.child_by_field_name("name").unwrap(), source),
19303                "Alias"
19304            );
19305            assert!(alias.child_by_field_name("type").is_some());
19306            assert_eq!(alias_has_visible_file_scope(alias), scope, "{source}");
19307        }
19308        for (source, argument_kinds, expression) in [
19309            (
19310                "ns::Alias value(42, arg);",
19311                vec!["number_literal", "identifier"],
19312                true,
19313            ),
19314            (
19315                "ns::Alias value(42, 7);",
19316                vec!["number_literal", "number_literal"],
19317                false,
19318            ),
19319            ("ns::Alias value();", vec![], false),
19320        ] {
19321            let tree = parse_cpp(source);
19322            assert!(!tree.root_node().has_error(), "{source}");
19323            let declaration = tree.root_node().named_child(0).unwrap();
19324            let ty = declaration.child_by_field_name("type").unwrap();
19325            assert_eq!(node_text(ty, source), "ns::Alias");
19326            let argument_lists: Vec<_> = crate::grammar_test_support::nodes(declaration)
19327                .into_iter()
19328                .filter(|node| node.kind() == "argument_list")
19329                .collect();
19330            let kinds: Vec<_> = argument_lists
19331                .iter()
19332                .flat_map(|node| {
19333                    let mut cursor = node.walk();
19334                    node.named_children(&mut cursor)
19335                        .map(|child| child.kind())
19336                        .collect::<Vec<_>>()
19337                })
19338                .collect();
19339            assert_eq!(kinds, argument_kinds, "{source}");
19340            assert_eq!(
19341                qualified_alias_constructor_has_expression_argument(ty),
19342                expression,
19343                "{source}"
19344            );
19345        }
19346    }
19347
19348    #[test]
19349    fn resolver_namespace_brace_grammar_table() {
19350        for (source, complete, closing_missing) in [
19351            ("namespace n { int item; }", true, false),
19352            ("namespace n { int item;", false, true),
19353        ] {
19354            let tree = parse_cpp(source);
19355            let namespace = tree.root_node().named_child(0).unwrap();
19356            assert_eq!(namespace.kind(), "namespace_definition");
19357            let body = namespace.child_by_field_name("body").unwrap();
19358            assert_eq!(body.kind(), "declaration_list");
19359            let open = body.child(0).unwrap();
19360            let close = body.child(body.child_count() - 1).unwrap();
19361            assert_eq!(open.kind(), "{");
19362            assert!(!open.is_missing());
19363            assert_eq!(close.kind(), "}");
19364            assert_eq!(close.is_missing(), closing_missing);
19365            assert_eq!(
19366                namespace_definition_has_real_braces(namespace),
19367                complete,
19368                "{source}"
19369            );
19370        }
19371        let source = "namespace { namespace detail { struct Derived {}; void run() {} } }";
19372        let tree = parse_cpp(source);
19373        assert!(!tree.root_node().has_error());
19374        let anonymous = tree.root_node().named_child(0).unwrap();
19375        assert_eq!(anonymous.kind(), "namespace_definition");
19376        assert!(anonymous.child_by_field_name("name").is_none());
19377        let run = named_node_at(&tree, source, "run");
19378        assert_eq!(
19379            enclosing_namespace_components(run, source),
19380            Some(vec!["detail".to_owned()])
19381        );
19382    }
19383
19384    #[test]
19385    fn resolver_c_block_envelope_grammar_table() {
19386        for (source, name, damaged, recovered) in [
19387            ("void outer(void) { nested() {} }", "nested", false, false),
19388            (
19389                "void outer(void) { Apply(first, ^(Bucket b) { return b.value; }); }",
19390                "Apply",
19391                true,
19392                true,
19393            ),
19394        ] {
19395            let tree = parse_cpp(source);
19396            let node = named_node_at(&tree, source, name);
19397            let declarator = node.parent().unwrap();
19398            assert_eq!(
19399                declarator.kind(),
19400                "function_declarator",
19401                "{source}: {:?}",
19402                tree.root_node()
19403            );
19404            assert_eq!(declarator.child_by_field_name("declarator"), Some(node));
19405            assert_eq!(declarator.has_error(), damaged, "{source}");
19406            let definition = declarator.parent().unwrap();
19407            assert_eq!(definition.kind(), "function_definition");
19408            assert!(definition.child_by_field_name("type").is_none());
19409            assert!(definition.child_by_field_name("body").is_some());
19410            assert_eq!(
19411                recovered_c_block_call_envelope(declarator).is_some(),
19412                recovered,
19413                "{source}"
19414            );
19415        }
19416    }
19417
19418    #[test]
19419    fn resolver_linkage_guard_grammar_table() {
19420        for (source, recovered, visible) in [
19421            (
19422                "#ifdef __cplusplus\nextern \"C\" {\n#endif\nvoid target();\n#ifdef __cplusplus\n}\n#endif\n",
19423                false,
19424                true,
19425            ),
19426            (
19427                "#ifdef __cplusplus\nextern \"C\" {\nvoid target();\n#ifdef __cplusplus\n}\n#endif\n",
19428                true,
19429                false,
19430            ),
19431            (
19432                "#ifdef __cplusplus\nextern \"C\" {\nvoid target();\n}\n#endif\n",
19433                true,
19434                false,
19435            ),
19436        ] {
19437            let tree = parse_cpp(source);
19438            let name = named_node_at(&tree, source, "target");
19439            let mut declaration = name;
19440            while declaration.kind() != "declaration" {
19441                declaration = declaration.parent().unwrap();
19442            }
19443            assert_eq!(
19444                has_ancestor_kind(declaration, "linkage_specification"),
19445                recovered,
19446                "{source}"
19447            );
19448            assert_eq!(
19449                has_ancestor_kind(declaration, "preproc_ifdef"),
19450                recovered,
19451                "{source}"
19452            );
19453            if !recovered {
19454                assert!(!tree.root_node().has_error());
19455                let open = tree.root_node().named_child(0).unwrap();
19456                assert_eq!(open.kind(), "preproc_linkage_open");
19457                assert!(open.child_by_field_name("value").is_some());
19458            }
19459            assert_eq!(
19460                callable_preprocessor_context_is_visible(declaration, source),
19461                visible,
19462                "{source}"
19463            );
19464        }
19465    }
19466
19467    #[test]
19468    #[hardwon("#6902: grammar 0.24.4 parsed an unnamed enum's base as its name")]
19469    fn resolver_anonymous_enum_base_grammar_boundary() {
19470        let source = "typedef unsigned Underlying; enum : Underlying { Value };";
19471        let tree = parse_cpp(source);
19472        assert!(
19473            !tree.root_node().has_error(),
19474            "{}",
19475            tree.root_node().to_sexp()
19476        );
19477        let start = source.find("Underlying {").unwrap();
19478        let base = tree
19479            .root_node()
19480            .named_descendant_for_byte_range(start, start + "Underlying".len())
19481            .unwrap();
19482        let enumeration = base.parent().unwrap();
19483        assert_eq!(enumeration.kind(), "enum_specifier");
19484        assert!(enumeration.child_by_field_name("name").is_none());
19485        assert_eq!(enumeration.child_by_field_name("base"), Some(base));
19486        assert!(!is_declaration_name(base));
19487    }
19488
19489    #[test]
19490    #[hardwon(
19491        "db00de0653f1: quicksilver round 4 on brokk-bifrost-cpp: spliced macro tokens need physical source coordinates"
19492    )]
19493    fn macro_splices_preserve_statement_origin_ranges() {
19494        for newline in ["\n", "\r\n"] {
19495            for (replacement, name, written) in [
19496                (
19497                    format!("jo\\{newline}ined\\{newline}();\\{newline}after();"),
19498                    "joined",
19499                    format!("jo\\{newline}ined"),
19500                ),
19501                (
19502                    format!("do {{ if (ready) bl\\{newline}ock }} while (0)"),
19503                    "block",
19504                    format!("bl\\{newline}ock"),
19505                ),
19506            ] {
19507                let parameters = vec!["block".to_owned()];
19508                let (source, tree, original_offsets) =
19509                    VisibilityIndex::parse_macro_replacement_body(&replacement, &parameters)
19510                        .expect("joined replacement parses");
19511                let body = ParsedReplacementBody {
19512                    source,
19513                    tree,
19514                    body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
19515                    parameters,
19516                    original_offsets,
19517                };
19518                let nodes = crate::grammar_test_support::nodes(body.tree.root_node());
19519                let matching: Vec<_> = nodes
19520                    .iter()
19521                    .filter(|node| {
19522                        node.kind() == "identifier" && node_text(**node, &body.source) == name
19523                    })
19524                    .collect();
19525                assert_eq!(matching.len(), 1, "{replacement}");
19526                let range = body.file_range(*matching[0], 0);
19527                assert_eq!(&replacement[range], written);
19528                for node in nodes.iter().filter(|node| {
19529                    node.kind() == "identifier" && node_text(**node, &body.source) == "after"
19530                }) {
19531                    assert_eq!(&replacement[body.file_range(*node, 0)], "after");
19532                }
19533            }
19534        }
19535    }
19536
19537    #[test]
19538    #[hardwon("05fbd05c6bd7: C++ declaration recovery regressed for this structured syntax edge")]
19539    fn issue_3089_statement_formal_at_end_of_replacement() {
19540        let parameters = vec!["handle".to_owned(), "block".to_owned()];
19541        for replacement in [
19542            "do { header_event_t* event; if ((handle)->active) block } while (0)",
19543            "do { header_event_t* event; block } while (0)",
19544        ] {
19545            assert!(
19546                super::VisibilityIndex::parse_macro_replacement_body(replacement, &parameters)
19547                    .is_some(),
19548                "{replacement}"
19549            );
19550        }
19551    }
19552    use super::*;
19553    use brokk_bifrost_test_macros::hardwon;
19554
19555    #[test]
19556    #[hardwon("2e5169325a14: C++ declaration recovery regressed for this structured syntax edge")]
19557    fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
19558        let source = "int size(void) { return sizeof(((Payload))); }\n";
19559        let mut parser = Parser::new();
19560        parser
19561            .set_language(&tree_sitter_cpp::LANGUAGE.into())
19562            .expect("C++ grammar");
19563        let tree = parser.parse(source, None).expect("fixture tree");
19564        let start = source.find("Payload").expect("sizeof operand");
19565        let node = tree
19566            .root_node()
19567            .named_descendant_for_byte_range(start, start + "Payload".len())
19568            .expect("focused operand");
19569        let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
19570        let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
19571
19572        assert_eq!(node.kind(), "identifier");
19573        assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
19574        assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
19575    }
19576
19577    #[test]
19578    fn recovered_macro_actuals_keep_their_cst_boundaries() {
19579        for (source, recovered) in [
19580            ("int f() { return M(Type, value); }", false),
19581            ("int f() { return M(Type, ^(int n){ return n; }); }", true),
19582        ] {
19583            let tree = parse_cpp(source);
19584            let first = named_node_at(&tree, source, "Type");
19585            let (call, index, actual) = c_function_macro_argument(first)
19586                .unwrap_or_else(|| panic!("first actual {source}: {}", tree.root_node().to_sexp()));
19587            assert_eq!(index, 0);
19588            assert_eq!(node_text(actual, source), "Type");
19589            let arguments = call.child_by_field_name("arguments").unwrap();
19590            let raw = arguments.named_child(0).unwrap();
19591            if recovered {
19592                assert_eq!(raw.kind(), "binary_expression");
19593                assert_eq!(raw.child_by_field_name("left"), Some(actual));
19594                let inside = named_node_at(&tree, source, "return n;");
19595                let (_, index, block) = c_function_macro_argument(inside).expect("block actual");
19596                assert_eq!(index, 1);
19597                assert_eq!(block.kind(), "compound_literal_expression");
19598                assert_eq!(node_text(block, source), "(int n){ return n; }");
19599                assert_eq!(raw.child_by_field_name("right"), Some(block));
19600            } else {
19601                assert_eq!(raw, actual);
19602                let second = named_node_at(&tree, source, "value");
19603                let (_, index, actual) = c_function_macro_argument(second).expect("second actual");
19604                assert_eq!(index, 1);
19605                assert_eq!(node_text(actual, source), "value");
19606            }
19607        }
19608    }
19609
19610    fn parse_cpp(source: &str) -> Tree {
19611        crate::diagnostics::parse_cpp_tree(source)
19612    }
19613
19614    #[test]
19615    #[hardwon("#4271: source splitting included a new-array declarator in the allocation type")]
19616    fn cpp_new_expression_type_comes_from_the_structured_type_field() {
19617        let source = "auto value = new  ns::Box<int> [ 3 ];";
19618        let tree = parse_cpp(source);
19619        let mut pending = vec![tree.root_node()];
19620        let allocation = loop {
19621            let node = pending.pop().expect("new expression in fixture");
19622            if node.kind() == "new_expression" {
19623                break node;
19624            }
19625            let mut cursor = node.walk();
19626            pending.extend(
19627                node.named_children(&mut cursor)
19628                    .collect::<Vec<_>>()
19629                    .into_iter()
19630                    .rev(),
19631            );
19632        };
19633
19634        let type_node = cpp_new_expression_type_node(allocation).expect("structured type field");
19635        assert_eq!(allocation.child_by_field_name("type"), Some(type_node));
19636        assert_eq!(node_text(type_node, source), "ns::Box<int>");
19637        assert_eq!(
19638            allocation
19639                .child_by_field_name("declarator")
19640                .expect("array declarator")
19641                .kind(),
19642            "new_declarator"
19643        );
19644
19645        let old_split = normalize_cpp_whitespace(node_text(allocation, source));
19646        let old_split = old_split
19647            .strip_prefix("new ")
19648            .unwrap_or(old_split.as_str())
19649            .split(['(', '{'])
19650            .next()
19651            .expect("old split result");
19652        assert_ne!(old_split.trim(), node_text(type_node, source));
19653    }
19654
19655    fn named_node_at<'tree>(tree: &'tree Tree, source: &str, needle: &str) -> Node<'tree> {
19656        let start = source.find(needle).expect("fixture needle");
19657        tree.root_node()
19658            .named_descendant_for_byte_range(start, start + needle.len())
19659            .expect("node at needle")
19660    }
19661
19662    fn prepared_cpp(source: &str) -> PreparedSyntaxTree {
19663        let tree = crate::diagnostics::parse_cpp_tree(source);
19664        PreparedSyntaxTree::new(
19665            PreparedSyntaxSource::Exact(Arc::from(source)),
19666            tree,
19667            compute_line_starts(source),
19668            LanguageDialect::Standard(Language::Cpp),
19669            PreparedSourceOrigin::Disk,
19670            None,
19671        )
19672    }
19673
19674    fn unresolved_include_before(source: &str, reference: &str) -> bool {
19675        let file = ProjectFile::new(std::env::temp_dir(), "issue-3078.cpp");
19676        let prepared = prepared_cpp(source);
19677        let facts = collect_structured_include_facts(&prepared);
19678        let include_targets = IncludeTargetIndex::build([&file]);
19679        has_unresolved_include_visible_before_in_prepared(
19680            &file,
19681            &prepared,
19682            &include_targets,
19683            &facts,
19684            source.find(reference).expect("reference fixture"),
19685        )
19686    }
19687
19688    #[test]
19689    #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
19690    fn unresolved_include_before_reference_is_visible() {
19691        let source = "#include \"missing.h\"\nint use = Missing;\n";
19692        assert!(unresolved_include_before(source, "Missing"));
19693    }
19694
19695    #[test]
19696    #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
19697    fn unresolved_include_after_reference_is_not_visible() {
19698        let source = "int use = Missing;\n#include \"missing.h\"\n";
19699        assert!(!unresolved_include_before(source, "Missing"));
19700    }
19701
19702    #[test]
19703    #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
19704    fn unresolved_include_in_incompatible_sibling_branch_is_not_visible() {
19705        let source = "#if FEATURE\n#include \"missing.h\"\n#else\nint use = Missing;\n#endif\n";
19706        assert!(!unresolved_include_before(source, "Missing"));
19707    }
19708
19709    #[test]
19710    #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
19711    fn unresolved_include_in_current_branch_is_visible() {
19712        let source =
19713            "#if FEATURE\n#include \"missing.h\"\nint use = Missing;\n#else\nint other;\n#endif\n";
19714        assert!(unresolved_include_before(source, "Missing"));
19715    }
19716
19717    /// Two macro-decorated class heads make tree-sitter close `detail` at the
19718    /// first class's `}`, `matchers` at the second's, and `app` at `detail`'s
19719    /// real `}`; the tail parses at translation-unit level and the two real
19720    /// closes for `matchers` and `app` land in a trailing ERROR (#1537).
19721    const STOLEN_BRACE_CASCADE: &str = r#"namespace app {
19722namespace matchers {
19723    namespace detail {
19724        class API [[nodiscard]] First {
19725        public:
19726            int value() const { return count_ + 1; }
19727        private:
19728            int count_;
19729        };
19730        class API [[nodiscard]] Second {
19731        public:
19732            int value() const { return count_ + 2; }
19733        private:
19734            int count_;
19735        };
19736    } // namespace detail
19737
19738    template <typename T>
19739    void tail_function(MatcherBase<T> const& value);
19740
19741    class TailClass {};
19742} // namespace matchers
19743} // namespace app
19744
19745struct AfterAll {};
19746"#;
19747
19748    #[test]
19749    #[hardwon("c820d8c6a8e3: C++ declaration recovery regressed for this structured syntax edge")]
19750    fn orphaned_namespace_scope_index_restores_a_stolen_brace_cascade() {
19751        let source = STOLEN_BRACE_CASCADE;
19752        let tree = parse_cpp(source);
19753        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
19754
19755        let tail_class = named_node_at(&tree, source, "TailClass");
19756        assert!(
19757            !has_ancestor_kind(tail_class, "namespace_definition"),
19758            "the fixture must reproduce the recovery: the tail has no namespace ancestor"
19759        );
19760        let displaced = named_node_at(&tree, source, "Second");
19761        assert_eq!(
19762            enclosing_namespace_components(displaced, source),
19763            Some(vec!["app".to_string(), "matchers".to_string()]),
19764            "the fixture must displace the second class out of detail"
19765        );
19766
19767        let components = |needle: &str| {
19768            index.enclosing_namespace_components(named_node_at(&tree, source, needle), source)
19769        };
19770        assert_eq!(components("First"), ["app", "matchers", "detail"]);
19771        assert_eq!(components("Second"), ["app", "matchers", "detail"]);
19772        assert_eq!(components("MatcherBase<T>"), ["app", "matchers"]);
19773        assert_eq!(components("tail_function"), ["app", "matchers"]);
19774        assert_eq!(components("TailClass"), ["app", "matchers"]);
19775        assert!(components("AfterAll").is_empty());
19776    }
19777
19778    #[test]
19779    #[hardwon("c820d8c6a8e3: C++ declaration recovery regressed for this structured syntax edge")]
19780    fn orphaned_namespace_scope_index_is_empty_without_lost_scopes() {
19781        let clean = "namespace a { namespace b { class C {}; } class D {}; }\n";
19782        let tree = parse_cpp(clean);
19783        assert!(!tree.root_node().has_error());
19784        assert!(OrphanedNamespaceScopeIndex::build(tree.root_node(), clean).is_empty());
19785
19786        // A namespace that merely contains a parse error closes where its
19787        // brace says; the declarations after it keep their parsed scope.
19788        let damaged = "namespace a { namespace b { UNKNOWN_MACRO(x) } class C {}; }\n";
19789        let tree = parse_cpp(damaged);
19790        assert!(tree.root_node().has_error());
19791        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), damaged);
19792        assert_eq!(
19793            index.enclosing_namespace_components(named_node_at(&tree, damaged, "class C"), damaged),
19794            ["a"]
19795        );
19796    }
19797
19798    /// A function-like `#define` in a class body, whose replacement returns a
19799    /// SFINAE type, makes tree-sitter give up on the whole enclosing namespace:
19800    /// `namespace app {` becomes an `ERROR` whose own `namespace`, name and `{`
19801    /// tokens are the only trace of the head, and every declaration the
19802    /// namespace holds becomes their flat sibling. This is Catch2's
19803    /// `catch_decomposer.hpp`, reduced (issue #3084).
19804    const COLLAPSED_NAMESPACE_HEAD: &str = r#"namespace app {
19805
19806    class Target {
19807        int value_;
19808    };
19809
19810    template <typename T>
19811    class Holder {
19812    public:
19813        explicit constexpr Holder( T lhs ): m_lhs( lhs ) {}
19814
19815#define HOLDER_DEFINE_OP( id, op )                                             \
19816    template <typename U>                                                      \
19817    constexpr friend auto operator op( Holder&& lhs, U&& rhs )                 \
19818        -> std::enable_if_t<is_##id##_comparable<T, U>::value, Target> {       \
19819        return Target{};                                                       \
19820    }
19821
19822        HOLDER_DEFINE_OP( equal, == )
19823#undef HOLDER_DEFINE_OP
19824        T m_lhs;
19825    };
19826
19827    class Tail {};
19828}
19829"#;
19830
19831    #[test]
19832    #[hardwon("a438f125e172: C++ declaration recovery regressed for this structured syntax edge")]
19833    fn orphaned_namespace_scope_index_names_a_collapsed_namespace_head() {
19834        let source = COLLAPSED_NAMESPACE_HEAD;
19835        let tree = parse_cpp(source);
19836        let target = named_node_at(&tree, source, "class Target");
19837
19838        // The macro-declaration grammar keeps the namespace head and its
19839        // members inside a surviving `namespace_definition` even with the
19840        // macro invocation shredding one declaration inside the body, so the
19841        // scope authority answers from parsed ancestry; the recovered-region
19842        // path this fixture exercised is no longer needed for it.
19843        assert!(
19844            tree.root_node().has_error(),
19845            "the fixture must retain the macro invocation's parse damage"
19846        );
19847        assert!(
19848            has_ancestor_kind(target, "namespace_definition"),
19849            "the namespace head survives: the class keeps its parsed namespace ancestor"
19850        );
19851
19852        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
19853        assert_eq!(
19854            index.enclosing_namespace_components(target, source),
19855            ["app"]
19856        );
19857        assert_eq!(
19858            index.enclosing_namespace_levels(target, source),
19859            vec![(
19860                "app".to_string(),
19861                Range {
19862                    start_byte: 0,
19863                    end_byte: source.len() - 1,
19864                    start_line: 1,
19865                    end_line: 25,
19866                },
19867                Range {
19868                    start_byte: 10,
19869                    end_byte: 13,
19870                    start_line: 1,
19871                    end_line: 1,
19872                },
19873            )],
19874            "the recovered namespace owns its full construct, not its first member or just its header"
19875        );
19876    }
19877
19878    #[test]
19879    #[hardwon("#5868: a literal namespace head inside a damaged function wrapper lost its owner")]
19880    fn namespace_body_name_components_recovers_literal_function_head() {
19881        // Independently authored direct-helper contract, not an index or
19882        // short-body reproduction. The index deliberately leaves clean
19883        // subtrees intact; head recognition alone does not make this simple
19884        // body an indexed recovery region. Corpus index/short-body/outside
19885        // ownership remains required by the private root golden prewriter.
19886        let source = "OPAQUE_PREFIX\nnamespace recovered\n{\nint member;\n}\nint member;\n";
19887        let tree = parse_cpp(source);
19888        let keyword_start = source.find("namespace recovered").unwrap();
19889        let keyword = tree
19890            .root_node()
19891            .named_descendant_for_byte_range(keyword_start, keyword_start + "namespace".len())
19892            .expect("literal keyword node");
19893        // The macro-declaration grammar reads the opaque prefix as a
19894        // `macro_modifier` and the literal `namespace` keyword as the bogus
19895        // function's type: a keyword cannot be a type, so the type field is
19896        // the same recovery evidence the older grammar's `ERROR` carried.
19897        assert_eq!(keyword.kind(), "type_identifier");
19898        assert_eq!(keyword.child_count(), 0);
19899        assert!(!keyword.is_missing() && !keyword.has_error());
19900        assert_eq!(node_text(keyword, source), "namespace");
19901        let wrapper = keyword.parent().expect("actual wrapper");
19902        assert_eq!(wrapper.kind(), "function_definition");
19903        let mut cursor = wrapper.walk();
19904        let children = wrapper
19905            .children(&mut cursor)
19906            .filter(|child| child.kind() != "comment")
19907            .collect::<Vec<_>>();
19908        assert_eq!(
19909            children.len(),
19910            4,
19911            "all named and unnamed children accounted: {children:?}"
19912        );
19913        let prefix = children[0];
19914        assert_eq!(prefix.kind(), "macro_modifier");
19915        assert!(!prefix.is_missing() && !prefix.has_error());
19916        assert_eq!(node_text(prefix, source), "OPAQUE_PREFIX");
19917        assert_eq!(children[1], keyword);
19918        assert_eq!(wrapper.child_by_field_name("type"), Some(keyword));
19919        let name = children[2];
19920        assert!(matches!(name.kind(), "identifier" | "namespace_identifier"));
19921        assert_eq!(name.child_count(), 0);
19922        assert!(!name.is_missing() && !name.has_error());
19923        assert_eq!(node_text(name, source), "recovered");
19924        assert_eq!(wrapper.child_by_field_name("declarator"), Some(name));
19925        let body = wrapper.child_by_field_name("body").expect("actual body");
19926        assert_eq!(children[3], body);
19927        assert_eq!(body.kind(), "compound_statement");
19928        assert!(!body.is_missing());
19929        let open = body.child(0).expect("actual first child");
19930        assert_eq!(open.kind(), "{");
19931        assert!(!open.is_missing() && !open.has_error());
19932        assert_eq!(open.start_byte(), body.start_byte());
19933        let (head, names) = namespace_body_name_components(wrapper, body, source)
19934            .expect("strict synthetic literal head recognized");
19935        assert_eq!(head, keyword);
19936        assert_eq!(
19937            names
19938                .iter()
19939                .map(|(name, _)| name.as_str())
19940                .collect::<Vec<_>>(),
19941            ["recovered"]
19942        );
19943        assert_eq!(
19944            (names[0].1.start_byte, names[0].1.end_byte),
19945            (name.start_byte(), name.end_byte())
19946        );
19947        let inside = named_node_at(&tree, source, "int member;");
19948        assert!(body.start_byte() < inside.start_byte() && inside.end_byte() < body.end_byte());
19949    }
19950
19951    #[test]
19952    #[hardwon("#5868: refusal controls must actually exercise the namespace body helper")]
19953    fn orphaned_namespace_scope_index_refuses_unproven_function_heads() {
19954        for refusal in [
19955            "int ordinary(int value) { return value; }",
19956            "OPAQUE_PREFIX namespace_ recovered { int member; }",
19957            "OPAQUE_PREFIX namespace recovered other { int member; }",
19958            "OPAQUE_PREFIX namespace recovered(int value) { return value; }",
19959        ] {
19960            let tree = parse_cpp(refusal);
19961            let mut stack = vec![tree.root_node()];
19962            let mut helper_calls = 0;
19963            while let Some(node) = stack.pop() {
19964                if node.kind() == "function_definition"
19965                    && let Some(body) = node.child_by_field_name("body")
19966                {
19967                    helper_calls += 1;
19968                    assert_eq!(
19969                        (node.start_byte(), node.end_byte()),
19970                        (0, refusal.len()),
19971                        "guard control must exercise its complete envelope: source={refusal}, node={node:?}"
19972                    );
19973                    assert!(
19974                        namespace_body_name_components(node, body, refusal).is_none(),
19975                        "refused shape acquired namespace: source={refusal}, node={node:?}, body={body:?}"
19976                    );
19977                }
19978                let mut cursor = node.walk();
19979                stack.extend(node.named_children(&mut cursor));
19980            }
19981            assert_eq!(
19982                helper_calls,
19983                1,
19984                "guard control must reach exactly its actual function/body helper call: source={refusal}, tree={:?}",
19985                tree.root_node()
19986            );
19987        }
19988
19989        // The measured '=' case has no function/body envelope. A missing
19990        // body is likewise parser-absence coverage, not a passed helper guard.
19991        // The macro-declaration grammar reads both as clean declarations whose
19992        // specifier words are `macro_modifier`s, so the parser-absence premise
19993        // is now their lack of a function/body envelope rather than an ERROR.
19994        for absent in [
19995            "OPAQUE_PREFIX namespace recovered = other { int member; }",
19996            "OPAQUE_PREFIX namespace recovered int member;",
19997        ] {
19998            let tree = parse_cpp(absent);
19999            let mut stack = vec![tree.root_node()];
20000            let mut helper_candidates = 0;
20001            while let Some(node) = stack.pop() {
20002                if node.kind() == "function_definition"
20003                    && node.child_by_field_name("body").is_some()
20004                {
20005                    helper_candidates += 1;
20006                }
20007                let mut cursor = node.walk();
20008                stack.extend(node.named_children(&mut cursor));
20009            }
20010            assert_eq!(
20011                helper_candidates,
20012                0,
20013                "parser-absence control unexpectedly acquired a helper envelope: source={absent}, tree={:?}",
20014                tree.root_node()
20015            );
20016        }
20017    }
20018
20019    /// A macro that opens a block: `APP_IF_ACTIVE` expands to `if (active) {`,
20020    /// so the `{` of a scope the source really opens is part of the macro's
20021    /// `preproc_arg` and never becomes a `{` token. The tree therefore holds
20022    /// one more real `}` than real `{`, and a file-global stack has to assign
20023    /// those closes to the wrong opens: the `}` on line 9 takes `check`'s body,
20024    /// `check`'s own `}` on line 11 takes `detail`, line 15 takes `app`, and
20025    /// line 16 arrives with nothing open. This is the state simdjson's
20026    /// `compile_time_json-inl.h` reaches eight times over (#3328).
20027    const MACRO_OPENED_SCOPE: &str = r#"#define APP_IF_ACTIVE if (active) {
20028
20029namespace app {
20030namespace detail {
20031
20032bool check(bool active) {
20033  APP_IF_ACTIVE
20034    return true;
20035  }
20036  return false;
20037}
20038
20039class Tail {};
20040
20041} // namespace detail
20042} // namespace app
20043"#;
20044
20045    /// simdjson's `include/simdjson/compile_time_json-inl.h` at `9b33047a`,
20046    /// reduced. ABI 15 with runtime 0.25.10 now keeps each case-label character
20047    /// literal and the nested namespace definitions. The malformed function
20048    /// body still causes a missing outer namespace close. Keep the original
20049    /// source as a positive for real inner closes, without inventing the outer
20050    /// close that the parser could not read (#3328).
20051    const REFLECTION_CASE_LABEL_RECOVERY: &str = r#"namespace simdjson {
20052namespace compile_time {
20053namespace number_parsing {
20054consteval int leading_zeroes(uint64_t input_num, int last_bit = 0) {
20055    auto hex_to_u32 = [&] [[nodiscard]] () -> uint32_t {
20056      auto digit = [](uint8_t c) -> uint32_t {
20057      }
20058    }
20059    if (a) {
20060      while (a) {
20061      }
20062    }
20063    switch (c) {
20064    case '{': {
20065    }
20066    }
20067  }
20068  while (a) {
20069    cursor += field.second;
20070    if (a) {
20071    case '{': {
20072      if (a) {
20073      }
20074    }
20075    case 'n': {
20076      if (a) {
20077      }
20078    }
20079    }
20080  }
20081  if (a) {
20082    simdjson_consteval_error("Expected '}'");
20083  }
20084}
20085}
20086}
20087"#;
20088
20089    /// Real `{` and `}` tokens of a tree, the stream the index pairs.
20090    fn real_brace_token_counts(root: Node<'_>) -> (usize, usize) {
20091        let mut stack = vec![root];
20092        let mut counts = (0, 0);
20093        while let Some(node) = stack.pop() {
20094            match (node.kind(), node.is_missing()) {
20095                ("{", false) => counts.0 += 1,
20096                ("}", false) => counts.1 += 1,
20097                _ => {}
20098            }
20099            let mut cursor = node.walk();
20100            stack.extend(node.children(&mut cursor));
20101        }
20102        counts
20103    }
20104
20105    /// The open brace of `head`, which is the key its close lands under.
20106    fn namespace_open_byte(source: &str, head: &str) -> usize {
20107        let at = source.find(head).expect("fixture namespace head");
20108        at + head.len() - 1
20109    }
20110
20111    fn assert_namespace_heads_have_no_matching_close(root: Node<'_>, source: &str, heads: &[&str]) {
20112        let index = OrphanedNamespaceScopeIndex::build(root, source);
20113        for head in heads {
20114            assert_eq!(
20115                index.matching_close_brace(namespace_open_byte(source, head)),
20116                None,
20117                "{head} must not claim a close belonging to an inner scope"
20118            );
20119        }
20120    }
20121
20122    #[test]
20123    #[hardwon("1352f739be3d: C++ declaration recovery regressed for this structured syntax edge")]
20124    fn orphaned_namespace_scope_index_pairs_nothing_when_a_scope_open_is_lost() {
20125        let source = MACRO_OPENED_SCOPE;
20126        let tree = parse_cpp(source);
20127        let root = tree.root_node();
20128        assert_eq!(
20129            real_brace_token_counts(root),
20130            (4, 5),
20131            "the fixture must reproduce the loss: one scope the source opens \
20132             has no `{{` token, so the stream holds an extra real close"
20133        );
20134
20135        assert_namespace_heads_have_no_matching_close(
20136            root,
20137            source,
20138            &["namespace app {", "namespace detail {"],
20139        );
20140    }
20141
20142    #[test]
20143    #[hardwon("5312adbcf9bf: C++ declaration recovery regressed for this structured syntax edge")]
20144    fn orphaned_namespace_scope_index_leaves_a_declaration_after_every_close_at_file_scope() {
20145        // The stack proves no pairing in this file, so every restored level
20146        // keeps its best-effort scope. None of it may reach a declaration the
20147        // file writes after its last close.
20148        let source = format!("{MACRO_OPENED_SCOPE}struct AfterAll {{}};\n");
20149        let tree = parse_cpp(&source);
20150        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), &source);
20151        let after_all = named_node_at(&tree, &source, "struct AfterAll");
20152        assert!(
20153            index
20154                .enclosing_namespace_components(after_all, &source)
20155                .is_empty()
20156        );
20157    }
20158
20159    #[test]
20160    #[hardwon("1352f739be3d: C++ declaration recovery regressed for this structured syntax edge")]
20161    fn orphaned_namespace_scope_index_preserves_structured_reflection_closes() {
20162        let source = REFLECTION_CASE_LABEL_RECOVERY;
20163        let tree = parse_cpp(source);
20164        let root = tree.root_node();
20165        let mut stack = vec![root];
20166        let mut mis_lexed_opens = Vec::new();
20167        while let Some(node) = stack.pop() {
20168            if node.kind() == "character"
20169                && &source[node.start_byte()..node.end_byte()] == "{"
20170                && node
20171                    .parent()
20172                    .is_some_and(|parent| parent.kind() != "char_literal")
20173            {
20174                mis_lexed_opens.push(node.start_position().row + 1);
20175            }
20176            let mut cursor = node.walk();
20177            stack.extend(node.children(&mut cursor));
20178        }
20179        assert!(
20180            mis_lexed_opens.is_empty(),
20181            "ABI15 should keep each case-label `{{` inside its `char_literal`; \
20182             the old ABI14 mis-lex is no longer a valid recovery witness"
20183        );
20184        assert_eq!(
20185            real_brace_token_counts(root),
20186            (17, 17),
20187            "the improved parse retains balanced real brace tokens"
20188        );
20189        assert_eq!(root.kind(), "translation_unit");
20190        assert!(root.has_error(), "the malformed body remains incomplete");
20191        let index = OrphanedNamespaceScopeIndex::build(root, source);
20192        assert_eq!(
20193            index.matching_close_brace(namespace_open_byte(source, "namespace simdjson {")),
20194            None,
20195            "a missing outer close must not borrow an inner namespace's real close"
20196        );
20197        for (head, close) in [
20198            ("namespace compile_time {", source.len() - 2),
20199            ("namespace number_parsing {", source.len() - 4),
20200        ] {
20201            assert_eq!(
20202                index
20203                    .matching_close_brace(namespace_open_byte(source, head))
20204                    .map(|range| range.start_byte),
20205                Some(close),
20206                "{head} retains its own real close"
20207            );
20208        }
20209    }
20210
20211    #[test]
20212    #[hardwon("#2243: an empty namespace could hide a nested indexed owner suffix")]
20213    fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
20214        let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
20215        assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
20216        assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
20217        assert!(indexed_namespace_path_is_recoverable(
20218            &["cache".to_string()],
20219            &indexed,
20220            1,
20221        ));
20222    }
20223
20224    #[test]
20225    #[hardwon("#1876: lookup ordering was partial and changed with unrelated workspace content")]
20226    fn sort_lookup_units_totally_orders_every_identity_field() {
20227        let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
20228        let base = CodeUnit::with_signature(
20229            file.clone(),
20230            CodeUnitType::Function,
20231            "scope",
20232            "value",
20233            Some("()".to_string()),
20234            false,
20235        );
20236        let different_kind = CodeUnit::with_signature(
20237            file.clone(),
20238            CodeUnitType::Field,
20239            "scope",
20240            "value",
20241            Some("()".to_string()),
20242            false,
20243        );
20244        let synthetic = base.with_synthetic(true);
20245
20246        let interner = segment_interner();
20247        let mut member_fq = FqName::new();
20248        member_fq.push(interner.intern("scope", SegmentKind::Package));
20249        member_fq.push(interner.intern("value", SegmentKind::Member));
20250        let different_package_boundary = CodeUnit::from_fq(
20251            file.clone(),
20252            CodeUnitType::Function,
20253            member_fq,
20254            0,
20255            Some("()".to_string()),
20256            false,
20257        );
20258
20259        let mut unknown_fq = FqName::new();
20260        unknown_fq.push(interner.intern("scope", SegmentKind::Package));
20261        unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
20262        let different_segment_kind = CodeUnit::from_fq(
20263            file,
20264            CodeUnitType::Function,
20265            unknown_fq,
20266            1,
20267            Some("()".to_string()),
20268            false,
20269        );
20270
20271        let input = vec![
20272            base,
20273            different_kind,
20274            synthetic,
20275            different_package_boundary,
20276            different_segment_kind,
20277        ];
20278        let mut expected = input.clone();
20279        sort_lookup_units(&mut expected);
20280        assert!(expected.windows(2).all(|pair| {
20281            let mut ordered = pair.to_vec();
20282            sort_lookup_units(&mut ordered);
20283            ordered == pair && pair[0] != pair[1]
20284        }));
20285
20286        let mut reversed = input.clone();
20287        reversed.reverse();
20288        sort_lookup_units(&mut reversed);
20289        assert_eq!(reversed, expected);
20290
20291        let mut rotated = input;
20292        rotated.rotate_left(2);
20293        sort_lookup_units(&mut rotated);
20294        assert_eq!(rotated, expected);
20295    }
20296
20297    #[test]
20298    #[hardwon("6ffe85773652: C++ declaration recovery regressed for this structured syntax edge")]
20299    fn structured_conditional_array_bound_preserves_the_real_guard() {
20300        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";
20301        let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
20302        let parse = |source: &str| {
20303            let mut parser = Parser::new();
20304            parser
20305                .set_language(&tree_sitter_cpp::LANGUAGE.into())
20306                .expect("C++ grammar");
20307            parser.parse(source, None).expect("fixture tree")
20308        };
20309
20310        let tree = parse(damaged);
20311        let root = tree.root_node();
20312        assert!(
20313            !root.has_error(),
20314            "the native conditional-array grammar now reads the original recovery witness cleanly: {}",
20315            root.to_sexp()
20316        );
20317        let target = damaged.find("target").expect("target byte");
20318        let declaration = root
20319            .descendant_for_byte_range(target, target + "target".len())
20320            .and_then(|mut node| {
20321                loop {
20322                    if node.kind() == "declaration" {
20323                        break Some(node);
20324                    }
20325                    node = node.parent()?;
20326                }
20327            })
20328            .expect("declaration after the displaced terminator");
20329        let outer = declaration
20330            .parent()
20331            .filter(|node| node.kind() == "preproc_ifdef")
20332            .expect("ordinary outer include guard");
20333        let bound = outer
20334            .named_children(&mut outer.walk())
20335            .filter(|node| node.kind() == "declaration")
20336            .filter_map(|node| node.child_by_field_name("declarator"))
20337            .find(|node| node.kind() == "array_declarator")
20338            .and_then(|node| node.child_by_field_name("size"))
20339            .expect("structured conditional array bound");
20340        assert_eq!(bound.kind(), "preproc_array_size");
20341        let conditional = bound
20342            .named_children(&mut bound.walk())
20343            .find(|node| node.kind() == "preproc_ifdef")
20344            .expect("array-size conditional");
20345        assert!(conditional.end_byte() <= declaration.start_byte());
20346        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
20347        assert!(
20348            !std::iter::successors(Some(declaration), |node| node.parent())
20349                .any(|ancestor| same_node(ancestor, conditional)),
20350            "the declaration must not inherit the array-size feature guard"
20351        );
20352        assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
20353        assert!(preprocessor_conditional_contains_descendant(
20354            outer,
20355            declaration
20356        ));
20357
20358        let tree = parse(guarded);
20359        let conditional = tree
20360            .root_node()
20361            .named_child(0)
20362            .filter(|node| node.kind() == "preproc_ifdef")
20363            .expect("ordinary conditional");
20364        let declaration = conditional
20365            .named_children(&mut conditional.walk())
20366            .find(|node| node.kind() == "declaration")
20367            .expect("guarded declaration");
20368        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
20369        assert!(preprocessor_conditional_contains_descendant(
20370            conditional,
20371            declaration
20372        ));
20373
20374        let damaged_alternative = format!(
20375            "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
20376            "UNUSED(value)\n".repeat(64)
20377        );
20378        let tree = parse(&damaged_alternative);
20379        let conditional = tree
20380            .root_node()
20381            .named_child(0)
20382            .filter(|node| node.kind() == "preproc_ifdef")
20383            .expect("outer conditional with an alternative");
20384        assert!(conditional.has_error());
20385        assert!(conditional.child_by_field_name("alternative").is_some());
20386        assert!(
20387            conditional
20388                .child(conditional.child_count() - 1)
20389                .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
20390        );
20391        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
20392
20393        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";
20394        let tree = parse(split_declaration);
20395        let root = tree.root_node();
20396        // The macro-declaration grammar folds the whole split declaration
20397        // into one damaged `type_definition` instead of keeping a
20398        // `preproc_ifdef` with a displaced terminator: the boundary the
20399        // recovery supplied is now the declaration node itself. What must not
20400        // change is the outcome -- the function written after the split
20401        // declaration is outside the damaged span and inherits no guard.
20402        let split = root
20403            .named_children(&mut root.walk())
20404            .find(|node| node.kind() == "type_definition")
20405            .expect("the split declaration folds into one type definition");
20406        assert_eq!(
20407            split.end_position().row + 1,
20408            9,
20409            "the split declaration ends at its own declarator line"
20410        );
20411        let target = split_declaration
20412            .find("static int target")
20413            .expect("target byte");
20414        assert!(split.end_byte() <= target);
20415        let target_node = root
20416            .descendant_for_byte_range(target, target + "static".len())
20417            .expect("target node");
20418        assert!(
20419            !std::iter::successors(Some(target_node), |node| node.parent())
20420                .any(|ancestor| ancestor.kind() == "preproc_ifdef" && ancestor.has_error()),
20421            "the declaration after the shredded split declaration must not \
20422             inherit a damaged conditional ancestor"
20423        );
20424    }
20425
20426    #[test]
20427    #[hardwon("6ba7704de3ed: C++ declaration recovery regressed for this structured syntax edge")]
20428    fn fragmented_reference_guard_is_recovered() {
20429        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";
20430        let mut parser = Parser::new();
20431        parser
20432            .set_language(&tree_sitter_cpp::LANGUAGE.into())
20433            .expect("C++ grammar");
20434        let tree = parser.parse(source, None).expect("fixture tree");
20435        let start = source.rfind("helper").expect("reference byte");
20436        let node = tree
20437            .root_node()
20438            .descendant_for_byte_range(start, start + "helper".len())
20439            .expect("reference node");
20440        let mut expected = HashSet::default();
20441        expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
20442            vec![
20443                BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
20444                BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
20445            ],
20446        )));
20447        assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
20448    }
20449
20450    #[test]
20451    #[hardwon(
20452        "#6325: grammar 0.24.4 displaced #else/#elif into a declaration ERROR and the branch boundary vanished"
20453    )]
20454    fn displaced_branch_directives_decide_guards_without_proving_unreadable_branches() {
20455        let parse = |source: &str| {
20456            let mut parser = Parser::new();
20457            parser
20458                .set_language(&tree_sitter_cpp::LANGUAGE.into())
20459                .expect("C++ grammar");
20460            parser.parse(source, None).expect("fixture tree")
20461        };
20462        fn at<'tree>(root: Node<'tree>, source: &str, needle: &str, token: &str) -> Node<'tree> {
20463            let start = source.find(needle).expect("needle") + needle.find(token).expect("token");
20464            root.descendant_for_byte_range(start, start + token.len())
20465                .expect("token node")
20466        }
20467        let guards = |node: Node<'_>, source: &str| preprocessor_guard_environment(node, source);
20468        let set = |items: &[PreprocessorGuard]| items.iter().cloned().collect::<HashSet<_>>();
20469
20470        // `#ifdef`: the `#else` is an ERROR child of the declaration that spans
20471        // both sentinels, and a `#define` after it stays a structured child of
20472        // the same conditional.
20473        let source = "namespace n {\n#ifdef A\nBEGIN_NS\nint x;\nEND_NS\n#else\nBEGIN_NS\nclass T {};\nEND_NS\n#define MODE 2\n#endif\n}\n";
20474        let tree = parse(source);
20475        let root = tree.root_node();
20476        let x = at(root, source, "int x", "x");
20477        let conditional = std::iter::successors(Some(x), |node| node.parent())
20478            .find(|node| node.kind() == "preproc_ifdef")
20479            .expect("ifdef");
20480        assert!(conditional.child_by_field_name("alternative").is_none());
20481        let directive = cpp_displaced_preprocessor_branch(conditional).expect("displaced #else");
20482        assert_eq!(directive.kind(), "#else");
20483        assert_eq!(directive.parent().map(|node| node.kind()), Some("ERROR"));
20484        let defined = PreprocessorGuard::Defined("A".to_string());
20485        assert_eq!(guards(x, source), Some(set(std::slice::from_ref(&defined))));
20486        let class_name = at(root, source, "class T", "T");
20487        assert_eq!(guards(class_name, source), Some(set(&[defined.negated()])));
20488        let define = at(root, source, "#define MODE", "MODE")
20489            .parent()
20490            .filter(|node| node.kind() == "preproc_def")
20491            .expect("macro event");
20492        let mut cache = PreprocessorContextCache::default();
20493        assert!(matches!(
20494            preprocessor_branch_of_descendant(conditional, define, &mut cache),
20495            PreprocessorBranch::DisplacedElse
20496        ));
20497        assert_eq!(
20498            preprocessor_conditional_family_for_declaration_with_cache(class_name, &mut cache),
20499            Some(conditional),
20500            "a displaced #else completes the family"
20501        );
20502        assert_eq!(
20503            callable_guard_completion_byte(class_name, source),
20504            Some(conditional.end_byte())
20505        );
20506
20507        // A displaced `#elif B` reads `B` as the declaration's type: its
20508        // condition has no structured node, so nothing after it, including the
20509        // later displaced `#else`, may be read as `!A`.
20510        let source = "namespace n {\n#if A\nBEGIN_NS\nint x;\nEND_NS\n#elif B\nBEGIN_NS\nclass T {};\nEND_NS\n#else\nBEGIN_NS\nstruct T {};\nEND_NS\n#endif\n}\n";
20511        let tree = parse(source);
20512        let root = tree.root_node();
20513        let x = at(root, source, "int x", "x");
20514        let conditional = std::iter::successors(Some(x), |node| node.parent())
20515            .find(|node| node.kind() == "preproc_if")
20516            .expect("if");
20517        assert!(conditional.child_by_field_name("alternative").is_none());
20518        assert_eq!(
20519            cpp_displaced_preprocessor_branch(conditional).map(|node| node.kind()),
20520            Some("#elif")
20521        );
20522        let truthy = PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy("A".to_string()));
20523        assert_eq!(guards(x, source), Some(set(&[truthy])));
20524        let mut cache = PreprocessorContextCache::default();
20525        for needle in ["class T", "struct T"] {
20526            let name = at(root, source, needle, "T");
20527            assert_eq!(
20528                guards(name, source),
20529                None,
20530                "{needle} after a displaced #elif"
20531            );
20532            assert!(matches!(
20533                preprocessor_branch_of_descendant(conditional, name, &mut cache),
20534                PreprocessorBranch::Unreadable
20535            ));
20536            assert_eq!(
20537                preprocessor_conditional_family_for_declaration_with_cache(name, &mut cache),
20538                None,
20539                "{needle}: an unreadable #elif branch does not complete the family"
20540            );
20541            assert_eq!(callable_guard_completion_byte(name, source), None);
20542        }
20543
20544        // A structured `#elif` whose own `#else` was displaced: the content
20545        // after it excludes both readable conditions.
20546        let source = "namespace n {\n#if defined(A)\nBEGIN_NS\nint x;\nEND_NS\n#elif defined(B)\nBEGIN_NS\nclass T {};\nEND_NS\n#else\nBEGIN_NS\nclass T {};\nEND_NS\n#endif\n}\n";
20547        let tree = parse(source);
20548        let root = tree.root_node();
20549        let start = source.rfind("class T").expect("else class") + "class ".len();
20550        let name = root
20551            .descendant_for_byte_range(start, start + 1)
20552            .expect("else class name");
20553        let elif = std::iter::successors(Some(name), |node| node.parent())
20554            .find(|node| node.kind() == "preproc_elif")
20555            .expect("structured elif");
20556        assert_eq!(
20557            cpp_displaced_preprocessor_branch(elif).map(|node| node.kind()),
20558            Some("#else")
20559        );
20560        assert_eq!(
20561            guards(name, source),
20562            Some(set(&[
20563                PreprocessorGuard::Defined("A".to_string()).negated(),
20564                PreprocessorGuard::Defined("B".to_string()).negated(),
20565            ]))
20566        );
20567        let mut cache = PreprocessorContextCache::default();
20568        assert!(
20569            preprocessor_conditional_family_for_declaration_with_cache(name, &mut cache).is_some()
20570        );
20571    }
20572
20573    #[test]
20574    #[hardwon(
20575        "#2184: grammar 0.24.4 reads a macro-decorated return type cleanly, so the recovered-only same-file alias gate stopped matching"
20576    )]
20577    fn macro_modifier_decorated_type_matches_the_recovered_gate_scope() {
20578        let source = "API static inline T make(void);\nAPI T value;\nvoid take(API T p);\nstatic inline T plain(void);\nAPI ns::T qualified(void);\nAPI static inline T body(void) { return 0; }\n";
20579        let mut parser = Parser::new();
20580        parser
20581            .set_language(&tree_sitter_cpp::LANGUAGE.into())
20582            .expect("C++ grammar");
20583        let tree = parser.parse(source, None).expect("fixture tree");
20584        let root = tree.root_node();
20585        let type_before = |declared: &str| {
20586            let end = source.find(declared).expect("declared name");
20587            let start = source[..end].rfind('T').expect("type token");
20588            root.descendant_for_byte_range(start, start + 1)
20589                .expect("type node")
20590        };
20591        for declared in ["make(", "value;", "p)", "body("] {
20592            let node = type_before(declared);
20593            assert_eq!(node.kind(), "type_identifier", "{declared}");
20594            assert!(
20595                macro_modifier_decorated_type_node(node),
20596                "{declared}: {}",
20597                node.parent().expect("declaration").to_sexp()
20598            );
20599        }
20600        for declared in ["plain(", "qualified("] {
20601            let node = type_before(declared);
20602            assert!(
20603                !macro_modifier_decorated_type_node(node),
20604                "{declared} is outside the macro-decorated head scope: {}",
20605                node.parent().expect("parent").to_sexp()
20606            );
20607        }
20608    }
20609
20610    #[test]
20611    fn expression_defined_and_ifndef_guards_are_incompatible() {
20612        let source = "#if defined(WIN_MODE)\nint selected;\n#endif\n#ifndef WIN_MODE\nint rejected;\n#endif\n";
20613        let mut parser = Parser::new();
20614        parser
20615            .set_language(&tree_sitter_cpp::LANGUAGE.into())
20616            .expect("C++ grammar");
20617        let tree = parser.parse(source, None).expect("fixture tree");
20618        let root = tree.root_node();
20619        let selected_start = source.find("selected").expect("selected declaration");
20620        let rejected_start = source.find("rejected").expect("rejected declaration");
20621        let selected = root
20622            .descendant_for_byte_range(selected_start, selected_start + "selected".len())
20623            .expect("selected node");
20624        let rejected = root
20625            .descendant_for_byte_range(rejected_start, rejected_start + "rejected".len())
20626            .expect("rejected node");
20627        let selected_guards =
20628            preprocessor_guard_environment(selected, source).expect("selected guards");
20629        let rejected_guards =
20630            preprocessor_guard_environment(rejected, source).expect("rejected guards");
20631
20632        assert!(
20633            merge_preprocessor_guards(&selected_guards, &rejected_guards).is_none(),
20634            "opposite spellings of one macro guard must contradict"
20635        );
20636    }
20637
20638    #[test]
20639    #[hardwon("02b3765c2a06: C++ declaration recovery regressed for this structured syntax edge")]
20640    fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
20641        let source = r#"#ifdef _WIN32
20642#if defined(__cplusplus)
20643extern "C"
20644#endif
20645int platform_api(void);
20646#endif
20647
20648#ifdef _WIN32
20649static int entropy_target(void) { return 0; }
20650#else
20651#ifdef HAVE_COMMON_RANDOM
20652static int other_target(void) { return 0; }
20653#elif defined(HAVE_GETENTROPY)
20654static int entropy_target(void) { return 1; }
20655static int use_entropy(void) { return entropy_target(); }
20656#endif
20657#endif
20658"#;
20659        let mut parser = Parser::new();
20660        parser
20661            .set_language(&tree_sitter_cpp::LANGUAGE.into())
20662            .expect("C++ grammar");
20663        let tree = parser.parse(source, None).expect("fixture tree");
20664        let start = source.rfind("entropy_target()").expect("reference");
20665        let node = tree
20666            .root_node()
20667            .descendant_for_byte_range(start, start + "entropy_target".len())
20668            .expect("reference node");
20669        let guards = preprocessor_guard_environment(node, source).expect("active C branch");
20670        assert!(
20671            guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
20672            "{guards:#?}"
20673        );
20674        assert!(
20675            guards.contains(&PreprocessorGuard::Undefined(
20676                "HAVE_COMMON_RANDOM".to_string()
20677            )),
20678            "{guards:#?}"
20679        );
20680        assert!(
20681            guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
20682            "{guards:#?}"
20683        );
20684        assert!(
20685            !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
20686            "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
20687        );
20688    }
20689
20690    #[test]
20691    #[hardwon("1e74d25492c1: C++ declaration recovery regressed for this structured syntax edge")]
20692    fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
20693        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";
20694        let mut parser = Parser::new();
20695        parser
20696            .set_language(&tree_sitter_cpp::LANGUAGE.into())
20697            .expect("C++ grammar");
20698        let tree = parser.parse(source, None).expect("fixture tree");
20699        let root = tree.root_node();
20700        let node_at = |text: &str, start: usize| {
20701            root.descendant_for_byte_range(start, start + text.len())
20702                .expect("token node")
20703        };
20704
20705        let key_start = source.find("case KEY").expect("case label") + "case ".len();
20706        let guard_start = source.find("ENABLE_KEYS").expect("guard name");
20707        assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
20708        assert!(!is_ordinary_macro_reference_node(node_at(
20709            "ENABLE_KEYS",
20710            guard_start,
20711        )));
20712    }
20713
20714    #[test]
20715    #[hardwon("31913fee42a0: C++ declaration recovery regressed for this structured syntax edge")]
20716    fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
20717        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";
20718        let mut parser = Parser::new();
20719        parser
20720            .set_language(&tree_sitter_cpp::LANGUAGE.into())
20721            .expect("C++ grammar");
20722        let tree = parser.parse(source, None).expect("fixture tree");
20723        let root = tree.root_node();
20724        let definition_start = source.find("target(void)").expect("definition");
20725        let reference_start = source.rfind("target()").expect("reference");
20726        let definition = root
20727            .descendant_for_byte_range(definition_start, definition_start + "target".len())
20728            .expect("definition node");
20729        let reference = root
20730            .descendant_for_byte_range(reference_start, reference_start + "target".len())
20731            .expect("reference node");
20732        let required =
20733            preprocessor_guard_environment(definition, source).expect("definition guard");
20734        let active = preprocessor_guard_environment(reference, source).expect("reference guard");
20735        assert!(guard_requirements_hold_at_reference(
20736            &required,
20737            Some(&active)
20738        ));
20739    }
20740
20741    #[test]
20742    #[hardwon("31913fee42a0: C++ declaration recovery regressed for this structured syntax edge")]
20743    fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
20744        let source = "g_autoptr(FuChunkArray) self = make_array();";
20745        let mut parser = Parser::new();
20746        parser
20747            .set_language(&tree_sitter_cpp::LANGUAGE.into())
20748            .expect("C++ grammar");
20749        let tree = parser.parse(source, None).expect("fixture tree");
20750        let statement = tree.root_node().named_child(0).expect("statement");
20751        let binding =
20752            recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
20753        assert_eq!(binding.name, "self");
20754        assert_eq!(binding.type_name, "FuChunkArray");
20755        assert_eq!(binding.pointer_depth, 1);
20756
20757        let near_miss = "holder(FuChunkArray) self = make_array();";
20758        let tree = parser.parse(near_miss, None).expect("near-miss tree");
20759        let statement = tree.root_node().named_child(0).expect("statement");
20760        assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
20761    }
20762
20763    #[test]
20764    #[hardwon("dc1b8c5a6ee8: C++ declaration recovery regressed for this structured syntax edge")]
20765    fn boolean_guard_normalization_proves_equivalence_and_implication() {
20766        let windows = BooleanGuardExpression::Defined("WIN32".to_string());
20767        let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
20768        let negated_windows_branch =
20769            BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
20770        let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
20771        assert_eq!(negated_windows_branch, portable);
20772
20773        let missing_a = BooleanGuardExpression::Undefined("A".to_string());
20774        let missing_b = BooleanGuardExpression::Undefined("B".to_string());
20775        let missing_c = BooleanGuardExpression::Undefined("C".to_string());
20776        let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
20777        let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
20778        assert!(fallback_branch.implies(&fallback_declaration));
20779        assert!(
20780            BooleanGuardExpression::Truthy("FEATURE".to_string())
20781                .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
20782        );
20783        assert!(
20784            BooleanGuardExpression::Undefined("FEATURE".to_string())
20785                .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
20786        );
20787        assert!(
20788            !BooleanGuardExpression::Defined("FEATURE".to_string())
20789                .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
20790        );
20791        assert!(!fallback_declaration.implies(&fallback_branch));
20792    }
20793
20794    #[test]
20795    #[hardwon("15e5ef579cdd: C++ declaration recovery regressed for this structured syntax edge")]
20796    fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
20797        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";
20798        let mut parser = Parser::new();
20799        parser
20800            .set_language(&tree_sitter_cpp::LANGUAGE.into())
20801            .expect("C++ grammar");
20802        let tree = parser.parse(source, None).expect("fixture tree");
20803        let root = tree.root_node();
20804        let call = |marker: &str| {
20805            let start = source.find(marker).expect("call marker");
20806            let mut node = root
20807                .descendant_for_byte_range(start, start + "helper".len())
20808                .expect("call name node");
20809            loop {
20810                if node.kind() == "call_expression" {
20811                    break node;
20812                }
20813                node = node.parent().expect("call expression ancestor");
20814            }
20815        };
20816        let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
20817        let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
20818        let keyword_call = call("helper(NULL, template); /* bound */");
20819        let keyword_arguments = keyword_call
20820            .child_by_field_name("arguments")
20821            .expect("keyword argument list");
20822        assert_eq!(
20823            recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
20824            1
20825        );
20826
20827        let nested_call = call("helper(NULL, template); /* nested bound */");
20828        let nested_arguments = nested_call
20829            .child_by_field_name("arguments")
20830            .expect("nested keyword argument list");
20831        assert_eq!(
20832            recovered_c_keyword_argument_count(&c_file, nested_call, nested_arguments, source),
20833            1
20834        );
20835
20836        let lambda_call = call("helper(NULL, template); }(); /* lambda scope */");
20837        let lambda_arguments = lambda_call
20838            .child_by_field_name("arguments")
20839            .expect("lambda keyword argument list");
20840        assert_eq!(
20841            recovered_c_keyword_argument_count(&c_file, lambda_call, lambda_arguments, source),
20842            0,
20843            "a lambda body must not borrow the enclosing collapsed C parameter"
20844        );
20845
20846        assert_eq!(
20847            recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
20848            0
20849        );
20850
20851        let unbound_call = call("helper(NULL, template); /* unbound */");
20852        let unbound_arguments = unbound_call
20853            .child_by_field_name("arguments")
20854            .expect("unbound argument list");
20855        assert_eq!(
20856            recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
20857            0
20858        );
20859    }
20860
20861    #[test]
20862    #[brokk_bifrost_test_macros::hardwon(
20863        "#4407: collapsed C keyword parameters need structural position proof"
20864    )]
20865    fn c_keyword_argument_recovery_rejects_non_parameter_keyword_positions() {
20866        let source = r#"static int helper(const char *left, wchar_t *right) { return 0; }
20867int type_keyword(template *value) {
20868    return helper(NULL, template); /* type */
20869}
20870int qualifier_keyword(const *template) {
20871    return helper(NULL, template); /* qualifier */
20872}
20873int annotation_keyword(wchar_t *value __attribute__((template))) {
20874    return helper(NULL, template); /* annotation */
20875}
20876int default_keyword(wchar_t *value = template) {
20877    return helper(NULL, template); /* default */
20878}
20879int nested_expression(wchar_t *value, (template)) {
20880    return helper(NULL, template); /* nested */
20881}
20882int malformed_head = (wchar_t *template) {
20883    return helper(NULL, template); /* malformed */
20884};
20885"#;
20886        let tree = parse_cpp(source);
20887        let root = tree.root_node();
20888        let call = |marker: &str| {
20889            let start = source.find(marker).expect("call marker");
20890            let mut node = root
20891                .descendant_for_byte_range(start, start + "helper".len())
20892                .expect("call name node");
20893            loop {
20894                if node.kind() == "call_expression" {
20895                    break Some(node);
20896                }
20897                let Some(parent) = node.parent() else {
20898                    break None;
20899                };
20900                node = parent;
20901            }
20902        };
20903        let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-near-miss.c");
20904        let markers = [
20905            ("helper(NULL, template); /* type */", true),
20906            ("helper(NULL, template); /* qualifier */", false),
20907            ("helper(NULL, template); /* annotation */", false),
20908            ("helper(NULL, template); /* default */", false),
20909            ("helper(NULL, template); /* nested */", false),
20910            ("helper(NULL, template); /* malformed */", false),
20911        ];
20912        let mut exercised = 0;
20913        for (marker, expect_call) in markers {
20914            // The macro-declaration grammar admits the misplaced keyword's
20915            // neighbors as `macro_modifier`s, which flattens these heads and
20916            // their bodies: where no `call_expression` survives, the recovery
20917            // has no invocation to fire on, which is itself the non-admission
20918            // proof. Pin the split so the near-miss matrix cannot silently
20919            // decay: only the `type` marker retains an invocation.
20920            let Some(keyword_call) = call(marker) else {
20921                assert!(
20922                    !expect_call,
20923                    "{marker} lost its call_expression; the near-miss matrix changed"
20924                );
20925                continue;
20926            };
20927            assert!(
20928                expect_call,
20929                "{marker} unexpectedly retains a call_expression; the near-miss matrix changed"
20930            );
20931            exercised += 1;
20932            let keyword_arguments = keyword_call
20933                .child_by_field_name("arguments")
20934                .expect("keyword argument list");
20935            assert_eq!(
20936                recovered_c_keyword_argument_count(
20937                    &c_file,
20938                    keyword_call,
20939                    keyword_arguments,
20940                    source,
20941                ),
20942                0,
20943                "keyword in {marker} must not prove a parameter binding"
20944            );
20945        }
20946        assert!(
20947            exercised > 0,
20948            "at least one near-miss marker must exercise the count itself"
20949        );
20950    }
20951
20952    #[test]
20953    #[hardwon("31982bd4d78a: C++ declaration recovery regressed for this structured syntax edge")]
20954    fn c_function_declarator_recovery_accepts_invocations_not_binders() {
20955        let source = r#"#define MAKE(type) type *value
20956MAKE(int *);
20957typedef struct Item Item;
20958struct CPUX86State { struct { int ZMM_L(int); } xmm_regs[8]; };
20959void gen_op_movl(void *s, int first, int second) { }
20960const char *strZ(const char *value) { return value; }
20961int body(void *s) {
20962    MAKE(int *);
20963    gen_op_movl(s, offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)),
20964                offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)));
20965    execvp(strZ(value), UNCONSTIFY(char **, args));
20966}
20967 #define DEV_CHECK_PRESENCE(TYPE, MEMBER, DEVTYPE, PROPERTY, VALUE) \
20968    if (!((TYPE)target)->MEMBER) { check(DEVTYPE, PROPERTY, VALUE); }
20969int recovered_deviation(struct Deviation *d, struct Target *target, void *ctx) {
20970    if (d->units) {
20971        switch (target->nodetype) {
20972        case 1:
20973        case 2:
20974            break;
20975        default:
20976            AMEND_WRONG_NODETYPE("deviation", "replace", "units");
20977        }
20978        DEV_CHECK_PRESENCE(struct Item *, units, "replacing", "units", d->units);
20979        lysdict_remove(ctx, ((struct Item *)target)->units);
20980        DUP_STRING_GOTO(ctx, d->units, ((struct Item *)target)->units, ret, cleanup);
20981    }
20982     return 0;
20983 }
20984STATIC EFI_STATUS Encode () { return 0; }
20985"#;
20986        let tree = parse_cpp(source);
20987        let top_macro_start = source.find("MAKE(int *);").expect("top macro");
20988        let top_macro = tree
20989            .root_node()
20990            .named_descendant_for_byte_range(top_macro_start, top_macro_start + 4)
20991            .expect("top macro node");
20992        let body_macro_start = source
20993            .match_indices("MAKE(int *);")
20994            .nth(1)
20995            .expect("body macro")
20996            .0;
20997        let body_macro = tree
20998            .root_node()
20999            .named_descendant_for_byte_range(body_macro_start, body_macro_start + 4)
21000            .expect("body macro node");
21001        let function_call_start = source
21002            .find("gen_op_movl(s, offsetof(CPUX86State")
21003            .expect("function call");
21004        let function_call = tree
21005            .root_node()
21006            .named_descendant_for_byte_range(function_call_start, function_call_start + 11)
21007            .expect("function call node");
21008        let strz_start = source.find("strZ(value)").expect("nested function call");
21009        let strz = tree
21010            .root_node()
21011            .named_descendant_for_byte_range(strz_start, strz_start + 4)
21012            .expect("nested function call node");
21013        let recovered_call_start = source.find("lysdict_remove(ctx").expect("recovered call");
21014        let recovered_call = tree
21015            .root_node()
21016            .named_descendant_for_byte_range(
21017                recovered_call_start,
21018                recovered_call_start + "lysdict_remove".len(),
21019            )
21020            .expect("recovered call node");
21021        let binder_start = source.find("Encode").expect("binder");
21022        let binder = tree
21023            .root_node()
21024            .named_descendant_for_byte_range(binder_start, binder_start + 6)
21025            .expect("binder node");
21026
21027        assert!(recovered_c_function_declarator_invocation(top_macro));
21028        assert!(recovered_c_function_declarator_invocation(body_macro));
21029        assert!(recovered_c_function_declarator_invocation(function_call));
21030        assert!(recovered_c_function_declarator_invocation(strz));
21031        assert!(recovered_c_function_declarator_invocation(recovered_call));
21032        assert!(!recovered_c_function_declarator_invocation(binder));
21033    }
21034
21035    #[test]
21036    #[hardwon("31982bd4d78a: C++ declaration recovery regressed for this structured syntax edge")]
21037    fn c_parenthesized_declarator_recovery_keeps_keyword_argument_and_rejects_siblings() {
21038        let source = r#"typedef int krb5_context;
21039int helper(int first, int second) { return first + second; }
21040static krb5_context ctx;
21041int main(int argc, char **argv) {
21042    int ccinitial;
21043    const char *collection_name, *typename;
21044    typename = helper(ctx, ccinitial);
21045    return 0;
21046}
21047"#;
21048        let tree = parse_cpp(source);
21049        let ctx = tree
21050            .root_node()
21051            .descendant_for_byte_range(
21052                source.find("ctx, ccinitial").expect("ctx argument"),
21053                source.find("ctx, ccinitial").expect("ctx argument") + 3,
21054            )
21055            .expect("ctx node");
21056        let ccinitial_start = source.find("ctx, ccinitial").expect("ctx argument") + 5;
21057        let ccinitial = tree
21058            .root_node()
21059            .descendant_for_byte_range(ccinitial_start, ccinitial_start + "ccinitial".len())
21060            .expect("sibling node");
21061        let typename = named_node_at(&tree, source, "typename = helper");
21062        let helper = named_node_at(&tree, source, "helper(ctx, ccinitial)");
21063
21064        // The macro-declaration grammar retains the argument beneath the
21065        // nested `ERROR` as a `type_descriptor`, so its type position is a
21066        // `type_identifier`; the recovery contract below is unchanged.
21067        assert_eq!(ctx.kind(), "type_identifier");
21068        assert!(recovered_c_parenthesized_declarator_reference(ctx));
21069        assert!(!recovered_c_parenthesized_declarator_reference(ccinitial));
21070        assert!(!recovered_c_parenthesized_declarator_reference(typename));
21071        assert!(!recovered_c_parenthesized_declarator_reference(helper));
21072    }
21073
21074    fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
21075        let mut parser = Parser::new();
21076        parser
21077            .set_language(&tree_sitter_cpp::LANGUAGE.into())
21078            .expect("C++ grammar");
21079        let tree = parser.parse(source, None).expect("C++ fixture tree");
21080        let mut stack = vec![tree.root_node()];
21081        while let Some(node) = stack.pop() {
21082            if node.kind() == "enum_specifier" {
21083                return flattened_macro_namespace_components(node, source);
21084            }
21085            let mut cursor = node.walk();
21086            let children = node.named_children(&mut cursor).collect::<Vec<_>>();
21087            stack.extend(children.into_iter().rev());
21088        }
21089        None
21090    }
21091
21092    #[test]
21093    #[hardwon("0e6df36dfa5d: C++ declaration recovery regressed for this structured syntax edge")]
21094    fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
21095        let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
21096namespace detail
21097{
21098enum class value_t { null };
21099}
21100NLOHMANN_JSON_NAMESPACE_END
21101NLOHMANN_JSON_NAMESPACE_BEGIN
21102namespace next
21103{
21104struct next_type {};
21105}
21106NLOHMANN_JSON_NAMESPACE_END
21107"#;
21108        assert_eq!(
21109            first_enum_flattened_namespace(complete),
21110            Some(vec!["detail".to_string()])
21111        );
21112
21113        let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
21114        assert_eq!(
21115            first_enum_flattened_namespace(&stale_end),
21116            Some(vec!["detail".to_string()]),
21117            "a stale end marker before the begin marker must not replace the intended namespace"
21118        );
21119
21120        let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
21121namespace detail
21122{
21123enum class value_t { null };
21124}
21125struct next_type {};
21126"#;
21127        assert_eq!(first_enum_flattened_namespace(incomplete), None);
21128    }
21129}
21130
21131/// Comparator laws for the total C++ lookup order introduced by #1876.
21132///
21133/// `sort_lookup_units` is the single tie-break the C++ resolver applies before
21134/// any "first wins" selection (template families in #1836, the visible
21135/// identifier index, the type-candidate lists). If its comparator is not a
21136/// total order over CodeUnit identity, some pair stays tied and the survivor
21137/// falls back to the order the units arrived in -- which is FxHash iteration
21138/// order over keys whose hash covers the absolute workspace root. That is the
21139/// exact mechanism behind #1836 and the #414 / #432 heisenbug, so the laws are
21140/// checked generatively rather than on one hand-picked list.
21141///
21142/// CodeUnit identity is `source`, `kind`, `fq`, `package_segment_count`,
21143/// `signature` and `synthetic` (see `impl PartialEq for CodeUnit`); a CodeUnit
21144/// carries no range, so declaration ranges are covered by the workspace-level
21145/// property in `tests/suite_analyzers/determinism_properties.rs` instead.
21146#[cfg(test)]
21147mod lookup_order_properties {
21148    use super::*;
21149    use proptest::prelude::*;
21150
21151    /// Segment spellings the C++ extractor and the shared renderer actually
21152    /// produce, including the `$`-joined nested spellings and non-ASCII
21153    /// identifiers that a byte-wise comparison has to keep apart.
21154    const ATOMS: [&str; 9] = ["a", "b", "A", "a$b", "a$", "$a", "ab", "naïve", "識別子"];
21155    const REL_PATHS: [&str; 3] = ["a.cpp", "b.cpp", "sub/a.cpp"];
21156    /// Two roots so the order is pinned across workspaces as well as inside
21157    /// one: the root path is precisely the byte string that used to leak into
21158    /// iteration order.
21159    const ROOT_NAMES: [&str; 2] = ["ws", "ws_much_longer_root_name"];
21160    const SIGNATURES: [Option<&str>; 3] = [None, Some("()"), Some("(int)")];
21161    const KINDS: [CodeUnitType; 6] = [
21162        CodeUnitType::Class,
21163        CodeUnitType::Function,
21164        CodeUnitType::Field,
21165        CodeUnitType::Module,
21166        CodeUnitType::Macro,
21167        CodeUnitType::FileScope,
21168    ];
21169
21170    /// Where one unit sits relative to another under the comparator that
21171    /// `sort_lookup_units` owns.
21172    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
21173    enum ProbedOrder {
21174        Before,
21175        Tied,
21176        After,
21177        /// Both directions reported "strictly first": the comparator is not
21178        /// dual, and no sort over it can be order-independent.
21179        Contradictory,
21180    }
21181
21182    impl ProbedOrder {
21183        fn mirror(self) -> Self {
21184            match self {
21185                ProbedOrder::Before => ProbedOrder::After,
21186                ProbedOrder::After => ProbedOrder::Before,
21187                other => other,
21188            }
21189        }
21190
21191        /// -1 / 0 / +1, so transitivity reads as the `<= 0` law.
21192        fn signum(self) -> i8 {
21193            match self {
21194                ProbedOrder::Before => -1,
21195                ProbedOrder::Tied => 0,
21196                ProbedOrder::After => 1,
21197                ProbedOrder::Contradictory => panic!("probed a non-dual comparator"),
21198            }
21199        }
21200    }
21201
21202    /// Read the comparator through its only caller.
21203    ///
21204    /// `sort_lookup_units` is a stable sort, so for a two-element slice the
21205    /// output says exactly whether the comparator put the second element
21206    /// strictly first. Sorting both arrangements of one pair therefore reports
21207    /// the comparator's verdict in both directions, including the contradictory
21208    /// case a single sort would hide.
21209    fn probe_order(left: &CodeUnit, right: &CodeUnit) -> ProbedOrder {
21210        if left == right {
21211            // A stable sort cannot distinguish two equal values, and `Equal` is
21212            // the only verdict a total order can give them.
21213            return ProbedOrder::Tied;
21214        }
21215        let mut forward = vec![left.clone(), right.clone()];
21216        sort_lookup_units(&mut forward);
21217        let mut backward = vec![right.clone(), left.clone()];
21218        sort_lookup_units(&mut backward);
21219        let left_first = backward[0] == *left;
21220        let right_first = forward[0] == *right;
21221        match (left_first, right_first) {
21222            (true, true) => ProbedOrder::Contradictory,
21223            (true, false) => ProbedOrder::Before,
21224            (false, true) => ProbedOrder::After,
21225            (false, false) => ProbedOrder::Tied,
21226        }
21227    }
21228
21229    fn code_unit_strategy() -> impl Strategy<Value = CodeUnit> {
21230        (
21231            0..ROOT_NAMES.len(),
21232            0..REL_PATHS.len(),
21233            0..KINDS.len(),
21234            prop::collection::vec((0..ATOMS.len(), 0..SegmentKind::ALL.len()), 1..=3),
21235            0..3usize,
21236            0..SIGNATURES.len(),
21237            any::<bool>(),
21238        )
21239            .prop_map(
21240                |(root, rel_path, kind, segments, package_prefix, signature, synthetic)| {
21241                    let source = ProjectFile::new(
21242                        std::env::temp_dir().join(ROOT_NAMES[root]),
21243                        REL_PATHS[rel_path],
21244                    );
21245                    let interner = segment_interner();
21246                    let mut fq = FqName::new();
21247                    for (atom, segment_kind) in &segments {
21248                        fq.push(interner.intern(ATOMS[*atom], SegmentKind::ALL[*segment_kind]));
21249                    }
21250                    // `from_fq` requires a non-empty declaration tail.
21251                    let package_segment_count = package_prefix % fq.len();
21252                    CodeUnit::from_fq(
21253                        source,
21254                        KINDS[kind],
21255                        fq,
21256                        package_segment_count,
21257                        SIGNATURES[signature].map(str::to_string),
21258                        synthetic,
21259                    )
21260                },
21261            )
21262    }
21263
21264    proptest! {
21265        #![proptest_config(ProptestConfig::with_cases(256))]
21266
21267        /// Reflexivity and duality: a unit ties with itself, and no pair is
21268        /// strictly first in both directions.
21269        #[test]
21270        #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
21271        fn lookup_order_is_reflexive_and_dual(
21272            left in code_unit_strategy(),
21273            right in code_unit_strategy(),
21274        ) {
21275            prop_assert_eq!(
21276                probe_order(&left, &left),
21277                ProbedOrder::Tied,
21278                "a unit must tie with itself: {:?}",
21279                left
21280            );
21281            let forward = probe_order(&left, &right);
21282            prop_assert_ne!(
21283                forward,
21284                ProbedOrder::Contradictory,
21285                "comparator put each of these strictly first: left={:?} right={:?}",
21286                left,
21287                right
21288            );
21289            prop_assert_eq!(
21290                probe_order(&right, &left),
21291                forward.mirror(),
21292                "compare(b, a) must reverse compare(a, b): left={:?} right={:?}",
21293                left,
21294                right
21295            );
21296        }
21297
21298        /// Transitivity: `a <= b` and `b <= c` imply `a <= c`.
21299        #[test]
21300        #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
21301        fn lookup_order_is_transitive(
21302            a in code_unit_strategy(),
21303            b in code_unit_strategy(),
21304            c in code_unit_strategy(),
21305        ) {
21306            let ab = probe_order(&a, &b);
21307            let bc = probe_order(&b, &c);
21308            let ac = probe_order(&a, &c);
21309            for (probed, pair) in [(ab, "a,b"), (bc, "b,c"), (ac, "a,c")] {
21310                prop_assert_ne!(
21311                    probed,
21312                    ProbedOrder::Contradictory,
21313                    "comparator is not dual over {}: a={:?} b={:?} c={:?}",
21314                    pair,
21315                    a,
21316                    b,
21317                    c
21318                );
21319            }
21320            if ab.signum() <= 0 && bc.signum() <= 0 {
21321                prop_assert!(
21322                    ac.signum() <= 0,
21323                    "transitivity broken: a<=b ({:?}) and b<=c ({:?}) but a?c is {:?}; \
21324                     a={:?} b={:?} c={:?}",
21325                    ab,
21326                    bc,
21327                    ac,
21328                    a,
21329                    b,
21330                    c
21331                );
21332            }
21333        }
21334
21335        /// The property #1876 exists for: only identical identities may tie.
21336        /// A tie between distinct units is the residual hash-order dependence.
21337        #[test]
21338        #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
21339        fn lookup_order_separates_distinct_identities(
21340            left in code_unit_strategy(),
21341            right in code_unit_strategy(),
21342        ) {
21343            if probe_order(&left, &right) == ProbedOrder::Tied {
21344                prop_assert_eq!(
21345                    &left,
21346                    &right,
21347                    "distinct identities tied, so their order is whatever order they \
21348                     arrived in: left_segments={:?} right_segments={:?}",
21349                    left.fq_segments_debug(),
21350                    right.fq_segments_debug()
21351                );
21352            }
21353        }
21354
21355        /// The consequence the resolver relies on: the sorted list is a
21356        /// function of the SET of units, not of the order they were pushed in.
21357        #[test]
21358        #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
21359        fn lookup_sort_is_permutation_invariant(
21360            units in prop::collection::vec(code_unit_strategy(), 1..=8),
21361        ) {
21362            let mut sorted = units.clone();
21363            sort_lookup_units(&mut sorted);
21364            for rotation in 0..units.len() {
21365                for reversed in [false, true] {
21366                    let mut permuted = units.clone();
21367                    permuted.rotate_left(rotation);
21368                    if reversed {
21369                        permuted.reverse();
21370                    }
21371                    sort_lookup_units(&mut permuted);
21372                    prop_assert_eq!(
21373                        &permuted,
21374                        &sorted,
21375                        "sorting a permutation gave a different list \
21376                         (rotation={}, reversed={}): input={:?}",
21377                        rotation,
21378                        reversed,
21379                        units
21380                    );
21381                }
21382            }
21383        }
21384    }
21385}
21386
21387#[cfg(test)]
21388mod resolver_domain_properties {
21389    use super::*;
21390    use proptest::prelude::*;
21391
21392    proptest! {
21393        // A missing free variable cannot be supplied by irrelevant names.
21394        // The expected fully bound tree is built directly from the inputs.
21395        #[test]
21396        fn template_substitution_respects_free_variables(
21397            first in 0u16..1000,
21398            second in 1000u16..2000,
21399            unrelated in prop::collection::vec(0u16..1000, 1..8),
21400            rename in "[A-Z][a-z]{0,6}",
21401        ) {
21402            let atom = |value: u16| CppTemplateTerm::Atom {
21403                kind: "number_literal".to_owned(), text: value.to_string(),
21404            };
21405            let term = CppTemplateTerm::Node {
21406                kind: "template_argument_list".to_owned(),
21407                children: vec![CppTemplateTerm::Parameter("T".to_owned()), CppTemplateTerm::Parameter(format!("U{rename}"))],
21408            };
21409            let mut bindings = HashMap::default();
21410            bindings.insert("T".to_owned(), atom(first));
21411            prop_assert_eq!(cpp_substitute_template_term(&term, &bindings), None);
21412            for (index, value) in unrelated.into_iter().enumerate() {
21413                bindings.insert(format!("unrelated{index}"), atom(value));
21414                prop_assert_eq!(cpp_substitute_template_term(&term, &bindings), None);
21415            }
21416            bindings.insert(format!("U{rename}"), atom(second));
21417            let expected = CppTemplateTerm::Node {
21418                kind: "template_argument_list".to_owned(), children: vec![atom(first), atom(second)],
21419            };
21420            prop_assert_eq!(cpp_substitute_template_term(&term, &bindings), Some(expected));
21421        }
21422
21423        // Combining redeclarations is symmetric. When their parameter lists
21424        // agree, the accepted argument counts are the union of both domains.
21425        #[test]
21426        fn callable_arity_merge_is_a_symmetric_domain_union(
21427            total in 0usize..20,
21428            left_required in 0usize..20,
21429            right_required in 0usize..20,
21430            left_variadic in any::<bool>(),
21431            right_variadic in any::<bool>(),
21432        ) {
21433            let left = CallableArity::new(left_required.min(total), total, left_variadic);
21434            let right = CallableArity::new(right_required.min(total), total, right_variadic);
21435            let merged = merge_compatible_callable_arities(left, right);
21436            prop_assert_eq!(merged, merge_compatible_callable_arities(right, left));
21437            if let Some(merged) = merged {
21438                for count in 0..=total + 2 {
21439                    prop_assert_eq!(merged.accepts(count), left.accepts(count) || right.accepts(count));
21440                }
21441            } else {
21442                prop_assert_ne!(left_variadic, right_variadic);
21443            }
21444        }
21445
21446        // Repeating the same include-path assumptions cannot discharge an
21447        // undecidable guard. This is the idempotence law of conjunction.
21448        #[test]
21449        fn guard_environment_conjunction_is_idempotent(names in prop::collection::vec("[A-Z]{1,5}", 1..8)) {
21450            let guards: HashSet<_> = names.into_iter().map(PreprocessorGuard::Defined).collect();
21451            let environment = PreprocessorGuardEnvironment { all: guards.clone(), partial: guards.clone() };
21452            let twice = environment.merged(&environment).unwrap();
21453            prop_assert_eq!(&twice.all, &guards);
21454            prop_assert_eq!(&twice.partial, &guards);
21455        }
21456    }
21457}
21458
21459#[cfg(test)]
21460mod resolver_lookup_key_properties {
21461    use super::*;
21462    use proptest::prelude::*;
21463
21464    proptest! {
21465        #![proptest_config(ProptestConfig::with_cases(32))]
21466
21467        // The declaration producer and the bounded FQN consumer must agree
21468        // on at least one exact index key. Canonical-name fallback would mask
21469        // this boundary mismatch in ordinary navigation.
21470        #[test]
21471        fn parsed_nested_types_round_trip_through_bounded_lookup_keys(
21472            namespaces in 0usize..3,
21473            owners in 1usize..5,
21474            suffix in "[a-z]{1,6}",
21475        ) {
21476            let mut source = String::new();
21477            let mut components = Vec::new();
21478            for index in 0..namespaces {
21479                let name = format!("ns{index}_{suffix}");
21480                source.push_str(&format!("namespace {name} {{ "));
21481                components.push(name);
21482            }
21483            for index in 0..owners {
21484                let name = format!("Owner{index}");
21485                source.push_str(&format!("struct {name} {{ "));
21486                components.push(name);
21487            }
21488            source.push_str("struct Leaf {}; ");
21489            for _ in 0..owners { source.push_str("}; "); }
21490            for _ in 0..namespaces { source.push_str("} "); }
21491            components.push("Leaf".to_owned());
21492            source.push_str(&format!("{}* value;", components.join("::")));
21493
21494            let tree = crate::diagnostics::parse_cpp_tree(&source);
21495            prop_assert!(!tree.root_node().has_error());
21496            let file = ProjectFile::new(std::env::temp_dir(), "lookup-keys.cpp");
21497            let parsed = crate::adapter::parse_cpp_file(&file, &source, &tree);
21498            let leaf = parsed.declarations().iter()
21499                .find(|unit| unit.is_class() && unit.identifier() == "Leaf")
21500                .expect("declaration producer indexed the nested type");
21501            let declaration = tree.root_node().named_child(tree.root_node().named_child_count() - 1).unwrap();
21502            prop_assert_eq!(declaration.kind(), "declaration");
21503            let type_node = declaration.child_by_field_name("type").unwrap();
21504            let parts = cpp_type_name_components(type_node, &source).unwrap();
21505            prop_assert_eq!(&parts, &components);
21506            let candidates = cpp_reference_fqn_candidates_from_parts(&parts, TargetKind::Type);
21507            let declaration_key = leaf.fq_name();
21508            prop_assert!(candidates.contains(&declaration_key), "declaration key {:?} absent from bounded query keys {:?}", declaration_key, candidates);
21509        }
21510    }
21511}