Skip to main content

brokk_bifrost_cpp/graph/
resolver.rs

1use crate::call_match::{CppArgType, normalize_cpp_type_name};
2use crate::compile_context::{CompiledLanguage, 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/// What a reference's guard and build evidence selects from the conditional
175/// alias arms on its alias chain.
176pub enum GuardedAliasSelection {
177    /// Evidence implies one arm at every conditional hop and the chain names
178    /// exactly `unit`. `candidates` are the selected top-level declarations.
179    Proven {
180        unit: CodeUnit,
181        candidates: Vec<CodeUnit>,
182    },
183    /// Undecided or unresolvable arms remain. Each type is a candidate only.
184    Alternatives(Vec<CodeUnit>),
185}
186
187#[derive(Clone, Copy, PartialEq, Eq)]
188enum AliasArmEvidence {
189    Implied,
190    Compatible,
191    Contradicted,
192}
193
194#[derive(Clone, Copy)]
195enum TypeCandidateResolution<'a> {
196    Canonical,
197    PreserveAlias,
198    PreserveTarget(&'a CodeUnit),
199}
200
201/// Why a name did not reduce to one indexed type declaration.
202///
203/// The two answers are not interchangeable. `Ambiguous` means the index holds
204/// several declarations and the caller must choose; `Unresolvable` means the
205/// index holds none, which is a boundary the workspace cannot see past. A
206/// `using`/`typedef` alias to a template parameter or to a standard-library
207/// type is unresolvable, and reporting it as ambiguity produced an `ambiguous`
208/// answer with an empty candidate list (#1828).
209#[derive(Clone, Copy, Debug, PartialEq, Eq)]
210enum TypeCandidateFailure {
211    Ambiguous,
212    Unresolvable,
213}
214
215impl TypeCandidateFailure {
216    fn lexical_resolution(self) -> LexicalTypeResolution {
217        match self {
218            Self::Ambiguous => LexicalTypeResolution::Ambiguous,
219            Self::Unresolvable => LexicalTypeResolution::Missing,
220        }
221    }
222}
223
224pub enum LexicalCallableValueResolution {
225    Type(CodeUnit),
226    FreeFunction(CodeUnit),
227    Ambiguous,
228    Missing,
229}
230
231pub enum UsingEnumMemberResolution {
232    Resolved { owner: CodeUnit, member: CodeUnit },
233    Ambiguous,
234    Missing,
235}
236
237pub enum NamespaceValueResolution {
238    Resolved,
239    Ambiguous,
240    Missing,
241}
242
243#[derive(Clone, Debug, PartialEq, Eq)]
244pub enum OrdinaryMacroReferenceResolution {
245    Resolved(CodeUnit),
246    Ambiguous,
247    Missing,
248}
249
250#[derive(Clone, Debug, PartialEq, Eq)]
251pub enum RecoveredCReferenceRanges {
252    Complete(Vec<Range>),
253    LimitExceeded,
254}
255
256pub fn resolve_namespace_value(
257    analyzer: &CppGraphSource<'_>,
258    visibility: &VisibilityIndex<'_>,
259    file: &ProjectFile,
260    namespace: &str,
261    name: &str,
262    before_byte: usize,
263) -> NamespaceValueResolution {
264    let mut matches = Vec::new();
265    for candidate in visibility.visible_identifier_candidates(file, name) {
266        if type_owner_of(analyzer, candidate).is_some()
267            || candidate.package_name() != namespace
268            || (candidate.source() == file
269                && !analyzer
270                    .ranges(candidate)
271                    .iter()
272                    .any(|range| range.start_byte < before_byte))
273            || matches
274                .iter()
275                .any(|existing| same_visible_symbol(existing, candidate))
276        {
277            continue;
278        }
279        matches.push(candidate.clone());
280        if matches.len() > 1 {
281            return NamespaceValueResolution::Ambiguous;
282        }
283    }
284    matches
285        .pop()
286        .map(|_| NamespaceValueResolution::Resolved)
287        .unwrap_or(NamespaceValueResolution::Missing)
288}
289
290pub(crate) struct ScopedUsingEnumOwners {
291    scopes: Vec<Vec<CodeUnit>>,
292}
293
294/// Same-file class and namespace imports collected by the targeted scanner's AST prepass.
295/// Cross-file and inherited class imports are deliberately not inferred without persisted
296/// evidence; a missing imported enumerator therefore remains unproven rather than being
297/// misresolved.
298pub(crate) struct SemanticUsingEnumOwners {
299    class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
300    namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
301}
302
303pub(crate) enum SemanticUsingEnumMemberResolution {
304    Class(UsingEnumMemberResolution),
305    Namespace(UsingEnumMemberResolution),
306    Missing,
307}
308
309impl SemanticUsingEnumOwners {
310    pub(crate) fn new() -> Self {
311        Self {
312            class_imports: HashMap::default(),
313            namespace_imports: HashMap::default(),
314        }
315    }
316
317    pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
318        let imports = self.class_imports.entry(class).or_default();
319        if !imports
320            .iter()
321            .any(|existing| same_visible_symbol(existing, &enum_owner))
322        {
323            imports.push(enum_owner);
324        }
325    }
326
327    pub fn import_namespace(
328        &mut self,
329        namespace: Vec<String>,
330        declaration_byte: usize,
331        enum_owner: CodeUnit,
332    ) {
333        let imports = self.namespace_imports.entry(namespace).or_default();
334        if !imports
335            .iter()
336            .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
337        {
338            imports.push((declaration_byte, enum_owner));
339        }
340    }
341
342    pub fn resolve_member(
343        &self,
344        visibility: &VisibilityIndex<'_>,
345        file: &ProjectFile,
346        class: Option<&CodeUnit>,
347        namespace: &[String],
348        before_byte: usize,
349        name: &str,
350    ) -> SemanticUsingEnumMemberResolution {
351        if let Some(class) = class
352            && let Some((_, imports)) = self
353                .class_imports
354                .iter()
355                .find(|(owner, _)| same_visible_symbol(owner, class))
356        {
357            let resolution =
358                resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
359            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
360                return SemanticUsingEnumMemberResolution::Class(resolution);
361            }
362        }
363        for prefix_len in (0..=namespace.len()).rev() {
364            let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
365                continue;
366            };
367            let owners = imports
368                .iter()
369                .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
370                .map(|(_, owner)| owner);
371            let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
372            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
373                return SemanticUsingEnumMemberResolution::Namespace(resolution);
374            }
375        }
376        SemanticUsingEnumMemberResolution::Missing
377    }
378}
379
380fn resolve_using_enum_member_for_owners<'a>(
381    visibility: &VisibilityIndex<'_>,
382    file: &ProjectFile,
383    owners: impl IntoIterator<Item = &'a CodeUnit>,
384    name: &str,
385) -> UsingEnumMemberResolution {
386    let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
387    for owner in owners {
388        for member in visibility.visible_members_for_owner_name(file, owner, name) {
389            if !member.is_field()
390                || matches.iter().any(|(existing_owner, existing_member)| {
391                    same_visible_symbol(existing_owner, owner)
392                        && same_visible_symbol(existing_member, member)
393                })
394            {
395                continue;
396            }
397            matches.push((owner.clone(), member.clone()));
398        }
399    }
400    match matches.len() {
401        0 => UsingEnumMemberResolution::Missing,
402        1 => {
403            let (owner, member) = matches.pop().expect("one using-enum match");
404            UsingEnumMemberResolution::Resolved { owner, member }
405        }
406        _ => UsingEnumMemberResolution::Ambiguous,
407    }
408}
409
410impl ScopedUsingEnumOwners {
411    pub(crate) fn new() -> Self {
412        Self {
413            scopes: vec![Vec::new()],
414        }
415    }
416
417    pub fn enter_scope(&mut self) {
418        self.scopes.push(Vec::new());
419    }
420
421    pub fn exit_scope(&mut self) {
422        brokk_bifrost_core::analyzer::usages::exit_nested_scope(&mut self.scopes);
423    }
424
425    pub fn import(&mut self, owner: CodeUnit) {
426        let scope = self
427            .scopes
428            .last_mut()
429            .expect("using-enum scope stack is never empty");
430        if !scope
431            .iter()
432            .any(|existing| same_visible_symbol(existing, &owner))
433        {
434            scope.push(owner);
435        }
436    }
437
438    pub fn resolve_member(
439        &self,
440        visibility: &VisibilityIndex<'_>,
441        file: &ProjectFile,
442        name: &str,
443    ) -> UsingEnumMemberResolution {
444        for scope in self.scopes.iter().rev() {
445            let resolution =
446                resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
447            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
448                return resolution;
449            }
450        }
451        UsingEnumMemberResolution::Missing
452    }
453}
454
455#[derive(Clone)]
456pub struct TargetSpec {
457    pub target: CodeUnit,
458    pub kind: TargetKind,
459    pub owner: Option<CodeUnit>,
460    pub member_name: String,
461    pub callable_arity: Option<CallableArity>,
462    pub activated_callable_arities: Vec<ActivatedCallableArity>,
463    pub param_types: Option<Vec<String>>,
464    pub enum_owner_kind: EnumOwnerKind,
465    pub owner_is_forward_declaration: bool,
466    pub callable_has_definition_body: bool,
467}
468
469#[derive(Clone, Copy)]
470pub struct ActivatedCallableArity {
471    pub activation_byte: usize,
472    pub arity: CallableArity,
473}
474
475#[derive(Debug, PartialEq, Eq, Hash)]
476pub struct TypeScanKey {
477    target: LogicalSymbolKey,
478    member_name: String,
479}
480
481#[derive(Clone, Debug, PartialEq, Eq, Hash)]
482struct LogicalSymbolKey {
483    kind: CodeUnitType,
484    fq_name: String,
485    signature: Option<String>,
486}
487
488struct ResolvedTypeOwner {
489    unit: CodeUnit,
490    is_forward_declaration: bool,
491}
492
493impl TargetSpec {
494    pub fn type_scan_key(&self) -> Option<TypeScanKey> {
495        (self.kind == TargetKind::Type).then(|| TypeScanKey {
496            target: logical_symbol_key(&self.target),
497            member_name: self.member_name.clone(),
498        })
499    }
500
501    pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
502        if target.is_class() {
503            return Some(Self::new(
504                target.clone(),
505                TargetKind::Type,
506                Some(target.clone()),
507                target.identifier().to_string(),
508                None,
509                None,
510            ));
511        }
512
513        if target.is_field() {
514            // A namespace (module) is not a receiver: a namespace-scoped constant such as
515            // `example::DefaultPrefix` is referenced unqualified from inside the namespace and
516            // qualified from outside, exactly like a global. Treating a module owner as a
517            // member-field owner makes the receiver/owner-context match reject every valid
518            // reference, so resolve it as a global field instead.
519            let owner = type_owner_of(analyzer, target);
520            let kind = if owner.is_some() {
521                TargetKind::MemberField
522            } else {
523                TargetKind::GlobalField
524            };
525            let enum_owner_kind = owner
526                .as_ref()
527                .map(|owner| classify_enum_owner(analyzer, owner))
528                .unwrap_or(EnumOwnerKind::NonEnum);
529            let mut spec = Self::new(
530                target.clone(),
531                kind,
532                owner,
533                target.identifier().to_string(),
534                None,
535                None,
536            );
537            spec.enum_owner_kind = enum_owner_kind;
538            return Some(spec);
539        }
540
541        if target.is_function() {
542            // Free functions declared inside a namespace have a module owner; that namespace is
543            // not a call receiver, so resolve them as free functions rather than methods.
544            let owner_resolution = target_type_owner_resolution(analyzer, target);
545            let owner_is_forward_declaration = owner_resolution
546                .as_ref()
547                .is_some_and(|owner| owner.is_forward_declaration);
548            let owner = owner_resolution.map(|owner| owner.unit);
549            let kind = if owner.as_ref().is_some_and(|owner| {
550                target.identifier() == owner.identifier()
551                    || analyzer
552                        .cpp
553                        .and_then(|cpp| cpp.template_metadata(owner))
554                        .is_some_and(|metadata| metadata.primary_name == target.identifier())
555            }) {
556                TargetKind::Constructor
557            } else if owner.is_some() {
558                TargetKind::Method
559            } else {
560                TargetKind::FreeFunction
561            };
562            let mut spec = Self::new(
563                target.clone(),
564                kind,
565                owner,
566                target.identifier().to_string(),
567                cpp_callable_arity(analyzer, target),
568                cpp_callable_parameter_types(analyzer, target),
569            );
570            spec.owner_is_forward_declaration = owner_is_forward_declaration;
571            spec.callable_has_definition_body =
572                callable_target_has_definition_body(analyzer, target);
573            return Some(spec);
574        }
575
576        if target.is_macro() {
577            return Some(Self::new(
578                target.clone(),
579                TargetKind::Macro,
580                None,
581                target.identifier().to_string(),
582                None,
583                None,
584            ));
585        }
586
587        None
588    }
589
590    pub fn with_visible_callable_arities<'a>(
591        &'a self,
592        analyzer: &CppGraphSource<'_>,
593        cpp: &dyn CppSource,
594        visibility: &VisibilityIndex<'_>,
595        file: &ProjectFile,
596        prepared: &PreparedSyntaxTree,
597    ) -> Cow<'a, Self> {
598        let macro_parameter_arity =
599            visibility.callable_parameter_macro_arity(&self.target, self.param_types.as_deref());
600        let activated_callable_arities =
601            visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
602        if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
603            return Cow::Borrowed(self);
604        }
605        let mut effective = self.clone();
606        if let Some(macro_parameter_arity) = macro_parameter_arity {
607            effective.callable_arity = Some(macro_parameter_arity);
608        }
609        effective.activated_callable_arities = activated_callable_arities;
610        Cow::Owned(effective)
611    }
612
613    pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
614        let base = self.callable_arity?;
615        Some(
616            self.activated_callable_arities
617                .iter()
618                .filter(|candidate| candidate.activation_byte <= byte)
619                .fold(base, |arity, candidate| {
620                    merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
621                }),
622        )
623    }
624
625    pub fn new(
626        target: CodeUnit,
627        kind: TargetKind,
628        owner: Option<CodeUnit>,
629        member_name: String,
630        callable_arity: Option<CallableArity>,
631        param_types: Option<Vec<String>>,
632    ) -> Self {
633        Self {
634            target,
635            kind,
636            owner,
637            member_name,
638            callable_arity,
639            activated_callable_arities: Vec::new(),
640            param_types,
641            enum_owner_kind: EnumOwnerKind::NonEnum,
642            owner_is_forward_declaration: false,
643            callable_has_definition_body: false,
644        }
645    }
646}
647
648fn callable_target_has_definition_body(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> bool {
649    analyzer
650        .signature_metadata(target)
651        .iter()
652        .any(|metadata| !metadata.is_declaration_only())
653}
654
655fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
656    LogicalSymbolKey {
657        kind: unit.kind(),
658        fq_name: unit.fq_name(),
659        signature: unit.signature().map(str::to_string),
660    }
661}
662
663fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
664    let Some(facts) = analyzer.declaration_source_properties(owner) else {
665        return EnumOwnerKind::NonEnum;
666    };
667    let Some(first) = facts.first() else {
668        return EnumOwnerKind::NonEnum;
669    };
670    if facts.iter().all(|fact| fact.enum_kind == first.enum_kind) {
671        first.enum_kind
672    } else {
673        EnumOwnerKind::NonEnum
674    }
675}
676
677#[derive(Clone, PartialEq, Eq, Hash)]
678pub struct CppScanBinding {
679    pub unit: Option<CodeUnit>,
680    pub type_name: Option<String>,
681    pub indirection: i32,
682}
683
684impl CppScanBinding {
685    pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
686        Self::from_type_name(cpp_name_for(&unit), Some(unit), indirection)
687    }
688
689    pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
690        Self {
691            type_name: Some(type_name),
692            unit,
693            indirection,
694        }
695    }
696
697    pub fn as_arg_type(&self) -> Option<CppArgType> {
698        let name = self
699            .type_name
700            .clone()
701            .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
702        Some(CppArgType {
703            name,
704            unit: self.unit.clone(),
705            indirection: self.indirection,
706            pointee_const: false,
707        })
708    }
709}
710
711type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
712pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
713pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
714type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
715type MacroEnvironmentCheckpointCell = Arc<OnceLock<MacroEnvironmentCheckpoints>>;
716type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
717type MacroLexicalTemplateCache =
718    HashMap<(ProjectFile, usize), Option<crate::graph::macro_lexical::MacroTemplate>>;
719
720type MacroLocalBindingTemplateCache =
721    HashMap<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>;
722type MacroReplacementBodyCache = HashMap<(ProjectFile, usize), Option<Arc<ParsedReplacementBody>>>;
723type MacroTypeParameterCache = HashMap<(ProjectFile, usize), Option<Arc<[usize]>>>;
724type StructuredIncludeFactCell = Arc<OnceLock<Arc<[StructuredIncludeFact]>>>;
725
726struct StructuredIncludeFact {
727    start_byte: usize,
728    end_byte: usize,
729    path: String,
730}
731
732#[derive(Clone, Default)]
733pub struct MacroEnvironment {
734    bindings: HashMap<String, MacroBinding>,
735    known_undefined_names: HashSet<String>,
736    /// Names the translation unit's compile command proves defined (#2011):
737    /// the `-D`s that survive command ordering, intersected across every
738    /// configuration naming the TU. Seeded once at TU start. An explicit
739    /// `#undef` seen later lands in `known_undefined_names` and wins.
740    build_proven_defines: HashSet<String>,
741    unknown_names: bool,
742    applied_pragma_once_files: HashSet<ProjectFile>,
743    maybe_applied_pragma_once_files: HashSet<ProjectFile>,
744}
745
746/// How many macro events one checkpoint window may cover.
747///
748/// A request for an environment replays only the events between the nearest
749/// earlier checkpoint and its own frontier, so one file's whole scan costs its
750/// event count (the checkpoint build) plus this many applications per request,
751/// whatever order the requests arrive in. The forward cursor this replaced was
752/// optimal for one worker reading one file in byte order and quadratic for the
753/// inverse, which asks many workers for positions that move backwards (#1496).
754pub const MACRO_ENVIRONMENT_CHECKPOINT_STRIDE: usize = 32;
755
756/// One event prefix of a file whose environment the index keeps.
757struct MacroEnvironmentCheckpoint {
758    /// How many of the file's events this environment has applied.
759    frontier: usize,
760    environment: Arc<MacroEnvironment>,
761}
762
763/// The replay checkpoints for one file's macro events, ascending by frontier
764/// and always starting at frontier zero (the compile-proven defines alone).
765///
766/// Checkpoints use an adaptive stride no greater than
767/// [`MACRO_ENVIRONMENT_CHECKPOINT_STRIDE`], and one lands directly after every
768/// `#include` event. Applying an include event replays the included file's
769/// complete event list, so keeping one there holds that unbounded cost out of
770/// every later replay window.
771struct MacroEnvironmentCheckpoints {
772    checkpoints: Vec<MacroEnvironmentCheckpoint>,
773}
774
775impl MacroEnvironmentCheckpoints {
776    /// The latest checkpoint at or before `frontier`.
777    fn at_or_before(&self, frontier: usize) -> &MacroEnvironmentCheckpoint {
778        let index = self
779            .checkpoints
780            .partition_point(|checkpoint| checkpoint.frontier <= frontier);
781        assert!(
782            index > 0,
783            "a checkpoint vector starts at frontier zero, which precedes every request"
784        );
785        &self.checkpoints[index - 1]
786    }
787}
788
789fn clone_or_default_file_cell<T: Default + Clone>(
790    cells: &Mutex<HashMap<ProjectFile, T>>,
791    file: &ProjectFile,
792    poison_message: &str,
793) -> T {
794    cells
795        .lock()
796        .expect(poison_message)
797        .entry(file.clone())
798        .or_default()
799        .clone()
800}
801
802impl MacroEnvironment {
803    fn binding(&self, name: &str) -> Option<&MacroBinding> {
804        self.bindings.get(name)
805    }
806
807    fn may_bind(&self, name: &str) -> bool {
808        self.bindings.contains_key(name) || self.unknown_names
809    }
810
811    fn insert(&mut self, name: String, binding: MacroBinding) {
812        self.known_undefined_names.remove(&name);
813        self.bindings.insert(name, binding);
814    }
815
816    fn remove(&mut self, name: &str) {
817        self.bindings.remove(name);
818        self.known_undefined_names.insert(name.to_string());
819    }
820
821    fn remove_known_undefined(&mut self, name: &str) {
822        self.known_undefined_names.remove(name);
823    }
824
825    fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
826        for binding in self.bindings.values_mut() {
827            *binding = MacroBinding::uncertain_from(binding, source, byte);
828        }
829        self.known_undefined_names.clear();
830        // An untracked include could `#undef` a command-line define, so the
831        // may-hold filter must stop treating the build facts as decisive from
832        // here on. The additive proof path keeps its facts: they still hold at
833        // the include chain's activation point.
834        self.build_proven_defines.clear();
835        self.unknown_names = true;
836    }
837
838    fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
839        guards.iter().all(|guard| self.guard_may_hold(guard))
840    }
841
842    fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
843        self.boolean_guard_may_hold(&guard.as_boolean_expression())
844    }
845
846    fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
847        match expression {
848            BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
849            BooleanGuardExpression::Undefined(name) => {
850                self.bindings
851                    .get(name)
852                    .is_none_or(|binding| !binding.is_exact())
853                    && (!self.build_proven_defines.contains(name)
854                        || self.known_undefined_names.contains(name))
855            }
856            BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
857            BooleanGuardExpression::Opaque(_)
858            | BooleanGuardExpression::NegatedOpaque(_)
859            | BooleanGuardExpression::Constant(true) => true,
860            BooleanGuardExpression::Constant(false) => false,
861            BooleanGuardExpression::All(expressions) => expressions
862                .iter()
863                .all(|expression| self.boolean_guard_may_hold(expression)),
864            BooleanGuardExpression::Any(expressions) => expressions
865                .iter()
866                .any(|expression| self.boolean_guard_may_hold(expression)),
867        }
868    }
869}
870
871fn any_visible_source_file_matches(
872    visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
873    file: &ProjectFile,
874    mut predicate: impl FnMut(&ProjectFile) -> bool,
875) -> bool {
876    match visible_source_files_by_root.get(file) {
877        Some(files) => files.iter().any(predicate),
878        None => predicate(file),
879    }
880}
881
882#[derive(Clone)]
883pub enum EffectiveUsingTarget {
884    Ordinary {
885        name: String,
886        target_components: Vec<String>,
887        global: bool,
888    },
889    Namespace {
890        namespace_components: Vec<String>,
891        global: bool,
892    },
893}
894
895#[derive(Clone)]
896pub struct OrdinaryTypeImport {
897    pub target: EffectiveUsingTarget,
898    pub source: ProjectFile,
899    pub declaration_byte: usize,
900    pub scope_start: usize,
901    pub scope_end: usize,
902    pub scope_depth: usize,
903    pub block_scope: bool,
904    pub lexical_depth: usize,
905    pub declaration_namespace: Vec<String>,
906    pub namespace_scope: Option<Vec<String>>,
907    pub resolved_target_components: Option<Vec<String>>,
908    pub required_guards: HashSet<PreprocessorGuard>,
909}
910
911#[derive(Clone)]
912pub struct ConditionalIncludeProjection {
913    pub activation_byte: usize,
914    pub required_guards: HashSet<PreprocessorGuard>,
915    /// The subset of `required_guards` a lone `#if` contributes: a branch no
916    /// sibling `#else` completes, so no configuration is obliged to take it.
917    /// [`IncludePathAdmission::Compatible`] still demands those.
918    pub partial_guards: HashSet<PreprocessorGuard>,
919}
920
921#[derive(Default)]
922pub struct SourceUsingIndex {
923    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
924    pub directives: Vec<OrdinaryTypeImport>,
925}
926
927#[derive(Default)]
928pub struct ProjectUsingIndex {
929    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
930    pub directives: Vec<OrdinaryTypeImport>,
931}
932
933type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
934
935pub struct EffectiveUsingIndex {
936    projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
937}
938
939impl EffectiveUsingIndex {
940    fn new(_root: ProjectFile) -> Self {
941        Self {
942            projected_by_name: Mutex::new(HashMap::default()),
943        }
944    }
945
946    pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
947        self.projected_by_name
948            .lock()
949            .expect("C++ effective-using projection cache poisoned")
950            .entry(name.to_string())
951            .or_default()
952            .clone()
953    }
954}
955
956pub enum OrdinaryTypeImportResolution {
957    Resolved {
958        target: CodeUnit,
959        target_components: Vec<String>,
960        lexical_depth: usize,
961        is_direct: bool,
962    },
963    Ambiguous {
964        lexical_depth: usize,
965    },
966    Missing,
967}
968
969type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
970type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
971type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
972type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
973type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
974type ParserAliasTargetMatchCell = Arc<OnceLock<bool>>;
975type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
976type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
977
978/// One callable declaration's inputs to [`VisibilityIndex::same_logical_callable`],
979/// read from its declaration syntax rather than from its persisted signature
980/// string: the comparable shape of each parameter, and the trailing identity
981/// suffix that shape does not carry.
982struct ExtractedComparable {
983    shapes: Vec<CppComparableSlot>,
984    suffix: String,
985    /// The template levels of the same occurrence, or `None` when its syntax
986    /// is unavailable or outside what [`crate::template_heads`] models.
987    template: Option<CppTemplateContext>,
988}
989
990/// How many alias hops [`VisibilityIndex::same_logical_callable`] follows
991/// before giving up on a written type name. A visited set already stops a
992/// cycle; this stops an adversarially long chain from costing a lookup per hop.
993const MAX_COMPARABLE_ALIAS_HOPS: usize = 32;
994
995/// Guard admission for a conditional include path. Exact visibility
996/// (`foreign_declaration_reachable_at_reference`) always uses `Proven`: the
997/// effective source and build context must imply the path. Candidate discovery
998/// (`foreign_declaration_may_be_reachable_from_raw_guards`) takes
999/// `include_path_admission(file)`, which is `Compatible` for completed families
1000/// when no compile coverage describes the file; partial-family requirements
1001/// still need proof. Discovery alone never establishes an exact definition or
1002/// a proven inverse hit (#6902).
1003#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1004enum IncludePathAdmission {
1005    Proven,
1006    Compatible,
1007}
1008
1009impl IncludePathAdmission {
1010    fn admits(
1011        self,
1012        required: &HashSet<PreprocessorGuard>,
1013        partial: &HashSet<PreprocessorGuard>,
1014        reference_guards: Option<&HashSet<PreprocessorGuard>>,
1015    ) -> bool {
1016        match self {
1017            Self::Proven => guard_requirements_hold_at_reference(required, reference_guards),
1018            Self::Compatible => {
1019                guard_requirements_hold_at_reference(partial, reference_guards)
1020                    && guards_compatible_at_reference(required, reference_guards)
1021            }
1022        }
1023    }
1024}
1025
1026/// Per-query C++ visibility facts with a borrowed source and scope proof.
1027/// Cache only `data()` across calls, and reattach the current source and token
1028/// with `from_data`; cloning the analyzer would lose its query read cache.
1029pub struct VisibilityIndex<'a> {
1030    cpp: &'a dyn CppSource,
1031    token: QueryToken<'a>,
1032    data: Arc<VisibilityIndexData>,
1033}
1034
1035impl std::ops::Deref for VisibilityIndex<'_> {
1036    type Target = VisibilityIndexData;
1037
1038    fn deref(&self) -> &Self::Target {
1039        &self.data
1040    }
1041}
1042
1043/// Owned visibility facts. They retain no source or request-scope borrow.
1044/// The caller keeps these facts inside the query that built them.
1045pub struct VisibilityIndexData {
1046    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1047    visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
1048    global_field_internal_linkage: HashMap<CodeUnit, bool>,
1049    visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
1050    alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
1051    visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
1052    parser_alias_target_matches:
1053        RwLock<HashMap<(ProjectFile, String, LogicalSymbolKey), ParserAliasTargetMatchCell>>,
1054    ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
1055    project_using_index: OnceLock<ProjectUsingIndex>,
1056    namespace_alias_sites: Mutex<HashMap<ProjectFile, super::namespace_alias::NamespaceAliasCell>>,
1057    callable_reference_specs:
1058        Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
1059    structured_include_fact_cells: Mutex<HashMap<ProjectFile, StructuredIncludeFactCell>>,
1060    include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
1061    compile_proven_guard_cells: Mutex<HashMap<ProjectFile, Arc<HashSet<PreprocessorGuard>>>>,
1062    include_path_admission_cells: Mutex<HashMap<ProjectFile, IncludePathAdmission>>,
1063    conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
1064    #[cfg(any(test, feature = "test-support"))]
1065    conditional_include_projection_index_build_count: AtomicUsize,
1066    #[cfg(any(test, feature = "test-support"))]
1067    conditional_include_projection_state_count: AtomicUsize,
1068    #[cfg(any(test, feature = "test-support"))]
1069    conditional_include_target_state_count: AtomicUsize,
1070    #[cfg(any(test, feature = "test-support"))]
1071    include_activation_build_count: AtomicUsize,
1072    #[cfg(any(test, feature = "test-support"))]
1073    using_donor_activation_count: AtomicUsize,
1074    #[cfg(any(test, feature = "test-support"))]
1075    using_namespace_lookup_count: AtomicUsize,
1076    #[cfg(any(test, feature = "test-support"))]
1077    using_name_candidate_inspection_count: AtomicUsize,
1078    #[cfg(any(test, feature = "test-support"))]
1079    callable_reference_spec_build_count: AtomicUsize,
1080    #[cfg(any(test, feature = "test-support"))]
1081    alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
1082    #[cfg(any(test, feature = "test-support"))]
1083    visible_parser_alias_name_set_build_count: AtomicUsize,
1084    parser_alias_fallback_calls: AtomicUsize,
1085    parser_alias_fallback_files: AtomicUsize,
1086    parser_alias_source_parses: AtomicUsize,
1087    parser_alias_fallback_elapsed_micros: AtomicUsize,
1088    field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
1089    structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
1090    callable_comparables: Mutex<HashMap<CodeUnit, Option<Arc<ExtractedComparable>>>>,
1091    class_definition_agreements: Mutex<HashMap<(CodeUnit, CodeUnit), bool>>,
1092    /// Complete callable metadata is expensive for reconciled C++ members:
1093    /// each read can rebuild the declaration/definition evidence. Reuse it
1094    /// across the member, arity, identity, and navigation phases of this
1095    /// visibility query, with an explicit cap so the query never retains a
1096    /// workspace-sized unit table.
1097    callable_metadata: Mutex<HashMap<CodeUnit, Arc<Vec<SignatureMetadata>>>>,
1098    comparable_name_declarations: Mutex<HashMap<StructuredTypeName, Option<CodeUnit>>>,
1099    indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
1100    indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
1101    precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1102    c_tag_kind_cache: Mutex<HashMap<CodeUnit, Option<CppCTagKind>>>,
1103    c_tag_complete_definition_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1104    macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
1105    macro_event_name_sets: Mutex<HashMap<ProjectFile, Arc<HashSet<String>>>>,
1106    pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
1107    // The environment at selected event prefixes of each file, built once and read by every
1108    // worker. The authoritative differential shares this index across target workers whose
1109    // frontiers interleave arbitrarily and move backwards, so a forward cursor -- per worker or
1110    // not -- replayed a file's events once per backward request. Checkpoints answer any position
1111    // with a binary search and at most one stride of replay, and being immutable they need no
1112    // per-worker copy (#1496).
1113    macro_environment_checkpoints: Mutex<HashMap<ProjectFile, MacroEnvironmentCheckpointCell>>,
1114    macro_replacements: Mutex<MacroReplacementCache>,
1115    macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
1116    pub(crate) macro_lexical_templates: Mutex<MacroLexicalTemplateCache>,
1117    macro_lexical_indexes:
1118        Mutex<HashMap<ProjectFile, Arc<crate::graph::macro_lexical::MacroLexicalIndex>>>,
1119    macro_replacement_bodies: Mutex<MacroReplacementBodyCache>,
1120    macro_type_parameters: Mutex<MacroTypeParameterCache>,
1121    callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
1122    #[cfg(any(test, feature = "test-support"))]
1123    pub macro_replacement_parse_count: AtomicUsize,
1124    #[cfg(any(test, feature = "test-support"))]
1125    pub macro_event_application_count: AtomicUsize,
1126    /// How many files had their checkpoint vector built. One build per file
1127    /// per query even when workers race for the same file.
1128    #[cfg(any(test, feature = "test-support"))]
1129    pub macro_environment_checkpoint_build_count: AtomicUsize,
1130    /// How many requests landed off a checkpoint and so had to copy one and
1131    /// replay the events after it.
1132    #[cfg(any(test, feature = "test-support"))]
1133    pub macro_environment_copy_count: AtomicUsize,
1134    #[cfg(any(test, feature = "test-support"))]
1135    pub macro_environment_request_count: AtomicUsize,
1136    cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
1137    cpp_template_families: HashMap<String, Vec<CodeUnit>>,
1138    #[cfg(any(test, feature = "test-support"))]
1139    qualified_candidate_inspections: AtomicUsize,
1140    #[cfg(any(test, feature = "test-support"))]
1141    target_preserving_type_resolution_count: AtomicUsize,
1142    #[cfg(any(test, feature = "test-support"))]
1143    visibility_identifier_lookup_count: usize,
1144    #[cfg(any(test, feature = "test-support"))]
1145    visibility_identifier_batch_count: usize,
1146}
1147
1148impl Drop for VisibilityIndexData {
1149    fn drop(&mut self) {
1150        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_none() {
1151            return;
1152        }
1153        #[cfg(any(test, feature = "test-support"))]
1154        eprintln!(
1155            "BIFROST_CPP_MACRO_STATS requests={} copies={} checkpoint_builds={} applications={}",
1156            self.macro_environment_request_count.load(Ordering::Relaxed),
1157            self.macro_environment_copy_count.load(Ordering::Relaxed),
1158            self.macro_environment_checkpoint_build_count
1159                .load(Ordering::Relaxed),
1160            self.macro_event_application_count.load(Ordering::Relaxed),
1161        );
1162        let calls = self.parser_alias_fallback_calls.load(Ordering::Relaxed);
1163        if calls == 0 {
1164            return;
1165        }
1166        eprintln!(
1167            "BIFROST_CPP_ALIAS_FALLBACK_STATS calls={} files={} source_parses={} elapsed_ms={}",
1168            calls,
1169            self.parser_alias_fallback_files.load(Ordering::Relaxed),
1170            self.parser_alias_source_parses.load(Ordering::Relaxed),
1171            self.parser_alias_fallback_elapsed_micros
1172                .load(Ordering::Relaxed)
1173                / 1_000,
1174        );
1175    }
1176}
1177
1178#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1179pub enum PreprocessorGuard {
1180    Defined(String),
1181    Undefined(String),
1182    Boolean(BooleanGuardExpression),
1183    Expression(String),
1184    NegatedExpression(String),
1185    Constant(bool),
1186}
1187
1188#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1189pub enum BooleanGuardExpression {
1190    Defined(String),
1191    Undefined(String),
1192    Truthy(String),
1193    Falsy(String),
1194    Opaque(String),
1195    NegatedOpaque(String),
1196    All(Vec<BooleanGuardExpression>),
1197    Any(Vec<BooleanGuardExpression>),
1198    Constant(bool),
1199}
1200
1201impl BooleanGuardExpression {
1202    fn negated(&self) -> Self {
1203        match self {
1204            Self::Defined(name) => Self::Undefined(name.clone()),
1205            Self::Undefined(name) => Self::Defined(name.clone()),
1206            Self::Truthy(name) => Self::Falsy(name.clone()),
1207            Self::Falsy(name) => Self::Truthy(name.clone()),
1208            Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
1209            Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
1210            Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
1211            Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
1212            Self::Constant(value) => Self::Constant(!value),
1213        }
1214    }
1215
1216    fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
1217        Self::normalized(expressions, true)
1218    }
1219
1220    fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
1221        Self::normalized(expressions, false)
1222    }
1223
1224    fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
1225        let mut normalized = Vec::new();
1226        for expression in expressions {
1227            match expression {
1228                Self::All(nested) if conjunction => normalized.extend(nested),
1229                Self::Any(nested) if !conjunction => normalized.extend(nested),
1230                Self::Constant(value) if value == conjunction => {}
1231                Self::Constant(value) => return Self::Constant(value),
1232                expression => normalized.push(expression),
1233            }
1234        }
1235        normalized.sort_unstable();
1236        normalized.dedup();
1237        match normalized.len() {
1238            0 => Self::Constant(conjunction),
1239            1 => normalized.pop().expect("one Boolean guard expression"),
1240            _ if conjunction => Self::All(normalized),
1241            _ => Self::Any(normalized),
1242        }
1243    }
1244
1245    fn implies(&self, required: &Self) -> bool {
1246        if self == required
1247            || matches!(self, Self::Constant(false))
1248            || matches!(required, Self::Constant(true))
1249        {
1250            return true;
1251        }
1252        if matches!(
1253            (self, required),
1254            (Self::Truthy(active), Self::Defined(required))
1255                | (Self::Undefined(active), Self::Falsy(required))
1256                if active == required
1257        ) {
1258            return true;
1259        }
1260        match self {
1261            Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
1262            Self::All(active) => match required {
1263                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1264                _ => active.iter().any(|expression| expression.implies(required)),
1265            },
1266            _ => match required {
1267                Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
1268                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1269                _ => false,
1270            },
1271        }
1272    }
1273
1274    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1275        match self {
1276            Self::Defined(name)
1277            | Self::Undefined(name)
1278            | Self::Truthy(name)
1279            | Self::Falsy(name) => name == macro_name,
1280            // Opaque expressions have structured conditional ownership but no
1281            // structured macro operands, so any mutation may change them.
1282            Self::Opaque(_) | Self::NegatedOpaque(_) => true,
1283            Self::All(expressions) | Self::Any(expressions) => expressions
1284                .iter()
1285                .any(|expression| expression.may_depend_on_macro(macro_name)),
1286            Self::Constant(_) => false,
1287        }
1288    }
1289
1290    pub fn heap_size(&self) -> usize {
1291        match self {
1292            Self::Defined(value)
1293            | Self::Undefined(value)
1294            | Self::Truthy(value)
1295            | Self::Falsy(value)
1296            | Self::Opaque(value)
1297            | Self::NegatedOpaque(value) => value.len(),
1298            Self::All(expressions) | Self::Any(expressions) => {
1299                expressions
1300                    .iter()
1301                    .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
1302                        size.saturating_add(std::mem::size_of::<Self>())
1303                            .saturating_add(expression.heap_size())
1304                    })
1305            }
1306            Self::Constant(_) => 0,
1307        }
1308    }
1309}
1310
1311impl PreprocessorGuard {
1312    fn as_boolean_expression(&self) -> BooleanGuardExpression {
1313        match self {
1314            Self::Defined(name) => BooleanGuardExpression::Defined(name.clone()),
1315            Self::Undefined(name) => BooleanGuardExpression::Undefined(name.clone()),
1316            Self::Boolean(expression) => expression.clone(),
1317            Self::Constant(value) => BooleanGuardExpression::Constant(*value),
1318            Self::Expression(expression) => BooleanGuardExpression::Opaque(expression.clone()),
1319            Self::NegatedExpression(expression) => {
1320                BooleanGuardExpression::NegatedOpaque(expression.clone())
1321            }
1322        }
1323    }
1324
1325    fn negated(&self) -> Self {
1326        match self {
1327            Self::Defined(name) => Self::Undefined(name.clone()),
1328            Self::Undefined(name) => Self::Defined(name.clone()),
1329            Self::Boolean(expression) => Self::Boolean(expression.negated()),
1330            Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1331            Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1332            Self::Constant(value) => Self::Constant(!value),
1333        }
1334    }
1335
1336    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1337        match self {
1338            Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1339            Self::Boolean(expression) => expression.may_depend_on_macro(macro_name),
1340            // These expressions could not be lowered to a Boolean operand
1341            // tree, so their dependencies remain unknown.
1342            Self::Expression(_) | Self::NegatedExpression(_) => true,
1343            Self::Constant(_) => false,
1344        }
1345    }
1346}
1347
1348#[derive(Clone, PartialEq, Eq)]
1349pub enum MacroDefinition {
1350    Object {
1351        replacement: String,
1352    },
1353    Function {
1354        parameters: Vec<String>,
1355        replacement: String,
1356    },
1357    VariadicFunction {
1358        parameters: Vec<String>,
1359        replacement: String,
1360    },
1361    Unsupported,
1362}
1363
1364#[derive(Clone, Debug, PartialEq, Eq)]
1365pub enum MacroIncludeProtection {
1366    MacroGuard(String),
1367    PragmaOnce,
1368    None,
1369}
1370
1371enum ParsedMacroReplacement {
1372    Parsed { source: String, tree: Tree },
1373    Unsupported,
1374}
1375
1376/// The sentinel that gives a function-like macro replacement a parseable
1377/// statement context after phase-2 splicing. The only bytes ahead of it are
1378/// this prefix.
1379const MACRO_BODY_SENTINEL_PREFIX: &str = "void __bifrost_macro_body() { ";
1380const MACRO_BODY_SENTINEL_SUFFIX: &str = "; }";
1381
1382// Each parsed byte owns its physical source byte, or an empty range for
1383// synthetic punctuation. Deleted splices own no parsed bytes.
1384type MacroOriginMap = Box<[std::ops::Range<usize>]>;
1385
1386/// A function-like macro replacement parsed inside a sentinel function body.
1387///
1388/// Tree-sitter keeps a `#define NAME(a) ...` replacement as one opaque
1389/// `preproc_arg`. Wrapping that exact byte slice in a function body recovers
1390/// its statements, declarations, and member calls as ordinary C++ structure.
1391/// The spliced text begins at [`Self::body_offset`]. [`Self::file_range`]
1392/// subtracts that prefix and uses physical byte origins to recover a node
1393/// range in the defining replacement.
1394pub struct ParsedReplacementBody {
1395    pub source: String,
1396    pub tree: Tree,
1397    pub body_offset: usize,
1398    pub parameters: Vec<String>,
1399    /// Each parsed replacement byte maps to its original physical byte. A
1400    /// deleted splice creates a gap; a synthetic statement separator maps to
1401    /// an empty range, keeping source-backed ranges exact after substitution.
1402    original_offsets: MacroOriginMap,
1403}
1404
1405impl ParsedReplacementBody {
1406    /// The sentinel function body holding the replacement's statements.
1407    pub fn statements(&self) -> Option<Node<'_>> {
1408        first_descendant_of_kind(self.tree.root_node(), "function_definition")?
1409            .child_by_field_name("body")
1410    }
1411
1412    /// The byte range `node` occupies in the file that defines the macro.
1413    ///
1414    /// `replacement_start` is the logical replacement span's start byte.
1415    /// The origin map accounts for deleted continuations and any synthetic
1416    /// statement separators inserted while parsing the replacement.
1417    pub fn file_range(&self, node: Node<'_>, replacement_start: usize) -> std::ops::Range<usize> {
1418        assert!(
1419            node.start_byte() >= self.body_offset,
1420            "synthetic sentinel node cannot be mapped to a macro replacement"
1421        );
1422        assert!(
1423            node.end_byte() >= self.body_offset,
1424            "synthetic sentinel node cannot be mapped to a macro replacement"
1425        );
1426        let start_offset = node.start_byte() - self.body_offset;
1427        let end_offset = node.end_byte() - self.body_offset;
1428        assert!(start_offset <= end_offset);
1429        let start_origin = self
1430            .original_offsets
1431            .get(start_offset)
1432            .expect("replacement node start must have a source mapping")
1433            .start;
1434        let end_origin = if start_offset == end_offset {
1435            start_origin
1436        } else {
1437            self.original_offsets
1438                .get(end_offset - 1)
1439                .expect("replacement node end must have a source mapping")
1440                .end
1441        };
1442        assert!(start_origin <= end_origin);
1443        let start = replacement_start + start_origin;
1444        let end = replacement_start + end_origin;
1445        start..end
1446    }
1447
1448    /// Whether the replacement names the variadic argument pack.
1449    ///
1450    /// `__VA_ARGS__` parses as an ordinary identifier, so the sentinel tree
1451    /// gives no error for it even though the expansion it stands for is
1452    /// unknown at the definition. Reject it from the parsed tree rather than
1453    /// by scanning the replacement text.
1454    fn expands_variadic_arguments(&self) -> bool {
1455        let mut stack = vec![self.tree.root_node()];
1456        while let Some(node) = stack.pop() {
1457            if matches!(
1458                node.kind(),
1459                "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
1460            ) && node_text(node, &self.source) == "__VA_ARGS__"
1461            {
1462                return true;
1463            }
1464            push_named_children_reversed(node, &mut stack);
1465        }
1466        false
1467    }
1468}
1469
1470/// Parse the value of a C++ integer-literal token when it fits in `i128`.
1471pub fn parse_cpp_integer_literal(text: &str) -> Option<i128> {
1472    let compact = text.chars().filter(|ch| *ch != '\'').collect::<String>();
1473    let (radix, digits_start, digit_matches): (u32, usize, fn(char) -> bool) =
1474        if compact.starts_with("0x") || compact.starts_with("0X") {
1475            (16, 2, |ch| ch.is_ascii_hexdigit())
1476        } else if compact.starts_with("0b") || compact.starts_with("0B") {
1477            (2, 2, |ch| matches!(ch, '0' | '1'))
1478        } else if compact.starts_with('0') && compact.len() > 1 {
1479            (8, 0, |ch| matches!(ch, '0'..='7'))
1480        } else {
1481            (10, 0, |ch| ch.is_ascii_digit())
1482        };
1483    let digit_len = compact[digits_start..]
1484        .chars()
1485        .take_while(|ch| digit_matches(*ch))
1486        .map(char::len_utf8)
1487        .sum::<usize>();
1488    if digit_len == 0 {
1489        return None;
1490    }
1491    let digits_end = digits_start + digit_len;
1492    if !compact[digits_end..]
1493        .chars()
1494        .all(|ch| matches!(ch, 'u' | 'U' | 'l' | 'L' | 'z' | 'Z'))
1495    {
1496        return None;
1497    }
1498    i128::from_str_radix(&compact[digits_start..digits_end], radix).ok()
1499}
1500
1501#[derive(Clone)]
1502enum MacroLocalBindingTypeTemplate {
1503    Parameter(usize),
1504    Fixed(String),
1505}
1506
1507#[derive(Clone)]
1508struct MacroLocalBindingTemplate {
1509    name: String,
1510    declared_type: MacroLocalBindingTypeTemplate,
1511    pointer_depth: i32,
1512}
1513
1514/// A local declaration contributed by one structurally known function-like macro.
1515///
1516/// `type_node` points into the invocation syntax when the replacement's type
1517/// is one of the macro parameters. Consumers can therefore use their normal
1518/// lexical type resolver without parsing replacement text themselves.
1519/// `proven_unit` carries a structured C-tag resolution when the grammar splits
1520/// an explicit `struct` or `union` argument across recovery nodes.
1521pub struct MacroLocalBinding<'tree> {
1522    pub name: String,
1523    pub type_name: String,
1524    pub type_node: Option<Node<'tree>>,
1525    pub pointer_depth: i32,
1526    pub proven_unit: Option<CodeUnit>,
1527}
1528
1529/// The source-backed declaration introduced by a function-like macro. The
1530/// declaration belongs to the macro definition, while the reference range
1531/// returned by [`VisibilityIndex::macro_lexical_references`] belongs to the
1532/// definition body, invocation argument, or caller scope where substitution
1533/// makes the binding visible.
1534#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1535pub enum MacroLexicalBindingKind {
1536    Parameter,
1537    Local,
1538}
1539
1540#[derive(Clone, Debug, PartialEq, Eq)]
1541pub struct MacroLexicalBinding {
1542    pub definition: ProjectFile,
1543    pub kind: MacroLexicalBindingKind,
1544    pub name: String,
1545    pub name_range: std::ops::Range<usize>,
1546    pub declaration_range: std::ops::Range<usize>,
1547}
1548
1549/// Lexical macro references are query-local facts. A cancelled or bounded
1550/// walk is kept distinct from a complete empty enumeration so authoritative
1551/// inverse callers can preserve their completeness status.
1552#[derive(Clone, Debug, Default, PartialEq, Eq)]
1553pub struct MacroLexicalReferences {
1554    pub references: Vec<(std::ops::Range<usize>, MacroLexicalBinding)>,
1555    pub truncated: bool,
1556    pub cancelled: bool,
1557}
1558
1559fn macro_replacement_type_parameters(
1560    body: &ParsedReplacementBody,
1561    parameters: &[String],
1562) -> Option<Vec<usize>> {
1563    let mut found = Vec::new();
1564    let mut stack = vec![body.tree.root_node()];
1565    while let Some(node) = stack.pop() {
1566        let type_position = node.kind() == "type_identifier"
1567            || (node.kind() == "identifier"
1568                && node.parent().is_some_and(|parent| {
1569                    parent.kind() == "type_descriptor"
1570                        && parent.child_by_field_name("type") == Some(node)
1571                }));
1572        let offsetof_type_position = node.kind() == "identifier"
1573            && node
1574                .parent()
1575                .filter(|parent| parent.kind() == "argument_list")
1576                .and_then(|arguments| arguments.parent())
1577                .is_some_and(|call| {
1578                    call.kind() == "call_expression"
1579                        && call
1580                            .child_by_field_name("function")
1581                            .is_some_and(|function| {
1582                                function.kind() == "identifier"
1583                                    && node_text(function, &body.source) == "offsetof"
1584                            })
1585                        && call
1586                            .child_by_field_name("arguments")
1587                            .is_some_and(|arguments| {
1588                                argument_children(arguments).next() == Some(node)
1589                            })
1590                });
1591        if (type_position || offsetof_type_position)
1592            && let Some(index) = parameters
1593                .iter()
1594                .position(|parameter| parameter == node_text(node, &body.source))
1595            && !found.contains(&index)
1596        {
1597            found.push(index);
1598        }
1599        push_named_children_reversed(node, &mut stack);
1600    }
1601    (!found.is_empty()).then_some(found)
1602}
1603
1604fn macro_replacement_type_parameter(
1605    body: &ParsedReplacementBody,
1606    parameters: &[String],
1607) -> Option<usize> {
1608    let mut parameters = macro_replacement_type_parameters(body, parameters)?;
1609    (parameters.len() == 1).then(|| parameters.pop().unwrap())
1610}
1611
1612/// The grammar retains a type-shaped macro invocation as a macro type
1613/// specifier even when it stands alone in a C block. Its descriptor is one
1614/// actual argument, just as the argument list of an expression-shaped call is.
1615pub(crate) fn function_macro_invocation_parts(node: Node<'_>) -> Option<(Node<'_>, Vec<Node<'_>>)> {
1616    let (name, arguments) = match node.kind() {
1617        "call_expression" => (
1618            node.child_by_field_name("function")?,
1619            argument_children(node.child_by_field_name("arguments")?).collect(),
1620        ),
1621        "macro_type_specifier" => (
1622            node.child_by_field_name("name")?,
1623            vec![node.child_by_field_name("type")?],
1624        ),
1625        _ => return None,
1626    };
1627    (name.kind() == "identifier").then_some((name, arguments))
1628}
1629
1630pub(crate) fn macro_type_argument_node<'tree>(
1631    node: Node<'tree>,
1632    source: &str,
1633) -> Option<Node<'tree>> {
1634    match node.kind() {
1635        "type_descriptor" => {
1636            let type_child = node
1637                .child_by_field_name("type")
1638                .or_else(|| first_type_child(node))?;
1639            for index in (0..node.named_child_count()).rev() {
1640                let child = node.named_child(index)?;
1641                if child != type_child
1642                    && matches!(
1643                        child.kind(),
1644                        "identifier"
1645                            | "type_identifier"
1646                            | "qualified_identifier"
1647                            | "scoped_type_identifier"
1648                    )
1649                {
1650                    return Some(child);
1651                }
1652            }
1653            macro_type_argument_node(type_child, source)
1654        }
1655        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => {
1656            node.child_by_field_name("name")
1657        }
1658        "identifier" if matches!(node_text(node, source), "struct" | "union") => node
1659            .next_named_sibling()
1660            .filter(|sibling| sibling.is_error() && sibling.named_child_count() == 1)
1661            .and_then(|error| error.named_child(0))
1662            .filter(|name| matches!(name.kind(), "identifier" | "type_identifier")),
1663        _ => cpp_name_component_nodes(node).is_some().then_some(node),
1664    }
1665}
1666
1667fn c_function_macro_argument<'tree>(
1668    node: Node<'tree>,
1669) -> Option<(Node<'tree>, usize, Node<'tree>)> {
1670    let mut current = node;
1671    while let Some(parent) = current.parent() {
1672        if parent.kind() == "argument_list" {
1673            let call = parent.parent().filter(|call| {
1674                call.kind() == "call_expression"
1675                    && call.child_by_field_name("arguments") == Some(parent)
1676                    && call
1677                        .child_by_field_name("function")
1678                        .is_some_and(|function| {
1679                            function.kind() == "identifier"
1680                                && !node_range_contains(function, current)
1681                        })
1682            })?;
1683            let mut actuals = argument_children(parent).enumerate();
1684            let (index, argument) = actuals.find(|(_, argument)| {
1685                node_range_contains(*argument, node)
1686                    && (argument.start_byte() == current.start_byte()
1687                        || argument.end_byte() == current.end_byte())
1688            })?;
1689            return Some((call, index, argument));
1690        }
1691        current = parent;
1692    }
1693    None
1694}
1695
1696/// Recover GLib's `g_autoptr(T) name = value` declaration from the CST shape
1697/// produced by tree-sitter-cpp for C source. The grammar retains the macro
1698/// invocation as the assignment's left operand and the declared name as one
1699/// adjacent `ERROR(identifier)` node, so no macro text splitting is needed.
1700/// The macro-declaration grammar reads the same line as a clean declaration
1701/// whose type is a `macro_type_specifier`; the `g_autoptr` name gate is what
1702/// keeps an arbitrary function-like type macro from becoming a binding.
1703fn recognized_c_macro_declarator_binding<'tree>(
1704    statement: Node<'tree>,
1705    source: &str,
1706) -> Option<MacroLocalBinding<'tree>> {
1707    if statement.kind() == "declaration" {
1708        let specifier = statement
1709            .child_by_field_name("type")
1710            .filter(|type_node| type_node.kind() == "macro_type_specifier")?;
1711        let macro_name = specifier
1712            .child_by_field_name("name")
1713            .filter(|name| name.kind() == "identifier")?;
1714        if node_text(macro_name, source) != "g_autoptr" {
1715            return None;
1716        }
1717        let type_node = specifier
1718            .child_by_field_name("type")
1719            .and_then(|descriptor| descriptor.child_by_field_name("type"))?;
1720        let declarator = statement
1721            .child_by_field_name("declarator")
1722            .filter(|declarator| declarator.kind() == "init_declarator")?;
1723        let name_node = declarator
1724            .child_by_field_name("declarator")
1725            .filter(|name| name.kind() == "identifier")?;
1726        declarator.child_by_field_name("value")?;
1727        let name = node_text(name_node, source).trim();
1728        let type_name = node_text(type_node, source).trim();
1729        if name.is_empty() || type_name.is_empty() {
1730            return None;
1731        }
1732        return Some(MacroLocalBinding {
1733            name: name.to_string(),
1734            type_name: type_name.to_string(),
1735            type_node: Some(type_node),
1736            pointer_depth: 1,
1737            proven_unit: None,
1738        });
1739    }
1740    let assignment = match statement.kind() {
1741        "assignment_expression" => statement,
1742        "expression_statement" => {
1743            single_named_child_matching(statement, |child| !child.is_extra())?
1744        }
1745        _ => return None,
1746    };
1747    if assignment.kind() != "assignment_expression" {
1748        return None;
1749    }
1750    let call = assignment.child_by_field_name("left")?;
1751    if call.kind() != "call_expression" {
1752        return None;
1753    }
1754    let function = call.child_by_field_name("function")?;
1755    if function.kind() != "identifier" || node_text(function, source) != "g_autoptr" {
1756        return None;
1757    }
1758    let arguments = call.child_by_field_name("arguments")?;
1759    let mut actuals = argument_children(arguments);
1760    let type_node = actuals.next()?;
1761    if actuals.next().is_some()
1762        || !matches!(
1763            type_node.kind(),
1764            "identifier"
1765                | "type_identifier"
1766                | "qualified_identifier"
1767                | "scoped_type_identifier"
1768                | "template_type"
1769        )
1770    {
1771        return None;
1772    }
1773    let name_node = (0..assignment.named_child_count())
1774        .filter_map(|index| assignment.named_child(index))
1775        .filter(|child| child.kind() == "ERROR")
1776        .filter_map(|error| {
1777            (error.named_child_count() == 1)
1778                .then(|| error.named_child(0))
1779                .flatten()
1780        })
1781        .find(|node| node.kind() == "identifier")?;
1782    let name = node_text(name_node, source).trim();
1783    let type_name = node_text(type_node, source).trim();
1784    if name.is_empty() || type_name.is_empty() {
1785        return None;
1786    }
1787    Some(MacroLocalBinding {
1788        name: name.to_string(),
1789        type_name: type_name.to_string(),
1790        type_node: Some(type_node),
1791        pointer_depth: 1,
1792        proven_unit: None,
1793    })
1794}
1795
1796#[derive(Clone, PartialEq, Eq)]
1797pub struct MacroBinding {
1798    source: ProjectFile,
1799    declaration_byte: usize,
1800    definition: MacroDefinition,
1801    exact: bool,
1802}
1803
1804impl MacroBinding {
1805    fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1806        Self {
1807            source: source.clone(),
1808            declaration_byte,
1809            definition: MacroDefinition::Unsupported,
1810            exact: false,
1811        }
1812    }
1813
1814    fn is_exact(&self) -> bool {
1815        self.exact
1816    }
1817
1818    fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1819        Self {
1820            source: source.clone(),
1821            declaration_byte,
1822            definition: current.definition.clone(),
1823            exact: false,
1824        }
1825    }
1826}
1827
1828/// The preprocessor conditionals whose truth decides whether one macro event
1829/// applies, by the start byte of each conditional node. Empty means the event
1830/// is unconditional.
1831///
1832/// [`VisibilityIndex::macro_event_condition_value`] needs exactly the
1833/// conditional ancestors that structurally contain the event, and deciding
1834/// containment means asking [`cpp_displaced_preprocessor_boundary`] for an
1835/// `#endif` tree-sitter displaced into error recovery -- a walk of the
1836/// conditional's whole subtree. That answer is a fact about the tree alone, so
1837/// it is settled once, when the events are collected, instead of on every
1838/// replay of the event (#1496).
1839type OwningPreprocessorConditionals = Box<[usize]>;
1840
1841#[derive(Clone)]
1842pub enum MacroEvent {
1843    Define {
1844        name: String,
1845        binding: MacroBinding,
1846        byte: usize,
1847        conditionals: OwningPreprocessorConditionals,
1848    },
1849    Undef {
1850        name: String,
1851        byte: usize,
1852        conditionals: OwningPreprocessorConditionals,
1853    },
1854    Include {
1855        targets: Vec<ProjectFile>,
1856        byte: usize,
1857        conditionals: OwningPreprocessorConditionals,
1858    },
1859    Invalidate {
1860        byte: usize,
1861    },
1862}
1863
1864impl MacroEvent {
1865    pub fn byte(&self) -> usize {
1866        match self {
1867            Self::Define { byte, .. }
1868            | Self::Undef { byte, .. }
1869            | Self::Include { byte, .. }
1870            | Self::Invalidate { byte } => *byte,
1871        }
1872    }
1873}
1874
1875#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1876pub enum CallArityEvidence {
1877    Exact(usize),
1878    Unknown,
1879}
1880
1881impl CallArityEvidence {
1882    pub fn exact(self) -> Option<usize> {
1883        match self {
1884            Self::Exact(arity) => Some(arity),
1885            Self::Unknown => None,
1886        }
1887    }
1888
1889    pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1890        self.exact().map(|arity| expected.accepts(arity))
1891    }
1892}
1893
1894struct CppAlias {
1895    name: String,
1896    target: String,
1897    namespace: Option<String>,
1898}
1899
1900type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1901
1902/// Why template-argument resolution failed. Definition diagnostics render
1903/// each mode differently; graph scans only care that the resolution is
1904/// unproven and match `Err(_)`.
1905#[derive(Debug, Clone, PartialEq, Eq)]
1906pub enum CppTemplateResolutionError {
1907    /// A template alias expansion revisited `alias`.
1908    AliasCycle { alias: CodeUnit },
1909    /// The explicit arguments do not bind to the declared template parameters.
1910    ArgumentBinding,
1911    /// Bound arguments do not substitute into the alias target's arguments.
1912    Substitution,
1913    /// No visible primary template declaration could be selected and
1914    /// reconciled for the specialization family.
1915    PrimarySelection,
1916    /// More than one applicable specialization remains and none is strictly
1917    /// more specialized than every other candidate.
1918    AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1919}
1920
1921/// The ambiguity candidates, deduplicated to one representative per visible
1922/// symbol so a diagnostic lists each contender once.
1923fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1924    let mut distinct: Vec<CodeUnit> = Vec::new();
1925    for unit in units {
1926        if !distinct
1927            .iter()
1928            .any(|existing| same_visible_symbol(existing, unit))
1929        {
1930            distinct.push(unit.clone());
1931        }
1932    }
1933    distinct
1934}
1935
1936/// Enumerate macro lexical references while constructing visibility only if
1937/// the bounded source walk finds a definition or identifier call candidate.
1938pub fn macro_lexical_references_with_visibility<'visibility, 'source: 'visibility, Factory>(
1939    visibility: Factory,
1940    file: &ProjectFile,
1941    root: Node<'_>,
1942    source: &str,
1943    max_references: usize,
1944    cancelled: impl FnMut() -> bool,
1945) -> MacroLexicalReferences
1946where
1947    Factory: FnOnce() -> &'visibility VisibilityIndex<'source>,
1948{
1949    crate::graph::macro_lexical::all_references(
1950        visibility,
1951        file,
1952        root,
1953        source,
1954        max_references,
1955        cancelled,
1956    )
1957}
1958
1959impl<'a> VisibilityIndex<'a> {
1960    pub fn data(&self) -> Arc<VisibilityIndexData> {
1961        Arc::clone(&self.data)
1962    }
1963
1964    pub fn from_data(
1965        cpp: &'a dyn CppSource,
1966        token: QueryToken<'a>,
1967        data: Arc<VisibilityIndexData>,
1968    ) -> Self {
1969        Self { cpp, token, data }
1970    }
1971
1972    pub fn cpp(&self) -> &'a dyn CppSource {
1973        self.cpp
1974    }
1975
1976    /// The request-scope proof this index was built with (issue #2414 step 3).
1977    pub fn token(&self) -> QueryToken<'a> {
1978        self.token
1979    }
1980
1981    /// Build the source-backed macro lexical index once per file in this
1982    /// visibility query. The entry cap keeps the query from retaining a
1983    /// workspace-sized collection when a batch visits many translation units.
1984    pub(crate) fn macro_lexical_index(
1985        &self,
1986        file: &ProjectFile,
1987        root: Node<'_>,
1988        source: &str,
1989    ) -> Arc<crate::graph::macro_lexical::MacroLexicalIndex> {
1990        if let Some(index) = self
1991            .macro_lexical_indexes
1992            .lock()
1993            .expect("macro lexical index cache poisoned")
1994            .get(file)
1995            .cloned()
1996        {
1997            debug_assert_eq!(
1998                index.source_len,
1999                source.len(),
2000                "macro lexical index for {file:?} was built from a different source"
2001            );
2002            return index;
2003        }
2004        let index = Arc::new(crate::graph::macro_lexical::build_index(
2005            self, file, root, source,
2006        ));
2007        let mut cache = self
2008            .macro_lexical_indexes
2009            .lock()
2010            .expect("macro lexical index cache poisoned");
2011        if let Some(existing) = cache.get(file).cloned() {
2012            return existing;
2013        }
2014        if cache.len() < MACRO_LEXICAL_INDEX_CACHE_CAP {
2015            cache.insert(file.clone(), Arc::clone(&index));
2016        }
2017        index
2018    }
2019
2020    /// The complete signature rows for one callable, shared by every
2021    /// candidate fact that the visibility query asks for. C++ reconciled
2022    /// members make this read substantially more expensive than an ordinary
2023    /// metadata lookup, so keep the answer on this per-query index and cap it
2024    /// explicitly.
2025    fn callable_metadata(&self, unit: &CodeUnit) -> Arc<Vec<SignatureMetadata>> {
2026        if let Some(metadata) = self
2027            .callable_metadata
2028            .lock()
2029            .expect("C++ callable metadata cache poisoned")
2030            .get(unit)
2031            .cloned()
2032        {
2033            return metadata;
2034        }
2035        let metadata = Arc::new(self.cpp.stored_signature_metadata(unit));
2036        let mut cache = self
2037            .callable_metadata
2038            .lock()
2039            .expect("C++ callable metadata cache poisoned");
2040        if cache.len() < CALLABLE_METADATA_CACHE_CAP {
2041            cache.insert(unit.clone(), Arc::clone(&metadata));
2042        }
2043        metadata
2044    }
2045
2046    /// Callable linkage backed by the query-local metadata memo.
2047    pub fn callable_linkage(&self, unit: &CodeUnit) -> Option<CallableLinkage> {
2048        crate::identity::cpp_callable_linkage_from_metadata(
2049            self.callable_metadata(unit).iter().cloned(),
2050        )
2051    }
2052
2053    /// Callable arity backed by the same query-local metadata memo.
2054    pub fn callable_arity(&self, unit: &CodeUnit) -> Option<CallableArity> {
2055        brokk_bifrost_core::analyzer::model::first_callable_arity(
2056            self.callable_metadata(unit).iter().cloned(),
2057        )
2058    }
2059
2060    /// Callable parameter types backed by the same stored-metadata memo.
2061    /// Parameter spellings are source-owned facts and do not depend on the
2062    /// modifier joins that full `signature_metadata` performs for an
2063    /// out-of-line member.
2064    pub fn callable_parameter_types(&self, unit: &CodeUnit) -> Option<Vec<String>> {
2065        brokk_bifrost_core::analyzer::model::first_callable_parameter_types(
2066            self.callable_metadata(unit).iter().cloned(),
2067        )
2068    }
2069
2070    /// Whether `file` has a structured unresolved include that is active before
2071    /// the reference at `before_byte`.
2072    ///
2073    /// Include facts are collected from the prepared tree once per visibility
2074    /// query. The reference position is still evaluated for each call because
2075    /// preprocessor visibility depends on the reference's own conditional
2076    /// context.
2077    pub fn has_unresolved_include_visible_before(
2078        &self,
2079        file: &ProjectFile,
2080        before_byte: usize,
2081    ) -> bool {
2082        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
2083            return false;
2084        };
2085        let facts = self.structured_include_facts(file, prepared.as_ref());
2086        has_unresolved_include_visible_before_in_prepared(
2087            file,
2088            prepared.as_ref(),
2089            self.cpp.include_target_index(),
2090            &facts,
2091            before_byte,
2092            &|guards: Option<&HashSet<PreprocessorGuard>>| {
2093                self.effective_reference_guards(file, guards)
2094            },
2095        )
2096    }
2097
2098    fn structured_include_facts(
2099        &self,
2100        file: &ProjectFile,
2101        prepared: &PreparedSyntaxTree,
2102    ) -> Arc<[StructuredIncludeFact]> {
2103        let cell = self
2104            .structured_include_fact_cells
2105            .lock()
2106            .expect("C++ structured include-fact cache poisoned")
2107            .entry(file.clone())
2108            .or_default()
2109            .clone();
2110        Arc::clone(cell.get_or_init(|| collect_structured_include_facts(prepared)))
2111    }
2112
2113    /// A [`VisibilityIndex`] over a caller-supplied visible-declaration map,
2114    /// bypassing the include-closure walk [`Self::build`] performs.
2115    ///
2116    /// The resolver's own unit tests drive the type-resolution paths against a
2117    /// hand-written visibility table; they live in `brokk-bifrost-analysis`
2118    /// because they need a real `CppAnalyzer`, so the struct literal they used
2119    /// to write inline is here instead of thirty-three public fields.
2120    #[cfg(any(test, feature = "test-support"))]
2121    pub fn from_visible_files_for_test(
2122        cpp: &'a dyn CppSource,
2123        token: QueryToken<'a>,
2124        visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
2125    ) -> Self {
2126        let visible_source_files_by_root = visible_by_file
2127            .iter()
2128            .map(|(file, visible)| {
2129                (
2130                    file.clone(),
2131                    visible
2132                        .iter()
2133                        .map(|unit| unit.source().clone())
2134                        .chain(std::iter::once(file.clone()))
2135                        .collect(),
2136                )
2137            })
2138            .collect();
2139        let mut global_field_internal_linkage = HashMap::default();
2140        Self {
2141            cpp,
2142            token,
2143            data: Arc::new(VisibilityIndexData {
2144                visible_by_identifier: build_visible_identifier_index(
2145                    &CppGraphSource::from_source(cpp, token),
2146                    &visible_by_file,
2147                    &visible_source_files_by_root,
2148                    &mut global_field_internal_linkage,
2149                ),
2150                global_field_internal_linkage,
2151                visible_by_file,
2152                visible_source_files_by_root,
2153                alias_cells: Mutex::new(HashMap::default()),
2154                visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2155                parser_alias_target_matches: RwLock::new(HashMap::default()),
2156                ordinary_type_import_cells: Mutex::new(HashMap::default()),
2157                project_using_index: OnceLock::new(),
2158                namespace_alias_sites: Mutex::new(HashMap::default()),
2159                callable_reference_specs: Mutex::new(HashMap::default()),
2160                structured_include_fact_cells: Mutex::new(HashMap::default()),
2161                include_activation_cells: Mutex::new(HashMap::default()),
2162                compile_proven_guard_cells: Mutex::new(HashMap::default()),
2163                include_path_admission_cells: Mutex::new(HashMap::default()),
2164                conditional_include_projection_cells: Mutex::new(HashMap::default()),
2165                conditional_include_projection_index_build_count: AtomicUsize::new(0),
2166                conditional_include_projection_state_count: AtomicUsize::new(0),
2167                conditional_include_target_state_count: AtomicUsize::new(0),
2168                include_activation_build_count: AtomicUsize::new(0),
2169                using_donor_activation_count: AtomicUsize::new(0),
2170                using_namespace_lookup_count: AtomicUsize::new(0),
2171                using_name_candidate_inspection_count: AtomicUsize::new(0),
2172                callable_reference_spec_build_count: AtomicUsize::new(0),
2173                alias_source_parse_counts: Mutex::new(HashMap::default()),
2174                visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2175                parser_alias_fallback_calls: AtomicUsize::new(0),
2176                parser_alias_fallback_files: AtomicUsize::new(0),
2177                parser_alias_source_parses: AtomicUsize::new(0),
2178                parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2179                field_type_facts: Mutex::new(HashMap::default()),
2180                structured_alias_targets: Mutex::new(HashMap::default()),
2181                callable_comparables: Mutex::new(HashMap::default()),
2182                class_definition_agreements: Mutex::new(HashMap::default()),
2183                callable_metadata: Mutex::new(HashMap::default()),
2184                comparable_name_declarations: Mutex::new(HashMap::default()),
2185                indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2186                indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2187                precise_parent_cache: Mutex::new(HashMap::default()),
2188                c_tag_kind_cache: Mutex::new(HashMap::default()),
2189                c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2190                macro_event_cells: Mutex::new(HashMap::default()),
2191                macro_event_name_sets: Mutex::new(HashMap::default()),
2192                macro_include_protection_cells: Mutex::new(HashMap::default()),
2193                macro_environment_checkpoints: Mutex::new(HashMap::default()),
2194                macro_replacements: Mutex::new(HashMap::default()),
2195                macro_local_binding_templates: Mutex::new(HashMap::default()),
2196                macro_lexical_templates: Mutex::new(HashMap::default()),
2197                macro_lexical_indexes: Mutex::new(HashMap::default()),
2198                macro_replacement_bodies: Mutex::new(HashMap::default()),
2199                macro_type_parameters: Mutex::new(HashMap::default()),
2200                callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2201                macro_replacement_parse_count: AtomicUsize::new(0),
2202                macro_event_application_count: AtomicUsize::new(0),
2203                macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2204                macro_environment_copy_count: AtomicUsize::new(0),
2205                macro_environment_request_count: AtomicUsize::new(0),
2206                cpp_template_metadata: HashMap::default(),
2207                cpp_template_families: HashMap::default(),
2208                qualified_candidate_inspections: AtomicUsize::new(0),
2209                target_preserving_type_resolution_count: AtomicUsize::new(0),
2210                visibility_identifier_lookup_count: 0,
2211                visibility_identifier_batch_count: 0,
2212            }),
2213        }
2214    }
2215
2216    /// The index's own C++ source, in the dispatching-analyzer shape.
2217    ///
2218    /// Four resolution paths reach the workspace through the C++ analyzer they
2219    /// already hold rather than through the analyzer the query was issued
2220    /// against; before the move they passed `&CppAnalyzer` straight into a
2221    /// `&dyn IAnalyzer` parameter. See [`CppGraphSource::from_source`].
2222    fn cpp_source(&self) -> CppGraphSource<'a> {
2223        CppGraphSource::from_source(self.cpp, self.token)
2224    }
2225
2226    pub fn build(
2227        cpp: &'a dyn CppSource,
2228        token: QueryToken<'a>,
2229        analyzer: &CppGraphSource<'_>,
2230        roots: &HashSet<ProjectFile>,
2231    ) -> Self {
2232        Self::build_with_cancellation(cpp, token, analyzer, roots, None)
2233    }
2234
2235    pub fn build_with_cancellation(
2236        cpp: &'a dyn CppSource,
2237        token: QueryToken<'a>,
2238        analyzer: &CppGraphSource<'_>,
2239        roots: &HashSet<ProjectFile>,
2240        cancellation: Option<&CancellationToken>,
2241    ) -> Self {
2242        let visibility_started = Instant::now();
2243        let include_targets = cpp.include_target_index();
2244        let includes_started = Instant::now();
2245        let mut include_graph = IncludeGraph::default();
2246        for root in roots {
2247            include_graph.extend_with(root, cancellation, &mut |file| {
2248                cpp.canonical_include_paths(token, file)
2249                    .unwrap_or_default()
2250                    .into_iter()
2251                    .flat_map(|include| {
2252                        resolve_include_targets_with_index(file, &include, include_targets)
2253                    })
2254                    .collect()
2255            });
2256        }
2257        let include_elapsed = includes_started.elapsed();
2258        let include_file_count = include_graph.files().count();
2259        let visible_source_files_by_root = roots
2260            .iter()
2261            .map(|root| {
2262                (
2263                    root.clone(),
2264                    include_graph.reachable_files(root, cancellation),
2265                )
2266            })
2267            .collect::<HashMap<_, _>>();
2268        let mut visibility_stats = BoundedVisibilityStats::default();
2269        let mut visible_by_file = build_bounded_visible_declarations(
2270            cpp,
2271            token,
2272            analyzer,
2273            roots,
2274            &visible_source_files_by_root,
2275            cancellation,
2276            &mut visibility_stats,
2277        );
2278        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
2279            eprintln!(
2280                "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={}",
2281                visibility_started.elapsed().as_millis(),
2282                include_elapsed.as_millis(),
2283                include_file_count,
2284                visibility_stats.rounds,
2285                visibility_stats.root_names,
2286                visibility_stats.identifier_lookups,
2287                visibility_stats.identifier_batches,
2288                visibility_stats.candidate_units,
2289                visibility_stats.candidate_sources,
2290                visibility_stats.declaration_reads,
2291                visibility_stats.declaration_units,
2292                visibility_stats.selected_units,
2293                visibility_stats.dependency_source_names,
2294                visibility_stats.dependency_names,
2295                visibility_stats.lookup_elapsed.as_millis(),
2296                visibility_stats.declaration_elapsed.as_millis(),
2297                visibility_stats.dependency_source_elapsed.as_millis(),
2298            );
2299        }
2300        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
2301        let finalize_started = Instant::now();
2302        if report_stats {
2303            eprintln!(
2304                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=started roots={} visible_units={}",
2305                visible_by_file.len(),
2306                visible_by_file.values().map(HashSet::len).sum::<usize>(),
2307            );
2308        }
2309        let owner_started = Instant::now();
2310        if report_stats {
2311            eprintln!("BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=started");
2312        }
2313        let owner_stats = extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
2314        if report_stats {
2315            eprintln!(
2316                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=completed unseen_owners={} definition_lookups={} admitted={} elapsed_ms={}",
2317                owner_stats.unseen_owners,
2318                owner_stats.definition_lookups,
2319                owner_stats.admitted,
2320                owner_started.elapsed().as_millis(),
2321            );
2322        }
2323        let mut global_field_internal_linkage = HashMap::default();
2324        let identifier_started = Instant::now();
2325        if report_stats {
2326            eprintln!(
2327                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=started"
2328            );
2329        }
2330        let visible_by_identifier = build_visible_identifier_index(
2331            analyzer,
2332            &visible_by_file,
2333            &visible_source_files_by_root,
2334            &mut global_field_internal_linkage,
2335        );
2336        if report_stats {
2337            eprintln!(
2338                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=completed roots={} names={} candidates={} elapsed_ms={}",
2339                visible_by_identifier.len(),
2340                visible_by_identifier
2341                    .values()
2342                    .map(HashMap::len)
2343                    .sum::<usize>(),
2344                visible_by_identifier
2345                    .values()
2346                    .flat_map(HashMap::values)
2347                    .map(Vec::len)
2348                    .sum::<usize>(),
2349                identifier_started.elapsed().as_millis(),
2350            );
2351        }
2352        let mut cpp_template_metadata = HashMap::default();
2353        let metadata_started = Instant::now();
2354        let mut template_classes = 0usize;
2355        if report_stats {
2356            eprintln!(
2357                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=started"
2358            );
2359        }
2360        for unit in visible_by_file
2361            .values()
2362            .flatten()
2363            .filter(|unit| unit.is_class())
2364        {
2365            template_classes += 1;
2366            if cpp_template_metadata.contains_key(unit) {
2367                continue;
2368            }
2369            if let Some(metadata) = cpp.template_metadata(unit) {
2370                cpp_template_metadata.insert(unit.clone(), metadata);
2371            }
2372        }
2373        if report_stats {
2374            eprintln!(
2375                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=completed classes={} metadata={} elapsed_ms={}",
2376                template_classes,
2377                cpp_template_metadata.len(),
2378                metadata_started.elapsed().as_millis(),
2379            );
2380        }
2381        let families_started = Instant::now();
2382        if report_stats {
2383            eprintln!(
2384                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=started"
2385            );
2386        }
2387        let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
2388        for (unit, metadata) in &cpp_template_metadata {
2389            cpp_template_families
2390                .entry(metadata.primary_fq_name.clone())
2391                .or_default()
2392                .push(unit.clone());
2393        }
2394        // `cpp_template_metadata` is hash-keyed on `CodeUnit`, so the push
2395        // order above is a function of those hashes. Two mirrored headers can
2396        // declare one specialization; `select_template_specialization` treats
2397        // them as interchangeable and returns the family's first entry, so an
2398        // unsorted family made the reported declaration depend on the
2399        // workspace's absolute path and on unrelated files (#1836). Order the
2400        // family exactly as `build_visible_identifier_index` orders its
2401        // per-identifier candidate lists.
2402        for family in cpp_template_families.values_mut() {
2403            sort_lookup_units(family);
2404        }
2405        if report_stats {
2406            eprintln!(
2407                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=completed families={} members={} elapsed_ms={}",
2408                cpp_template_families.len(),
2409                cpp_template_families.values().map(Vec::len).sum::<usize>(),
2410                families_started.elapsed().as_millis(),
2411            );
2412            eprintln!(
2413                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=completed roots={} visible_units={} elapsed_ms={} total_ms={}",
2414                visible_by_file.len(),
2415                visible_by_file.values().map(HashSet::len).sum::<usize>(),
2416                finalize_started.elapsed().as_millis(),
2417                visibility_started.elapsed().as_millis(),
2418            );
2419        }
2420        Self {
2421            cpp,
2422            token,
2423            data: Arc::new(VisibilityIndexData {
2424                visible_by_file,
2425                visible_by_identifier,
2426                global_field_internal_linkage,
2427                visible_source_files_by_root,
2428                alias_cells: Mutex::new(HashMap::default()),
2429                visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2430                parser_alias_target_matches: RwLock::new(HashMap::default()),
2431                ordinary_type_import_cells: Mutex::new(HashMap::default()),
2432                project_using_index: OnceLock::new(),
2433                namespace_alias_sites: Mutex::new(HashMap::default()),
2434                callable_reference_specs: Mutex::new(HashMap::default()),
2435                structured_include_fact_cells: Mutex::new(HashMap::default()),
2436                include_activation_cells: Mutex::new(HashMap::default()),
2437                compile_proven_guard_cells: Mutex::new(HashMap::default()),
2438                include_path_admission_cells: Mutex::new(HashMap::default()),
2439                conditional_include_projection_cells: Mutex::new(HashMap::default()),
2440                #[cfg(any(test, feature = "test-support"))]
2441                conditional_include_projection_index_build_count: AtomicUsize::new(0),
2442                #[cfg(any(test, feature = "test-support"))]
2443                conditional_include_projection_state_count: AtomicUsize::new(0),
2444                #[cfg(any(test, feature = "test-support"))]
2445                conditional_include_target_state_count: AtomicUsize::new(0),
2446                #[cfg(any(test, feature = "test-support"))]
2447                include_activation_build_count: AtomicUsize::new(0),
2448                #[cfg(any(test, feature = "test-support"))]
2449                using_donor_activation_count: AtomicUsize::new(0),
2450                #[cfg(any(test, feature = "test-support"))]
2451                using_namespace_lookup_count: AtomicUsize::new(0),
2452                #[cfg(any(test, feature = "test-support"))]
2453                using_name_candidate_inspection_count: AtomicUsize::new(0),
2454                #[cfg(any(test, feature = "test-support"))]
2455                callable_reference_spec_build_count: AtomicUsize::new(0),
2456                #[cfg(any(test, feature = "test-support"))]
2457                alias_source_parse_counts: Mutex::new(HashMap::default()),
2458                #[cfg(any(test, feature = "test-support"))]
2459                visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2460                parser_alias_fallback_calls: AtomicUsize::new(0),
2461                parser_alias_fallback_files: AtomicUsize::new(0),
2462                parser_alias_source_parses: AtomicUsize::new(0),
2463                parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2464                field_type_facts: Mutex::new(HashMap::default()),
2465                structured_alias_targets: Mutex::new(HashMap::default()),
2466                callable_comparables: Mutex::new(HashMap::default()),
2467                class_definition_agreements: Mutex::new(HashMap::default()),
2468                callable_metadata: Mutex::new(HashMap::default()),
2469                comparable_name_declarations: Mutex::new(HashMap::default()),
2470                indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2471                indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2472                precise_parent_cache: Mutex::new(HashMap::default()),
2473                c_tag_kind_cache: Mutex::new(HashMap::default()),
2474                c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2475                macro_event_cells: Mutex::new(HashMap::default()),
2476                macro_event_name_sets: Mutex::new(HashMap::default()),
2477                macro_include_protection_cells: Mutex::new(HashMap::default()),
2478                macro_environment_checkpoints: Mutex::new(HashMap::default()),
2479                macro_replacements: Mutex::new(HashMap::default()),
2480                macro_local_binding_templates: Mutex::new(HashMap::default()),
2481                macro_lexical_templates: Mutex::new(HashMap::default()),
2482                macro_lexical_indexes: Mutex::new(HashMap::default()),
2483                macro_replacement_bodies: Mutex::new(HashMap::default()),
2484                macro_type_parameters: Mutex::new(HashMap::default()),
2485                callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2486                #[cfg(any(test, feature = "test-support"))]
2487                macro_replacement_parse_count: AtomicUsize::new(0),
2488                #[cfg(any(test, feature = "test-support"))]
2489                macro_event_application_count: AtomicUsize::new(0),
2490                #[cfg(any(test, feature = "test-support"))]
2491                macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2492                #[cfg(any(test, feature = "test-support"))]
2493                macro_environment_copy_count: AtomicUsize::new(0),
2494                #[cfg(any(test, feature = "test-support"))]
2495                macro_environment_request_count: AtomicUsize::new(0),
2496                cpp_template_metadata,
2497                cpp_template_families,
2498                #[cfg(any(test, feature = "test-support"))]
2499                qualified_candidate_inspections: AtomicUsize::new(0),
2500                #[cfg(any(test, feature = "test-support"))]
2501                target_preserving_type_resolution_count: AtomicUsize::new(0),
2502                #[cfg(any(test, feature = "test-support"))]
2503                visibility_identifier_lookup_count: visibility_stats.identifier_lookups,
2504                #[cfg(any(test, feature = "test-support"))]
2505                visibility_identifier_batch_count: visibility_stats.identifier_batches,
2506            }),
2507        }
2508    }
2509
2510    pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2511        if file == target.source() {
2512            return true;
2513        }
2514        if self.global_field_has_internal_linkage(target) {
2515            return self
2516                .visible_source_files_by_root
2517                .get(file)
2518                .is_some_and(|sources| sources.contains(target.source()));
2519        }
2520        self.visible_by_file
2521            .get(file)
2522            .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
2523    }
2524
2525    fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
2526        self.global_field_internal_linkage
2527            .get(unit)
2528            .copied()
2529            .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
2530    }
2531
2532    pub fn call_arity_evidence(
2533        &self,
2534        file: &ProjectFile,
2535        call: Node<'_>,
2536        source: &str,
2537    ) -> CallArityEvidence {
2538        self.call_arity_evidence_at(file, call, source, call.start_byte())
2539    }
2540
2541    /// Argument-count evidence for a call whose macro environment is not the
2542    /// one at its own byte offset.
2543    ///
2544    /// A call recovered from a macro replacement lives in a sentinel parse of
2545    /// its own, so its node offsets say nothing about which macros are active.
2546    /// `environment_byte` names the position in `file` whose macro environment
2547    /// governs the call: the macro definition site for a replacement body.
2548    pub fn call_arity_evidence_at(
2549        &self,
2550        file: &ProjectFile,
2551        call: Node<'_>,
2552        source: &str,
2553        environment_byte: usize,
2554    ) -> CallArityEvidence {
2555        let Some(arguments) = call
2556            .child_by_field_name("arguments")
2557            .or_else(|| call.child_by_field_name("parameters"))
2558            .or_else(|| call.child_by_field_name("value"))
2559            .or_else(|| first_named_child_of_kind(call, "argument_list"))
2560            .or_else(|| first_named_child_of_kind(call, "initializer_list"))
2561        else {
2562            return CallArityEvidence::Exact(0);
2563        };
2564        let recovered_c_keyword_arguments =
2565            recovered_c_keyword_argument_count(file, call, arguments, source);
2566        let c_semantics = reference_uses_c_semantics(self.cpp, file);
2567        let arguments = argument_children(arguments)
2568            .flat_map(|argument| {
2569                recovered_c_new_expression_arguments(argument, c_semantics)
2570                    .map(Vec::from)
2571                    .unwrap_or_else(|| vec![argument])
2572            })
2573            .collect::<Vec<_>>();
2574        if arguments
2575            .iter()
2576            .all(|argument| !argument_shape_may_change_arity(*argument))
2577        {
2578            return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
2579        }
2580        let environment = self.macro_environment(file, environment_byte);
2581        let mut stack = Vec::new();
2582        let mut total = recovered_c_keyword_arguments;
2583        for argument in arguments {
2584            if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
2585                return CallArityEvidence::Unknown;
2586            }
2587            let CallArityEvidence::Exact(spread) =
2588                self.argument_arity_evidence(argument, source, &environment, &mut stack)
2589            else {
2590                return CallArityEvidence::Unknown;
2591            };
2592            total += spread;
2593        }
2594        CallArityEvidence::Exact(total)
2595    }
2596
2597    fn argument_arity_evidence(
2598        &self,
2599        argument: Node<'_>,
2600        source: &str,
2601        environment: &MacroEnvironment,
2602        stack: &mut Vec<(ProjectFile, usize)>,
2603    ) -> CallArityEvidence {
2604        let (name, invocation_arguments, function_like) = match argument.kind() {
2605            "identifier" => (node_text(argument, source), None, false),
2606            "call_expression" => {
2607                let Some(function) = argument.child_by_field_name("function") else {
2608                    return CallArityEvidence::Exact(1);
2609                };
2610                if function.kind() != "identifier" {
2611                    return CallArityEvidence::Exact(1);
2612                }
2613                let Some(arguments) = argument.child_by_field_name("arguments") else {
2614                    return CallArityEvidence::Exact(1);
2615                };
2616                (node_text(function, source), Some(arguments), true)
2617            }
2618            _ => return CallArityEvidence::Exact(1),
2619        };
2620        let Some(binding) = environment.binding(name) else {
2621            return if environment.unknown_names {
2622                CallArityEvidence::Unknown
2623            } else {
2624                CallArityEvidence::Exact(1)
2625            };
2626        };
2627        if !binding.is_exact() {
2628            return CallArityEvidence::Unknown;
2629        }
2630        match (&binding.definition, invocation_arguments, function_like) {
2631            (MacroDefinition::Object { replacement }, None, false) => self
2632                .replacement_arity_evidence(
2633                    replacement,
2634                    &[],
2635                    &[],
2636                    source,
2637                    environment,
2638                    stack,
2639                    binding,
2640                ),
2641            (
2642                MacroDefinition::Function {
2643                    parameters,
2644                    replacement,
2645                },
2646                Some(arguments),
2647                true,
2648            ) => {
2649                let actuals = argument_children(arguments).collect::<Vec<_>>();
2650                if actuals.len() != parameters.len() {
2651                    CallArityEvidence::Unknown
2652                } else {
2653                    self.replacement_arity_evidence(
2654                        replacement,
2655                        parameters,
2656                        &actuals,
2657                        source,
2658                        environment,
2659                        stack,
2660                        binding,
2661                    )
2662                }
2663            }
2664            (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
2665            _ => CallArityEvidence::Unknown,
2666        }
2667    }
2668
2669    #[allow(clippy::too_many_arguments)]
2670    fn replacement_arity_evidence(
2671        &self,
2672        replacement: &str,
2673        parameters: &[String],
2674        actuals: &[Node<'_>],
2675        actual_source: &str,
2676        environment: &MacroEnvironment,
2677        stack: &mut Vec<(ProjectFile, usize)>,
2678        binding: &MacroBinding,
2679    ) -> CallArityEvidence {
2680        let identity = (binding.source.clone(), binding.declaration_byte);
2681        if stack.contains(&identity) || replacement.trim().is_empty() {
2682            return CallArityEvidence::Unknown;
2683        }
2684        stack.push(identity);
2685        let parsed = self.parsed_macro_replacement(binding, replacement);
2686        let evidence = (|| {
2687            let ParsedMacroReplacement::Parsed {
2688                source: sentinel,
2689                tree,
2690            } = parsed.as_ref()
2691            else {
2692                return None;
2693            };
2694            let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
2695            let arguments = call.child_by_field_name("arguments")?;
2696            let mut total = 0usize;
2697            for argument in argument_children(arguments) {
2698                if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
2699                    return None;
2700                }
2701                if argument.kind() == "identifier"
2702                    && let Some(parameter_index) = parameters
2703                        .iter()
2704                        .position(|parameter| parameter == node_text(argument, sentinel))
2705                {
2706                    if !macro_expansion_shape_is_safe(
2707                        actuals[parameter_index],
2708                        actual_source,
2709                        &[],
2710                        environment,
2711                    ) {
2712                        return None;
2713                    }
2714                    let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
2715                        actuals[parameter_index],
2716                        actual_source,
2717                        environment,
2718                        stack,
2719                    ) else {
2720                        return None;
2721                    };
2722                    total += spread;
2723                    continue;
2724                }
2725                let CallArityEvidence::Exact(spread) =
2726                    self.argument_arity_evidence(argument, sentinel, environment, stack)
2727                else {
2728                    return None;
2729                };
2730                total += spread;
2731            }
2732            Some(CallArityEvidence::Exact(total))
2733        })()
2734        .unwrap_or(CallArityEvidence::Unknown);
2735        stack.pop();
2736        evidence
2737    }
2738
2739    fn parsed_macro_replacement(
2740        &self,
2741        binding: &MacroBinding,
2742        replacement: &str,
2743    ) -> Arc<ParsedMacroReplacement> {
2744        let key = (binding.source.clone(), binding.declaration_byte);
2745        let mut cache = self
2746            .macro_replacements
2747            .lock()
2748            .expect("C++ macro replacement cache poisoned");
2749        if let Some(parsed) = cache.get(&key) {
2750            return Arc::clone(parsed);
2751        }
2752        #[cfg(any(test, feature = "test-support"))]
2753        self.macro_replacement_parse_count
2754            .fetch_add(1, Ordering::Relaxed);
2755        let parsed = parse_sentinel(
2756            "void __bifrost_macro_arity() { __bifrost_macro_call(",
2757            SentinelInput::replacement(replacement),
2758            "); }",
2759        );
2760        let parsed = if parsed.tree.root_node().has_error() {
2761            ParsedMacroReplacement::Unsupported
2762        } else {
2763            ParsedMacroReplacement::Parsed {
2764                source: parsed.source,
2765                tree: parsed.tree,
2766            }
2767        };
2768        let parsed = Arc::new(parsed);
2769        cache.insert(key, Arc::clone(&parsed));
2770        parsed
2771    }
2772
2773    /// Recover a typed local declared by an active C function-like macro.
2774    ///
2775    /// This is intentionally narrower than macro expansion. The replacement
2776    /// must parse as one declaration, and the invocation must bind every
2777    /// formal parameter to one structured argument. That is sufficient for
2778    /// declaration macros such as `THIS(StorageAzure)`. An unavailable include
2779    /// can make the binding provisional without erasing its last known
2780    /// definition; an explicit conflicting definition still replaces it with
2781    /// Unsupported. Malformed and statement-producing macros also fail closed.
2782    pub fn function_macro_local_binding<'tree>(
2783        &self,
2784        file: &ProjectFile,
2785        statement: Node<'tree>,
2786        source: &str,
2787    ) -> Option<MacroLocalBinding<'tree>> {
2788        if !is_c_source_file(file) {
2789            return None;
2790        }
2791        if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
2792            return Some(binding);
2793        }
2794        let call = match statement.kind() {
2795            "expression_statement" => {
2796                single_named_child_matching(statement, |child| !child.is_extra())?
2797            }
2798            _ => statement,
2799        };
2800        let (function, actuals) = function_macro_invocation_parts(call)?;
2801        let environment = self.macro_environment(file, call.start_byte());
2802        let function_name = node_text(function, source);
2803        let binding = environment.binding(function_name)?;
2804        let MacroDefinition::Function {
2805            parameters,
2806            replacement,
2807        } = &binding.definition
2808        else {
2809            return None;
2810        };
2811        if actuals.len() != parameters.len() {
2812            return None;
2813        }
2814        let template = self.macro_local_binding_template(binding, parameters, replacement)?;
2815        let (type_name, type_node) = match &template.declared_type {
2816            MacroLocalBindingTypeTemplate::Parameter(index) => {
2817                let actual = *actuals.get(*index)?;
2818                if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
2819                    return None;
2820                }
2821                let type_node = macro_type_argument_node(actual, source).unwrap_or(actual);
2822                (
2823                    node_text(actual, source).trim().to_string(),
2824                    Some(type_node),
2825                )
2826            }
2827            MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
2828        };
2829        if type_name.is_empty() {
2830            return None;
2831        }
2832        Some(MacroLocalBinding {
2833            name: template.name.clone(),
2834            type_name,
2835            type_node,
2836            pointer_depth: template.pointer_depth,
2837            proven_unit: None,
2838        })
2839    }
2840
2841    /// Recover the typed receiver established by a C container macro
2842    /// assignment, such as `value = container_of(ptr, struct item, link)`.
2843    ///
2844    /// The macro definition's parsed replacement identifies the one formal
2845    /// parameter used in type position.  The invocation supplies the actual
2846    /// type node, which is then resolved through the ordinary visibility
2847    /// index.  Calls with no unique type-position parameter, an unresolved
2848    /// type, or an uncertain macro environment remain unproven.
2849    pub fn function_macro_container_binding<'tree>(
2850        &self,
2851        analyzer: &CppGraphSource<'_>,
2852        file: &ProjectFile,
2853        assignment: Node<'tree>,
2854        source: &str,
2855    ) -> Option<MacroLocalBinding<'tree>> {
2856        if !is_c_source_file(file) || assignment.kind() != "assignment_expression" {
2857            return None;
2858        }
2859        let name_node = assignment.child_by_field_name("left")?;
2860        if name_node.kind() != "identifier" {
2861            return None;
2862        }
2863        let call = assignment.child_by_field_name("right")?;
2864        if call.kind() != "call_expression" {
2865            return None;
2866        }
2867        let function = call.child_by_field_name("function")?;
2868        if function.kind() != "identifier" {
2869            return None;
2870        }
2871        let arguments = call.child_by_field_name("arguments")?;
2872        let actuals = argument_children(arguments).collect::<Vec<_>>();
2873        let function_name = node_text(function, source);
2874        let environment = self.macro_environment(file, call.start_byte());
2875        let binding = environment.binding(function_name)?;
2876        if !binding.is_exact() {
2877            return None;
2878        }
2879        let MacroDefinition::Function {
2880            parameters,
2881            replacement,
2882        } = &binding.definition
2883        else {
2884            return None;
2885        };
2886        if actuals.len() != parameters.len() {
2887            return None;
2888        }
2889        let body = self.parsed_macro_replacement_body(
2890            &(binding.source.clone(), binding.declaration_byte),
2891            parameters,
2892            replacement,
2893        )?;
2894        let type_parameter = macro_replacement_type_parameter(&body, parameters)?;
2895        let type_argument = *actuals.get(type_parameter)?;
2896        let type_node = macro_type_argument_node(type_argument, source)?;
2897        let type_name = node_text(type_node, source).trim().to_string();
2898        if type_name.is_empty() {
2899            return None;
2900        }
2901        let explicit_tag = match node_text(type_argument, source) {
2902            "struct" => Some(CppCTagKind::Struct),
2903            "union" => Some(CppCTagKind::Union),
2904            _ => None,
2905        };
2906        let resolved_type = if let Some(tag) = explicit_tag {
2907            let candidates = self
2908                .visible_identifier_candidates(file, &type_name)
2909                .filter(|candidate| self.cached_c_tag_kind(analyzer, candidate) == Some(tag))
2910                .collect::<Vec<_>>();
2911            self.resolve_type_candidates(
2912                analyzer,
2913                file,
2914                &candidates,
2915                TypeCandidateResolution::Canonical,
2916            )
2917            .ok()
2918        } else {
2919            self.resolve_type_node_result(file, type_node, source)
2920                .ok()
2921                .flatten()
2922        };
2923        let proven_unit = resolved_type?;
2924        Some(MacroLocalBinding {
2925            name: node_text(name_node, source).to_string(),
2926            type_name,
2927            type_node: Some(type_node),
2928            pointer_depth: 1,
2929            proven_unit: Some(proven_unit),
2930        })
2931    }
2932
2933    /// Recover the invocation-specific type of a replacement local visible
2934    /// at the selected source token.
2935    pub fn macro_local_binding_at<'tree>(
2936        &self,
2937        file: &ProjectFile,
2938        root: Node<'tree>,
2939        source: &str,
2940        start_byte: usize,
2941        end_byte: usize,
2942    ) -> Option<MacroLocalBinding<'tree>> {
2943        crate::graph::macro_lexical::typed_binding(self, file, root, source, start_byte, end_byte)
2944    }
2945
2946    /// Resolve the source-backed lexical macro declaration for one source
2947    /// range. The range may be a point range (`start..start + 1`) inside an
2948    /// identifier, which is the form used by goto-definition queries.
2949    pub fn macro_lexical_binding(
2950        &self,
2951        file: &ProjectFile,
2952        root: Node<'_>,
2953        source: &str,
2954        start_byte: usize,
2955        end_byte: usize,
2956    ) -> Option<MacroLexicalBinding> {
2957        crate::graph::macro_lexical::binding(self, file, root, source, start_byte, end_byte)
2958    }
2959
2960    /// Enumerate source ranges whose lexical meaning comes from a
2961    /// function-like macro formal or replacement local. Definition spellings
2962    /// themselves are omitted; callers needing goto-definition on a spelling
2963    /// use [`Self::macro_lexical_binding`].
2964    pub fn macro_lexical_references(
2965        &self,
2966        file: &ProjectFile,
2967        root: Node<'_>,
2968        source: &str,
2969        max_references: usize,
2970        cancelled: impl FnMut() -> bool,
2971    ) -> MacroLexicalReferences {
2972        crate::graph::macro_lexical::all_references(
2973            || self,
2974            file,
2975            root,
2976            source,
2977            max_references,
2978            cancelled,
2979        )
2980    }
2981
2982    /// Return the source identity of the exact active function-like macro
2983    /// binding. The definition AST is hydrated by the lexical helper from
2984    /// this `(ProjectFile, declaration byte)` pair, so no parser node escapes
2985    /// the prepared source borrow.
2986    pub(crate) fn function_macro_binding_at(
2987        &self,
2988        file: &ProjectFile,
2989        name: &str,
2990        before_byte: usize,
2991    ) -> Option<(ProjectFile, usize)> {
2992        let environment = self.macro_environment(file, before_byte);
2993        let binding = environment.binding(name)?;
2994        if binding.is_exact()
2995            && matches!(
2996                binding.definition,
2997                MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2998            )
2999        {
3000            return Some((binding.source.clone(), binding.declaration_byte));
3001        }
3002        // A definition under an unknown condition is still certain at a use
3003        // within that same branch. The checkpoint retains the event that made
3004        // the binding uncertain. Require that exact defining event, so an
3005        // intervening undef, include, or conditional redefinition cannot be
3006        // bypassed by a lexical search for an older macro.
3007        if binding.source != *file {
3008            return None;
3009        }
3010        let prepared = self.cpp.prepared_syntax(self.token, file)?;
3011        let root = prepared.tree().root_node();
3012        let source = prepared.source();
3013        let reference = root.descendant_for_byte_range(
3014            before_byte,
3015            before_byte.saturating_add(1).min(source.len()),
3016        )?;
3017        let reference_conditions = owning_preprocessor_conditionals(root, reference, source);
3018        let cell = self.macro_event_cell(file);
3019        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3020        let event = events
3021            .iter()
3022            .find(|event| event.byte() == binding.declaration_byte)?;
3023        let MacroEvent::Define {
3024            name: defined_name,
3025            binding: definition,
3026            conditionals,
3027            ..
3028        } = event
3029        else {
3030            return None;
3031        };
3032        if defined_name != name
3033            || conditionals.is_empty()
3034            || !conditionals
3035                .iter()
3036                .all(|condition| reference_conditions.contains(condition))
3037            || !matches!(
3038                definition.definition,
3039                MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
3040            )
3041        {
3042            return None;
3043        }
3044        Some((definition.source.clone(), definition.declaration_byte))
3045    }
3046
3047    /// Return the type argument at a C function-like macro invocation.
3048    ///
3049    /// Exact local macro definitions identify type formals from their parsed
3050    /// replacement. An unavailable definition is deliberately not inferred:
3051    /// C has no AST distinction between an external macro call and an
3052    /// ordinary call to an unindexed function.
3053    pub fn function_macro_type_argument<'tree>(
3054        &self,
3055        file: &ProjectFile,
3056        node: Node<'tree>,
3057        source: &str,
3058    ) -> Option<Node<'tree>> {
3059        if !is_c_source_file(file) {
3060            return None;
3061        }
3062        let (call, argument_index, argument) = c_function_macro_argument(node)?;
3063        let function = call.child_by_field_name("function")?;
3064        let function_name = node_text(function, source);
3065        if function_name.is_empty() {
3066            return None;
3067        }
3068        if !self.file_defines_macro_name(file, function_name)
3069            && !self
3070                .visible_identifier_candidates(file, function_name)
3071                .any(|candidate| candidate.is_macro())
3072        {
3073            return None;
3074        }
3075        let environment = self.macro_environment(file, call.start_byte());
3076        let binding = environment.binding(function_name)?;
3077        if !binding.is_exact() {
3078            return None;
3079        }
3080        let (parameters, replacement, variadic) = match &binding.definition {
3081            MacroDefinition::Function {
3082                parameters,
3083                replacement,
3084            } => (parameters, replacement, false),
3085            MacroDefinition::VariadicFunction {
3086                parameters,
3087                replacement,
3088            } => (parameters, replacement, true),
3089            MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
3090        };
3091        let actuals = call
3092            .child_by_field_name("arguments")
3093            .map(argument_children)
3094            .into_iter()
3095            .flatten()
3096            .collect::<Vec<_>>();
3097        let arity_matches = if variadic {
3098            actuals.len() >= parameters.len()
3099        } else {
3100            actuals.len() == parameters.len()
3101        };
3102        let type_parameters = self.macro_type_parameter_indices(
3103            &(binding.source.clone(), binding.declaration_byte),
3104            parameters,
3105            replacement,
3106        )?;
3107        if !arity_matches || !type_parameters.contains(&argument_index) {
3108            return None;
3109        }
3110        let type_node = macro_type_argument_node(argument, source)?;
3111        if self.names_a_macro_at(file, node_text(type_node, source), type_node.start_byte()) {
3112            return None;
3113        }
3114        Some(type_node)
3115    }
3116
3117    fn macro_local_binding_template(
3118        &self,
3119        binding: &MacroBinding,
3120        parameters: &[String],
3121        replacement: &str,
3122    ) -> Option<Arc<MacroLocalBindingTemplate>> {
3123        let key = (binding.source.clone(), binding.declaration_byte);
3124        if let Some(template) = self
3125            .macro_local_binding_templates
3126            .lock()
3127            .expect("C++ macro local-binding cache poisoned")
3128            .get(&key)
3129        {
3130            return template.clone();
3131        }
3132        let template = (|| {
3133            let body = self.parsed_macro_replacement_body(&key, parameters, replacement)?;
3134            let sentinel = body.source.as_str();
3135            let statements = body.statements()?;
3136            let declaration = single_named_child_matching(statements, |child| !child.is_extra())?;
3137            if declaration.kind() != "declaration" {
3138                return None;
3139            }
3140            let type_node = declaration
3141                .child_by_field_name("type")
3142                .or_else(|| first_type_child(declaration))?;
3143            let declarator = declaration.child_by_field_name("declarator").or_else(|| {
3144                let mut cursor = declaration.walk();
3145                declaration.named_children(&mut cursor).find_map(|child| {
3146                    if child.kind() == "init_declarator" {
3147                        child.child_by_field_name("declarator")
3148                    } else {
3149                        is_declarator_node(child).then_some(child)
3150                    }
3151                })
3152            })?;
3153            let name = extract_variable_name(declarator, sentinel)?;
3154            let pointer_depth =
3155                declared_name_binding(declaration, type_node, &name, sentinel)?.pointer_depth;
3156            let type_text = node_text(type_node, sentinel).trim();
3157            let declared_type = parameters
3158                .iter()
3159                .position(|parameter| parameter == type_text)
3160                .map(MacroLocalBindingTypeTemplate::Parameter)
3161                .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
3162            Some(Arc::new(MacroLocalBindingTemplate {
3163                name,
3164                declared_type,
3165                pointer_depth,
3166            }))
3167        })();
3168        self.macro_local_binding_templates
3169            .lock()
3170            .expect("C++ macro local-binding cache poisoned")
3171            .insert(key, template.clone());
3172        template
3173    }
3174
3175    /// The parsed replacement body of the function-like macro `definition`
3176    /// defines, or `None` when the replacement cannot be recovered exactly.
3177    ///
3178    /// `definition` is the defining `preproc_function_def` node in `file`, so
3179    /// the result describes that definition rather than whichever same-named
3180    /// macro a later reference resolves to.
3181    pub fn function_macro_replacement_body(
3182        &self,
3183        file: &ProjectFile,
3184        definition: Node<'_>,
3185        source: &str,
3186    ) -> Option<Arc<ParsedReplacementBody>> {
3187        debug_assert_eq!(definition.kind(), "preproc_function_def");
3188        let (parameters, replacement) = match Self::decode_macro_definition(definition, source) {
3189            MacroDefinition::Function {
3190                parameters,
3191                replacement,
3192            }
3193            | MacroDefinition::VariadicFunction {
3194                parameters,
3195                replacement,
3196            } => (parameters, replacement),
3197            MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
3198        };
3199        self.parsed_macro_replacement_body(
3200            &(file.clone(), definition.start_byte()),
3201            &parameters,
3202            &replacement,
3203        )
3204    }
3205
3206    /// Parse one function-like macro replacement inside the shared sentinel.
3207    ///
3208    /// The parse fails closed, and the failure is cached, whenever the
3209    /// sentinel tree carries an error or the replacement uses preprocessor
3210    /// syntax that has no C++ meaning. Token pasting and stringizing produce
3211    /// `ERROR` nodes; `__VA_ARGS__` parses as an ordinary identifier and is
3212    /// therefore rejected from the parsed tree instead of the source text.
3213    fn parsed_macro_replacement_body(
3214        &self,
3215        key: &(ProjectFile, usize),
3216        parameters: &[String],
3217        replacement: &str,
3218    ) -> Option<Arc<ParsedReplacementBody>> {
3219        if let Some(body) = self
3220            .macro_replacement_bodies
3221            .lock()
3222            .expect("C++ macro replacement body cache poisoned")
3223            .get(key)
3224        {
3225            return body.clone();
3226        }
3227        let body = (|| {
3228            if replacement.trim().is_empty() {
3229                return None;
3230            }
3231            let (source, tree, original_offsets) =
3232                Self::parse_macro_replacement_body(replacement, parameters)?;
3233            let body = ParsedReplacementBody {
3234                source,
3235                tree,
3236                body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3237                parameters: parameters.to_vec(),
3238                original_offsets,
3239            };
3240            body.statements()?;
3241            if body.expands_variadic_arguments() {
3242                return None;
3243            }
3244            Some(Arc::new(body))
3245        })();
3246        self.macro_replacement_bodies
3247            .lock()
3248            .expect("C++ macro replacement body cache poisoned")
3249            .insert(key.clone(), body.clone());
3250        body
3251    }
3252
3253    /// Parse a replacement while preserving a map from the parse buffer back
3254    /// to the original logical-line bytes. A block formal can occur where C
3255    /// requires a statement terminator (`if (condition) block`); the caller's
3256    /// actual block supplies that terminator only after macro substitution.
3257    /// Tree-sitter exposes the formal as an identifier inside recovery, so a
3258    /// semicolon is inserted at that AST-derived boundary for the fallback
3259    /// parse. No identifier text is scanned to find the insertion point.
3260    fn parse_macro_replacement_body(
3261        replacement: &str,
3262        parameters: &[String],
3263    ) -> Option<(String, Tree, MacroOriginMap)> {
3264        let parsed = parse_sentinel(
3265            MACRO_BODY_SENTINEL_PREFIX,
3266            SentinelInput::replacement(replacement),
3267            MACRO_BODY_SENTINEL_SUFFIX,
3268        );
3269        let normalized_end = parsed.source.len() - MACRO_BODY_SENTINEL_SUFFIX.len() - 1;
3270        let normalized = &parsed.source[MACRO_BODY_SENTINEL_PREFIX.len()..normalized_end];
3271        let normalized_offsets = &parsed.original_offsets;
3272        let source = parsed.source.as_str();
3273        let tree = &parsed.tree;
3274        if !tree.root_node().has_error() {
3275            return Some((
3276                parsed.source,
3277                parsed.tree,
3278                parsed.original_offsets.into_boxed_slice(),
3279            ));
3280        }
3281
3282        let body_offset = MACRO_BODY_SENTINEL_PREFIX.len();
3283        let mut insertion_points = Vec::new();
3284        let mut stack = vec![tree.root_node()];
3285        while let Some(node) = stack.pop() {
3286            if matches!(
3287                node.kind(),
3288                "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
3289            ) && parameters
3290                .iter()
3291                .any(|parameter| parameter.as_str() == node_text(node, source))
3292                && Self::macro_formal_needs_statement_separator(node)
3293            {
3294                let point = node.end_byte().saturating_sub(body_offset);
3295                if point <= normalized.len() && !insertion_points.contains(&point) {
3296                    insertion_points.push(point);
3297                }
3298            }
3299            push_named_children_reversed(node, &mut stack);
3300        }
3301        if insertion_points.is_empty() {
3302            return None;
3303        }
3304        insertion_points.sort_unstable();
3305        let mut recovered = Vec::with_capacity(normalized.len() + insertion_points.len());
3306        let mut original_offsets =
3307            Vec::with_capacity(normalized.len() + insertion_points.len() + 1);
3308        let mut next_insertion = 0;
3309        for (index, byte) in normalized.bytes().enumerate() {
3310            recovered.push(byte);
3311            original_offsets.push(normalized_offsets[index].clone());
3312            while insertion_points.get(next_insertion).copied() == Some(index + 1) {
3313                recovered.push(b';');
3314                let boundary = normalized_offsets[index].end;
3315                original_offsets.push(boundary..boundary);
3316                next_insertion += 1;
3317            }
3318        }
3319        original_offsets.push(replacement.len()..replacement.len());
3320        let recovered = String::from_utf8(recovered).expect("source text remains UTF-8");
3321        let parsed = parse_sentinel(
3322            MACRO_BODY_SENTINEL_PREFIX,
3323            SentinelInput {
3324                source: recovered,
3325                original_offsets,
3326            },
3327            MACRO_BODY_SENTINEL_SUFFIX,
3328        );
3329        let (source, tree, original_offsets) =
3330            (parsed.source, parsed.tree, parsed.original_offsets);
3331        if tree.root_node().has_error() {
3332            return None;
3333        }
3334        Some((source, tree, original_offsets.into_boxed_slice()))
3335    }
3336
3337    /// A formal is statement-shaped when the recovered tree places it directly
3338    /// at a statement boundary. This is the grammar recovery produced for
3339    /// replacement forms such as `if (condition) block`; an identifier inside
3340    /// an expression or call argument must not receive an artificial `;`.
3341    fn macro_formal_needs_statement_separator(node: Node<'_>) -> bool {
3342        let mut current = node;
3343        let mut crossed_recovery = false;
3344        while let Some(parent) = current.parent() {
3345            if parent.is_error() {
3346                crossed_recovery = true;
3347                current = parent;
3348                continue;
3349            }
3350            if matches!(
3351                parent.kind(),
3352                "call_expression"
3353                    | "argument_list"
3354                    | "field_expression"
3355                    | "binary_expression"
3356                    | "unary_expression"
3357                    | "assignment_expression"
3358                    | "conditional_expression"
3359                    | "parenthesized_expression"
3360                    | "subscript_expression"
3361            ) {
3362                return false;
3363            }
3364            if parent.kind() == "expression_statement" {
3365                return single_named_child_matching(parent, |child| !child.is_extra()).is_some();
3366            }
3367            if parent.kind() == "compound_statement" && current == node {
3368                return true;
3369            }
3370            if matches!(
3371                parent.kind(),
3372                "compound_statement" | "if_statement" | "while_statement" | "do_statement"
3373            ) {
3374                return crossed_recovery
3375                    || parent.child_by_field_name("consequence") == Some(current);
3376            }
3377            if matches!(parent.kind(), "declaration" | "init_declarator") {
3378                return false;
3379            }
3380            current = parent;
3381        }
3382        false
3383    }
3384
3385    /// Identify fixed macro formals that retain an unambiguous type role even
3386    /// when an unrelated part of the replacement needs parser recovery.
3387    ///
3388    /// Variadic production macros commonly contain token pasting elsewhere in
3389    /// the body. The strict shared replacement parser must keep rejecting that
3390    /// body for expansion modeling, but a type declaration such as `TY col`
3391    /// remains usable when the node itself retains a structured type role.
3392    fn macro_type_parameter_indices(
3393        &self,
3394        key: &(ProjectFile, usize),
3395        parameters: &[String],
3396        replacement: &str,
3397    ) -> Option<Arc<[usize]>> {
3398        if let Some(indices) = self
3399            .macro_type_parameters
3400            .lock()
3401            .expect("C++ macro type-parameter cache poisoned")
3402            .get(key)
3403        {
3404            return indices.clone();
3405        }
3406        #[cfg(any(test, feature = "test-support"))]
3407        self.macro_replacement_parse_count
3408            .fetch_add(1, Ordering::Relaxed);
3409        let indices = (|| {
3410            if replacement.trim().is_empty() {
3411                return None;
3412            }
3413            // Keep this parser permissive. Type-role extraction predates the
3414            // replacement-body model and deliberately survives unrelated
3415            // token-pasting or recovery elsewhere in a production macro.
3416            let parsed = parse_sentinel(
3417                MACRO_BODY_SENTINEL_PREFIX,
3418                SentinelInput::replacement(replacement),
3419                MACRO_BODY_SENTINEL_SUFFIX,
3420            );
3421            let (source, tree, original_offsets) =
3422                (parsed.source, parsed.tree, parsed.original_offsets);
3423            let body = ParsedReplacementBody {
3424                source,
3425                tree,
3426                body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3427                parameters: parameters.to_vec(),
3428                original_offsets: original_offsets.into_boxed_slice(),
3429            };
3430            macro_replacement_type_parameters(&body, parameters).map(Arc::from)
3431        })();
3432        self.macro_type_parameters
3433            .lock()
3434            .expect("C++ macro type-parameter cache poisoned")
3435            .insert(key.clone(), indices.clone());
3436        indices
3437    }
3438
3439    fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
3440        let replacement = if node.kind() == "preproc_function_def" {
3441            function_macro_replacement_span(node, source)
3442                .and_then(|span| source.get(span))
3443                .map(str::to_owned)
3444                .or_else(|| {
3445                    node.child_by_field_name("value")
3446                        .map(|value| node_text(value, source).to_string())
3447                })
3448                .unwrap_or_default()
3449        } else {
3450            node.child_by_field_name("value")
3451                .map(|value| node_text(value, source).to_string())
3452                .unwrap_or_default()
3453        };
3454        if node.kind() == "preproc_def" {
3455            return MacroDefinition::Object { replacement };
3456        }
3457        let Some(parameters) = node.child_by_field_name("parameters") else {
3458            return MacroDefinition::Unsupported;
3459        };
3460        let variadic = (0..parameters.child_count()).any(|index| {
3461            parameters
3462                .child(index)
3463                .is_some_and(|child| child.kind() == "...")
3464        });
3465        let parameters = (0..parameters.named_child_count())
3466            .filter_map(|index| parameters.named_child(index))
3467            .map(|parameter| node_text(parameter, source).to_string())
3468            .collect::<Vec<_>>();
3469        if variadic {
3470            MacroDefinition::VariadicFunction {
3471                parameters,
3472                replacement,
3473            }
3474        } else {
3475            MacroDefinition::Function {
3476                parameters,
3477                replacement,
3478            }
3479        }
3480    }
3481
3482    pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
3483        clone_or_default_file_cell(
3484            &self.macro_event_cells,
3485            file,
3486            "C++ macro event cache poisoned",
3487        )
3488    }
3489
3490    fn file_defines_macro_name(&self, file: &ProjectFile, name: &str) -> bool {
3491        if let Some(names) = self
3492            .macro_event_name_sets
3493            .lock()
3494            .expect("C++ macro event-name cache poisoned")
3495            .get(file)
3496            .cloned()
3497        {
3498            return names.contains(name);
3499        }
3500        let cell = self.macro_event_cell(file);
3501        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3502        let names = Arc::new(
3503            events
3504                .iter()
3505                .filter_map(|event| match event {
3506                    MacroEvent::Define { name, .. } => Some(name.clone()),
3507                    MacroEvent::Undef { .. }
3508                    | MacroEvent::Include { .. }
3509                    | MacroEvent::Invalidate { .. } => None,
3510                })
3511                .collect(),
3512        );
3513        self.macro_event_name_sets
3514            .lock()
3515            .expect("C++ macro event-name cache poisoned")
3516            .insert(file.clone(), Arc::clone(&names));
3517        names.contains(name)
3518    }
3519
3520    fn macro_environment_checkpoint_cell(
3521        &self,
3522        file: &ProjectFile,
3523    ) -> MacroEnvironmentCheckpointCell {
3524        clone_or_default_file_cell(
3525            &self.macro_environment_checkpoints,
3526            file,
3527            "C++ macro environment checkpoint cache poisoned",
3528        )
3529    }
3530
3531    /// The environment of `file`'s macro events applied up to `before_byte`.
3532    pub fn macro_environment(
3533        &self,
3534        file: &ProjectFile,
3535        before_byte: usize,
3536    ) -> Arc<MacroEnvironment> {
3537        #[cfg(any(test, feature = "test-support"))]
3538        self.macro_environment_request_count
3539            .fetch_add(1, Ordering::Relaxed);
3540        let cell = self.macro_event_cell(file);
3541        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3542        let frontier = events.partition_point(|event| event.byte() < before_byte);
3543        let checkpoint_cell = self.macro_environment_checkpoint_cell(file);
3544        let checkpoints =
3545            checkpoint_cell.get_or_init(|| self.build_macro_environment_checkpoints(file, events));
3546        let checkpoint = checkpoints.at_or_before(frontier);
3547        if checkpoint.frontier == frontier {
3548            return Arc::clone(&checkpoint.environment);
3549        }
3550        #[cfg(any(test, feature = "test-support"))]
3551        self.macro_environment_copy_count
3552            .fetch_add(1, Ordering::Relaxed);
3553        let mut environment = checkpoint.environment.as_ref().clone();
3554        let mut include_stack = HashSet::from_iter([file.clone()]);
3555        for event in &events[checkpoint.frontier..frontier] {
3556            self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3557        }
3558        Arc::new(environment)
3559    }
3560
3561    /// Apply `file`'s events once, keeping the environment at the prefixes
3562    /// [`MacroEnvironmentCheckpoints`] describes.
3563    fn build_macro_environment_checkpoints(
3564        &self,
3565        file: &ProjectFile,
3566        events: &[MacroEvent],
3567    ) -> MacroEnvironmentCheckpoints {
3568        #[cfg(any(test, feature = "test-support"))]
3569        self.macro_environment_checkpoint_build_count
3570            .fetch_add(1, Ordering::Relaxed);
3571        // The TU's build-proven defines hold from the first byte (#2011): they
3572        // are facts of the whole compile, so they seed the frontier-zero
3573        // checkpoint. A later explicit #undef event still overrides them
3574        // through `known_undefined_names`.
3575        let mut environment = MacroEnvironment {
3576            build_proven_defines: self
3577                .compile_proven_guards(file)
3578                .iter()
3579                .filter_map(|guard| match guard {
3580                    PreprocessorGuard::Defined(name) => Some(name.clone()),
3581                    _ => None,
3582                })
3583                .collect(),
3584            ..MacroEnvironment::default()
3585        };
3586        let mut checkpoints = vec![MacroEnvironmentCheckpoint {
3587            frontier: 0,
3588            environment: Arc::new(environment.clone()),
3589        }];
3590        // Keep roughly one fixed stride's worth of checkpoints, while taking
3591        // a checkpoint at every event for event sets no larger than the fixed
3592        // stride. Generated C tables commonly have thousands of uses between
3593        // one #define and a trailing #undef; with a fixed stride those
3594        // identical frontier requests would all copy and replay the same
3595        // prefix.
3596        let checkpoint_stride = events
3597            .len()
3598            .div_ceil(MACRO_ENVIRONMENT_CHECKPOINT_STRIDE)
3599            .clamp(1, MACRO_ENVIRONMENT_CHECKPOINT_STRIDE);
3600        let mut include_stack = HashSet::from_iter([file.clone()]);
3601        for (index, event) in events.iter().enumerate() {
3602            self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3603            let frontier = index + 1;
3604            if frontier % checkpoint_stride == 0 || matches!(event, MacroEvent::Include { .. }) {
3605                checkpoints.push(MacroEnvironmentCheckpoint {
3606                    frontier,
3607                    environment: Arc::new(environment.clone()),
3608                });
3609            }
3610        }
3611        MacroEnvironmentCheckpoints { checkpoints }
3612    }
3613
3614    /// Whether `name` is bound as a macro at `before_byte` in `file`,
3615    /// including a binding this environment cannot pin to one replacement
3616    /// (a conditional `#define`, or a function-like macro).
3617    ///
3618    /// [`Self::object_macro_replacement_at`] collapses every such binding to
3619    /// `None`, which is indistinguishable from "not a macro at all". A caller
3620    /// that must not read a macro token as an ordinary type name needs the two
3621    /// apart: an unexpandable macro is an unknown, a plain identifier is not.
3622    pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
3623        self.macro_environment(file, before_byte)
3624            .binding(name)
3625            .is_some()
3626    }
3627
3628    pub fn macro_name_may_be_bound_at(
3629        &self,
3630        file: &ProjectFile,
3631        name: &str,
3632        before_byte: usize,
3633    ) -> bool {
3634        self.macro_environment(file, before_byte).may_bind(name)
3635    }
3636
3637    /// Whether the active macro binding at this reference is the requested
3638    /// indexed definition. Name equality alone is not enough because two
3639    /// headers can define the same macro for different translation units.
3640    pub fn macro_binding_matches_target_at(
3641        &self,
3642        analyzer: &CppGraphSource<'_>,
3643        file: &ProjectFile,
3644        name: &str,
3645        before_byte: usize,
3646        target: &CodeUnit,
3647    ) -> bool {
3648        let ranges = analyzer.ranges(target);
3649        let declaration_bytes = self.macro_declaration_bytes(target, &ranges);
3650        self.macro_binding_matches_target_declaration_at(
3651            file,
3652            name,
3653            before_byte,
3654            target.source(),
3655            &declaration_bytes,
3656        )
3657    }
3658
3659    /// Resolve declaration ranges once for inverse scans that test many call
3660    /// sites against one macro target. Walking a large generated syntax tree
3661    /// back to the same `#define` for every call site is otherwise quadratic
3662    /// in the number of top-level declarations.
3663    pub(crate) fn macro_declaration_bytes(
3664        &self,
3665        target: &CodeUnit,
3666        ranges: &[Range],
3667    ) -> Vec<usize> {
3668        let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
3669            return Vec::new();
3670        };
3671        ranges
3672            .iter()
3673            .filter_map(|range| {
3674                let mut node = node_for_exact_range(prepared.tree().root_node(), range)?;
3675                while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
3676                    node = node.parent()?;
3677                }
3678                Some(node.start_byte())
3679            })
3680            .collect()
3681    }
3682
3683    pub(crate) fn macro_binding_matches_target_declaration_at(
3684        &self,
3685        file: &ProjectFile,
3686        name: &str,
3687        before_byte: usize,
3688        target_source: &ProjectFile,
3689        target_declaration_bytes: &[usize],
3690    ) -> bool {
3691        let environment = self.macro_environment(file, before_byte);
3692        let Some(binding) = environment.binding(name) else {
3693            return false;
3694        };
3695        if binding.definition == MacroDefinition::Unsupported {
3696            return false;
3697        }
3698        // A normal header guard makes the replacement text conditional, but
3699        // it does not erase the definition site's source and byte identity.
3700        // Keep that identity even when expansion details are not exact.
3701        if binding.source != *target_source {
3702            return false;
3703        }
3704        target_declaration_bytes.contains(&binding.declaration_byte)
3705    }
3706
3707    /// Resolve an ordinary expression-position macro token at its exact byte.
3708    ///
3709    /// Calls and preprocessor-condition tokens have separate resolution
3710    /// surfaces. Declaration names, macro parameters, and labels are not
3711    /// references. Keeping that role policy here makes forward and both
3712    /// inverse graph builders consume the same activation verdict (#2093).
3713    pub fn resolve_ordinary_macro_reference(
3714        &self,
3715        analyzer: &CppGraphSource<'_>,
3716        file: &ProjectFile,
3717        node: Node<'_>,
3718        source: &str,
3719    ) -> OrdinaryMacroReferenceResolution {
3720        if !is_ordinary_macro_reference_node(node) {
3721            return OrdinaryMacroReferenceResolution::Missing;
3722        }
3723        let name = node_text(node, source);
3724        if name.is_empty() {
3725            return OrdinaryMacroReferenceResolution::Missing;
3726        }
3727        let visible = self
3728            .visible_identifier_candidates(file, name)
3729            .filter(|candidate| candidate.is_macro())
3730            .cloned()
3731            .collect::<Vec<_>>();
3732        let mut exact = Vec::new();
3733        for candidate in &visible {
3734            if self.macro_binding_matches_target_at(
3735                analyzer,
3736                file,
3737                name,
3738                node.start_byte(),
3739                candidate,
3740            ) && !exact
3741                .iter()
3742                .any(|existing| same_visible_symbol(existing, candidate))
3743            {
3744                exact.push(candidate.clone());
3745            }
3746        }
3747        match exact.len() {
3748            1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
3749            2.. => OrdinaryMacroReferenceResolution::Ambiguous,
3750            0 if !visible.is_empty()
3751                && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
3752            {
3753                OrdinaryMacroReferenceResolution::Ambiguous
3754            }
3755            0 => OrdinaryMacroReferenceResolution::Missing,
3756        }
3757    }
3758
3759    /// Collect reference-capable C tokens beneath tree-sitter recovery nodes.
3760    ///
3761    /// The ordinary census deliberately skips every `ERROR` subtree. This
3762    /// separate, precision-only frontier admits only roles that retain enough
3763    /// structure for the C usage graph to interpret independently (#2089).
3764    /// Macro evidence comes from this visibility index at the exact byte; no
3765    /// source-text parsing or terminal-name fallback is used.
3766    pub fn recovered_c_reference_ranges(
3767        &self,
3768        file: &ProjectFile,
3769        root: Node<'_>,
3770        source: &str,
3771        limit: usize,
3772    ) -> RecoveredCReferenceRanges {
3773        if !is_c_source_file(file) {
3774            return RecoveredCReferenceRanges::Complete(Vec::new());
3775        }
3776        let mut ranges = Vec::new();
3777        let mut seen = HashSet::default();
3778        let mut stack = vec![(root, root.is_error())];
3779        while let Some((node, inside_error)) = stack.pop() {
3780            // A `macro_type_specifier` is the macro-declaration grammar's
3781            // structured form of the same evidence an `ERROR` carried: it is
3782            // admitted only where the parse would otherwise fail, so the type
3783            // it wraps keeps its structured references.
3784            let inside_error =
3785                inside_error || node.is_error() || node.kind() == "macro_type_specifier";
3786            if node.kind() == "preproc_arg" {
3787                // Tree-sitter keeps an object-like replacement opaque. The
3788                // inverse extractor uses the same parsed replacement helper;
3789                // retain its exact macro/type leaves for precision membership
3790                // even when the preprocessor node is outside ERROR recovery.
3791                let macro_value_kind = node.parent().and_then(|parent| {
3792                    (parent.child_by_field_name("value") == Some(node)).then_some(parent.kind())
3793                });
3794                if matches!(
3795                    macro_value_kind,
3796                    Some("preproc_def" | "preproc_function_def")
3797                ) {
3798                    let name = node_text(node, source);
3799                    if !name.is_empty()
3800                        && self.macro_name_may_be_bound_at(file, name, node.start_byte())
3801                        && !push_recovered_c_range(
3802                            &mut ranges,
3803                            &mut seen,
3804                            node.start_byte(),
3805                            node.end_byte(),
3806                            node,
3807                            limit,
3808                        )
3809                    {
3810                        return RecoveredCReferenceRanges::LimitExceeded;
3811                    }
3812                }
3813                if macro_value_kind == Some("preproc_def") {
3814                    for reference in object_macro_replacement_type_references(node, source) {
3815                        for range in reference.component_ranges {
3816                            let visible = self
3817                                .visible_identifier_candidates(file, &source[range.clone()])
3818                                .any(|candidate| candidate.is_class() || candidate.is_module());
3819                            if visible
3820                                && !push_recovered_c_range(
3821                                    &mut ranges,
3822                                    &mut seen,
3823                                    range.start,
3824                                    range.end,
3825                                    node,
3826                                    limit,
3827                                )
3828                            {
3829                                return RecoveredCReferenceRanges::LimitExceeded;
3830                            }
3831                        }
3832                    }
3833                }
3834            }
3835            if inside_error
3836                && recovered_c_reference_node(self, file, node, source)
3837                && !push_recovered_c_range(
3838                    &mut ranges,
3839                    &mut seen,
3840                    node.start_byte(),
3841                    node.end_byte(),
3842                    node,
3843                    limit,
3844                )
3845            {
3846                return RecoveredCReferenceRanges::LimitExceeded;
3847            }
3848            let mut cursor = node.walk();
3849            for child in node.named_children(&mut cursor) {
3850                stack.push((child, inside_error));
3851            }
3852        }
3853        ranges.sort_unstable();
3854        RecoveredCReferenceRanges::Complete(ranges)
3855    }
3856
3857    /// Whether this target is an indexed macro visible from this file.
3858    ///
3859    /// An unresolved conditional can make more than one same-name macro a
3860    /// possible active binding. Each possible target can keep the site as an
3861    /// unproven hit. A macro in an unrelated translation unit stays excluded.
3862    pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
3863        self.visible_identifier_candidates(file, target.identifier())
3864            .filter(|candidate| candidate.is_macro())
3865            .any(|candidate| {
3866                candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
3867            })
3868    }
3869
3870    pub fn object_macro_replacement_at(
3871        &self,
3872        file: &ProjectFile,
3873        name: &str,
3874        before_byte: usize,
3875    ) -> Option<String> {
3876        let environment = self.macro_environment(file, before_byte);
3877        let binding = environment.binding(name)?;
3878        if !binding.exact {
3879            return None;
3880        }
3881        match &binding.definition {
3882            MacroDefinition::Object { replacement } => Some(replacement.clone()),
3883            MacroDefinition::Function { .. }
3884            | MacroDefinition::VariadicFunction { .. }
3885            | MacroDefinition::Unsupported => None,
3886        }
3887    }
3888
3889    fn apply_macro_events(
3890        &self,
3891        file: &ProjectFile,
3892        environment: &mut MacroEnvironment,
3893        include_stack: &mut HashSet<ProjectFile>,
3894    ) {
3895        if !include_stack.insert(file.clone()) {
3896            return;
3897        }
3898        if self.cpp.prepared_syntax(self.token, file).is_none() {
3899            environment.mark_unknown_names(file, 0);
3900            include_stack.remove(file);
3901            return;
3902        }
3903        match self.macro_include_protection(file) {
3904            MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
3905                Some(binding) if binding.is_exact() => {
3906                    include_stack.remove(file);
3907                    return;
3908                }
3909                Some(_) | None if environment.unknown_names => {
3910                    let mut ambiguous_seen = HashSet::default();
3911                    self.mark_macro_events_ambiguous(
3912                        file,
3913                        environment,
3914                        &mut ambiguous_seen,
3915                        file,
3916                        0,
3917                    );
3918                    include_stack.remove(file);
3919                    return;
3920                }
3921                Some(_) => {
3922                    let mut ambiguous_seen = HashSet::default();
3923                    self.mark_macro_events_ambiguous(
3924                        file,
3925                        environment,
3926                        &mut ambiguous_seen,
3927                        file,
3928                        0,
3929                    );
3930                    include_stack.remove(file);
3931                    return;
3932                }
3933                None => {}
3934            },
3935            MacroIncludeProtection::PragmaOnce => {
3936                if !environment.applied_pragma_once_files.insert(file.clone()) {
3937                    include_stack.remove(file);
3938                    return;
3939                }
3940                if environment.maybe_applied_pragma_once_files.remove(file) {
3941                    // A prior conditional include may already have consumed the pragma-once
3942                    // header. This unconditional include guarantees it is consumed now, but
3943                    // cannot prove whether its events occur before or after intervening local
3944                    // macro changes, so preserve the union as ambiguous.
3945                    let mut ambiguous_seen = HashSet::default();
3946                    environment.applied_pragma_once_files.remove(file);
3947                    self.mark_macro_events_ambiguous(
3948                        file,
3949                        environment,
3950                        &mut ambiguous_seen,
3951                        file,
3952                        0,
3953                    );
3954                    environment.maybe_applied_pragma_once_files.remove(file);
3955                    environment.applied_pragma_once_files.insert(file.clone());
3956                    include_stack.remove(file);
3957                    return;
3958                }
3959            }
3960            MacroIncludeProtection::None => {}
3961        }
3962        let cell = self.macro_event_cell(file);
3963        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3964        for event in events {
3965            self.apply_macro_event(file, event, environment, include_stack);
3966        }
3967        include_stack.remove(file);
3968    }
3969
3970    fn apply_macro_event(
3971        &self,
3972        file: &ProjectFile,
3973        event: &MacroEvent,
3974        environment: &mut MacroEnvironment,
3975        include_stack: &mut HashSet<ProjectFile>,
3976    ) {
3977        #[cfg(any(test, feature = "test-support"))]
3978        self.macro_event_application_count
3979            .fetch_add(1, Ordering::Relaxed);
3980        match event {
3981            MacroEvent::Define {
3982                name,
3983                binding,
3984                conditionals,
3985                byte,
3986            } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3987                Some(true) => environment.insert(name.clone(), binding.clone()),
3988                Some(false) => {}
3989                None => Self::merge_conditional_macro_definition(
3990                    environment,
3991                    name,
3992                    binding,
3993                    file,
3994                    *byte,
3995                ),
3996            },
3997            MacroEvent::Undef {
3998                name,
3999                conditionals,
4000                byte,
4001            } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
4002                Some(true) => environment.remove(name),
4003                Some(false) => {}
4004                None => {
4005                    if environment.binding(name).is_some() {
4006                        environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
4007                    }
4008                }
4009            },
4010            MacroEvent::Include {
4011                targets,
4012                conditionals,
4013                byte,
4014            } => {
4015                let condition =
4016                    self.macro_event_condition_value(file, *byte, environment, conditionals);
4017                if condition == Some(false) {
4018                    return;
4019                }
4020                if targets.is_empty() {
4021                    environment.mark_unknown_names(file, *byte);
4022                    return;
4023                }
4024                if condition.is_none() || targets.len() > 1 {
4025                    let mut ambiguous_seen = HashSet::default();
4026                    for target in targets {
4027                        self.mark_macro_events_ambiguous(
4028                            target,
4029                            environment,
4030                            &mut ambiguous_seen,
4031                            file,
4032                            *byte,
4033                        );
4034                    }
4035                } else if let Some(target) = targets.first() {
4036                    self.apply_macro_events(target, environment, include_stack);
4037                }
4038            }
4039            MacroEvent::Invalidate { byte } => {
4040                for binding in environment.bindings.values_mut() {
4041                    *binding = MacroBinding::uncertain_from(binding, file, *byte);
4042                }
4043            }
4044        }
4045    }
4046
4047    /// Evaluate the structured conditional path that owns one macro event.
4048    ///
4049    /// `Some(true)` and `Some(false)` are proofs from exact macro bindings at
4050    /// this source byte. `None` preserves the old conditional merge when a
4051    /// build/configuration input or an unsupported expression is involved.
4052    ///
4053    /// `conditionals` is the event's own [`OwningPreprocessorConditionals`]:
4054    /// which ancestors structurally own the event was decided when the events
4055    /// were collected, so all this walk does is find those nodes again and ask
4056    /// the environment what their conditions are worth here.
4057    fn macro_event_condition_value(
4058        &self,
4059        file: &ProjectFile,
4060        event_byte: usize,
4061        environment: &MacroEnvironment,
4062        conditionals: &OwningPreprocessorConditionals,
4063    ) -> Option<bool> {
4064        if conditionals.is_empty() {
4065            return Some(true);
4066        }
4067        let prepared = self.cpp.prepared_syntax(self.token, file)?;
4068        let source = prepared.source();
4069        let root = prepared.tree().root_node();
4070        let descendant = root.descendant_for_byte_range(
4071            event_byte,
4072            event_byte.saturating_add(1).min(source.len()),
4073        )?;
4074        let mut unknown = false;
4075        let mut current = descendant.parent();
4076        while let Some(conditional) = current {
4077            if matches!(
4078                conditional.kind(),
4079                "preproc_if" | "preproc_ifdef" | "preproc_elif"
4080            ) && conditionals.contains(&conditional.start_byte())
4081            {
4082                let mut value = match conditional.kind() {
4083                    "preproc_ifdef" => {
4084                        let name = conditional.child_by_field_name("name")?;
4085                        let defined =
4086                            self.macro_name_defined_value(environment, node_text(name, source));
4087                        match conditional.child(0)?.kind() {
4088                            "#ifdef" => defined,
4089                            "#ifndef" => defined.map(|defined| !defined),
4090                            _ => None,
4091                        }
4092                    }
4093                    "preproc_if" | "preproc_elif" => conditional
4094                        .child_by_field_name("condition")
4095                        .and_then(|condition| {
4096                            self.preprocessor_integer_value(
4097                                condition,
4098                                source,
4099                                environment,
4100                                &mut Vec::new(),
4101                                0,
4102                            )
4103                        })
4104                        .map(|value| value != 0),
4105                    _ => unreachable!(),
4106                };
4107                match preprocessor_branch_of_descendant(
4108                    conditional,
4109                    descendant,
4110                    &mut PreprocessorContextCache::default(),
4111                ) {
4112                    PreprocessorBranch::Consequence => {}
4113                    PreprocessorBranch::Structured(_) | PreprocessorBranch::DisplacedElse => {
4114                        value = value.map(|value| !value);
4115                    }
4116                    PreprocessorBranch::Unreadable => value = None,
4117                }
4118                match value {
4119                    Some(true) => {}
4120                    Some(false) => return Some(false),
4121                    None => unknown = true,
4122                }
4123            }
4124            current = conditional.parent();
4125        }
4126        (!unknown).then_some(true)
4127    }
4128
4129    fn macro_name_defined_value(&self, environment: &MacroEnvironment, name: &str) -> Option<bool> {
4130        if environment.known_undefined_names.contains(name) {
4131            return Some(false);
4132        }
4133        if let Some(binding) = environment.binding(name) {
4134            return binding.is_exact().then_some(true);
4135        }
4136        environment
4137            .build_proven_defines
4138            .contains(name)
4139            .then_some(true)
4140    }
4141
4142    fn preprocessor_integer_value(
4143        &self,
4144        expression: Node<'_>,
4145        source: &str,
4146        environment: &MacroEnvironment,
4147        expansion_stack: &mut Vec<(ProjectFile, usize)>,
4148        depth: usize,
4149    ) -> Option<i128> {
4150        // Macro replacement graphs can cycle. This explicit bound makes the
4151        // otherwise recursive AST evaluation stack-safe for hostile input.
4152        if depth >= 64 {
4153            return None;
4154        }
4155        match expression.kind() {
4156            "number_literal" => parse_cpp_integer_literal(node_text(expression, source)),
4157            "identifier" | "type_identifier" => {
4158                let binding = environment.binding(node_text(expression, source))?;
4159                if !binding.is_exact() {
4160                    return None;
4161                }
4162                let MacroDefinition::Object { replacement } = &binding.definition else {
4163                    return None;
4164                };
4165                let identity = (binding.source.clone(), binding.declaration_byte);
4166                if expansion_stack.contains(&identity) {
4167                    return None;
4168                }
4169                expansion_stack.push(identity);
4170                let parsed = self.parsed_macro_replacement(binding, replacement);
4171                let value = match parsed.as_ref() {
4172                    ParsedMacroReplacement::Parsed {
4173                        source: replacement_source,
4174                        tree,
4175                    } => first_descendant_of_kind(tree.root_node(), "call_expression")
4176                        .and_then(|call| call.child_by_field_name("arguments"))
4177                        .and_then(|arguments| argument_children(arguments).next())
4178                        .and_then(|argument| {
4179                            self.preprocessor_integer_value(
4180                                argument,
4181                                replacement_source,
4182                                environment,
4183                                expansion_stack,
4184                                depth + 1,
4185                            )
4186                        }),
4187                    ParsedMacroReplacement::Unsupported => None,
4188                };
4189                expansion_stack.pop();
4190                value
4191            }
4192            "preproc_defined" => {
4193                let mut cursor = expression.walk();
4194                let name = expression
4195                    .named_children(&mut cursor)
4196                    .find(|child| child.kind() == "identifier")?;
4197                self.macro_name_defined_value(environment, node_text(name, source))
4198                    .map(i128::from)
4199            }
4200            "parenthesized_expression" => expression.named_child(0).and_then(|child| {
4201                self.preprocessor_integer_value(
4202                    child,
4203                    source,
4204                    environment,
4205                    expansion_stack,
4206                    depth + 1,
4207                )
4208            }),
4209            "unary_expression" => {
4210                let operator = expression.child_by_field_name("operator")?.kind();
4211                let argument = expression.child_by_field_name("argument")?;
4212                let value = self.preprocessor_integer_value(
4213                    argument,
4214                    source,
4215                    environment,
4216                    expansion_stack,
4217                    depth + 1,
4218                )?;
4219                match operator {
4220                    "+" => Some(value),
4221                    "-" => value.checked_neg(),
4222                    "!" => Some(i128::from(value == 0)),
4223                    "~" => Some(!value),
4224                    _ => None,
4225                }
4226            }
4227            "binary_expression" => {
4228                let left = self.preprocessor_integer_value(
4229                    expression.child_by_field_name("left")?,
4230                    source,
4231                    environment,
4232                    expansion_stack,
4233                    depth + 1,
4234                )?;
4235                let right = self.preprocessor_integer_value(
4236                    expression.child_by_field_name("right")?,
4237                    source,
4238                    environment,
4239                    expansion_stack,
4240                    depth + 1,
4241                )?;
4242                match expression.child_by_field_name("operator")?.kind() {
4243                    "+" => left.checked_add(right),
4244                    "-" => left.checked_sub(right),
4245                    "*" => left.checked_mul(right),
4246                    "/" => left.checked_div(right),
4247                    "%" => left.checked_rem(right),
4248                    "<<" => u32::try_from(right)
4249                        .ok()
4250                        .and_then(|shift| left.checked_shl(shift)),
4251                    ">>" => u32::try_from(right)
4252                        .ok()
4253                        .and_then(|shift| left.checked_shr(shift)),
4254                    "<" => Some(i128::from(left < right)),
4255                    "<=" => Some(i128::from(left <= right)),
4256                    ">" => Some(i128::from(left > right)),
4257                    ">=" => Some(i128::from(left >= right)),
4258                    "==" => Some(i128::from(left == right)),
4259                    "!=" => Some(i128::from(left != right)),
4260                    "&" => Some(left & right),
4261                    "|" => Some(left | right),
4262                    "^" => Some(left ^ right),
4263                    "&&" => Some(i128::from(left != 0 && right != 0)),
4264                    "||" => Some(i128::from(left != 0 || right != 0)),
4265                    _ => None,
4266                }
4267            }
4268            _ => None,
4269        }
4270    }
4271
4272    fn mark_macro_events_ambiguous(
4273        &self,
4274        file: &ProjectFile,
4275        environment: &mut MacroEnvironment,
4276        include_stack: &mut HashSet<ProjectFile>,
4277        conditional_file: &ProjectFile,
4278        conditional_byte: usize,
4279    ) {
4280        if !include_stack.insert(file.clone()) {
4281            return;
4282        }
4283        if self.cpp.prepared_syntax(self.token, file).is_none() {
4284            environment.mark_unknown_names(conditional_file, conditional_byte);
4285            return;
4286        }
4287        match self.macro_include_protection(file) {
4288            MacroIncludeProtection::MacroGuard(guard) => {
4289                if environment
4290                    .binding(&guard)
4291                    .is_some_and(MacroBinding::is_exact)
4292                {
4293                    return;
4294                }
4295            }
4296            MacroIncludeProtection::PragmaOnce => {
4297                if environment.applied_pragma_once_files.contains(file) {
4298                    return;
4299                }
4300                environment
4301                    .maybe_applied_pragma_once_files
4302                    .insert(file.clone());
4303            }
4304            MacroIncludeProtection::None => {}
4305        }
4306        let cell = self.macro_event_cell(file);
4307        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
4308        for event in events {
4309            #[cfg(any(test, feature = "test-support"))]
4310            self.macro_event_application_count
4311                .fetch_add(1, Ordering::Relaxed);
4312            match event {
4313                MacroEvent::Define { name, binding, .. } => {
4314                    Self::merge_conditional_macro_definition(
4315                        environment,
4316                        name,
4317                        binding,
4318                        conditional_file,
4319                        conditional_byte,
4320                    );
4321                }
4322                MacroEvent::Undef { name, .. } => {
4323                    if environment.binding(name).is_some() {
4324                        environment.insert(
4325                            name.clone(),
4326                            MacroBinding::ambiguous(conditional_file, conditional_byte),
4327                        );
4328                    } else {
4329                        environment.remove_known_undefined(name);
4330                    }
4331                }
4332                MacroEvent::Include { targets, .. } => {
4333                    if targets.is_empty() {
4334                        environment.mark_unknown_names(conditional_file, conditional_byte);
4335                        continue;
4336                    }
4337                    for target in targets {
4338                        self.mark_macro_events_ambiguous(
4339                            target,
4340                            environment,
4341                            include_stack,
4342                            conditional_file,
4343                            conditional_byte,
4344                        );
4345                    }
4346                }
4347                MacroEvent::Invalidate { .. } => {
4348                    for binding in environment.bindings.values_mut() {
4349                        *binding = MacroBinding::uncertain_from(
4350                            binding,
4351                            conditional_file,
4352                            conditional_byte,
4353                        );
4354                    }
4355                }
4356            }
4357        }
4358    }
4359
4360    fn merge_conditional_macro_definition(
4361        environment: &mut MacroEnvironment,
4362        name: &str,
4363        possible_binding: &MacroBinding,
4364        conditional_file: &ProjectFile,
4365        conditional_byte: usize,
4366    ) {
4367        // A conditional include can revisit an already-active guarded header.
4368        // If the possible branch defines the exact same macro, both outcomes
4369        // leave the binding unchanged; degrading it to Unknown would discard
4370        // proof because of an unrelated unresolved macro name (#2092).
4371        if environment.binding(name).is_some_and(|current| {
4372            current.definition != MacroDefinition::Unsupported
4373                && current.definition == possible_binding.definition
4374        }) {
4375            return;
4376        }
4377        environment.insert(
4378            name.to_string(),
4379            MacroBinding::ambiguous(conditional_file, conditional_byte),
4380        );
4381    }
4382
4383    pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
4384        let cell = self
4385            .macro_include_protection_cells
4386            .lock()
4387            .expect("C++ include protection cache poisoned")
4388            .entry(file.clone())
4389            .or_default()
4390            .clone();
4391        cell.get_or_init(|| {
4392            self.cpp.prepared_syntax(self.token, file).map_or(
4393                MacroIncludeProtection::None,
4394                |prepared| {
4395                    top_level_macro_include_protection(
4396                        prepared.tree().root_node(),
4397                        prepared.source(),
4398                    )
4399                },
4400            )
4401        })
4402        .clone()
4403    }
4404
4405    fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
4406        collect_macro_events(self.cpp, self.token, file)
4407    }
4408
4409    pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
4410        self.ordinary_type_import_cells
4411            .lock()
4412            .expect("C++ ordinary type import cache poisoned")
4413            .entry(file.clone())
4414            .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
4415            .clone()
4416    }
4417
4418    pub fn project_using_index(
4419        &self,
4420        build: impl FnOnce() -> ProjectUsingIndex,
4421    ) -> &ProjectUsingIndex {
4422        self.project_using_index.get_or_init(build)
4423    }
4424
4425    pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
4426        let mut files = self
4427            .visible_source_files_by_root
4428            .values()
4429            .flatten()
4430            .cloned()
4431            .collect::<HashSet<_>>()
4432            .into_iter()
4433            .collect::<Vec<_>>();
4434        files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
4435        files
4436    }
4437
4438    /// The namespace aliases written in `root`'s visible source files, by name.
4439    pub(super) fn namespace_alias_cell(
4440        &self,
4441        root: &ProjectFile,
4442    ) -> super::namespace_alias::NamespaceAliasCell {
4443        self.namespace_alias_sites
4444            .lock()
4445            .expect("C++ namespace alias cache poisoned")
4446            .entry(root.clone())
4447            .or_default()
4448            .clone()
4449    }
4450
4451    /// `root` and every source file its include closure makes visible.
4452    pub(super) fn visible_source_files(&self, root: &ProjectFile) -> Vec<ProjectFile> {
4453        self.visible_source_files_by_root.get(root).map_or_else(
4454            || vec![root.clone()],
4455            |files| {
4456                files
4457                    .iter()
4458                    .chain([root])
4459                    .cloned()
4460                    .collect::<HashSet<_>>()
4461                    .into_iter()
4462                    .collect()
4463            },
4464        )
4465    }
4466
4467    pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
4468        self.visible_source_files_by_root
4469            .get(root)
4470            .is_some_and(|files| files.contains(source))
4471    }
4472
4473    fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
4474        let cached = self
4475            .visible_parser_alias_name_sets
4476            .read()
4477            .expect("visible parser alias-name cache poisoned")
4478            .get(file)
4479            .cloned();
4480        let cell = if let Some(cached) = cached {
4481            cached
4482        } else {
4483            let mut cells = self
4484                .visible_parser_alias_name_sets
4485                .write()
4486                .expect("visible parser alias-name cache poisoned");
4487            Arc::clone(
4488                cells
4489                    .entry(file.clone())
4490                    .or_insert_with(|| Arc::new(OnceLock::new())),
4491            )
4492        };
4493        if let Some(names) = cell.get() {
4494            return names.contains(name);
4495        }
4496        let mut names = HashSet::default();
4497        let visible_files = self
4498            .visible_source_files_by_root
4499            .get(file)
4500            .cloned()
4501            .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
4502        for visible_file in visible_files {
4503            let Some(aliases) = self.canonical_alias_cell(self.cpp, &visible_file) else {
4504                return false;
4505            };
4506            for alias in aliases.get().expect("published canonical aliases").iter() {
4507                names.insert(alias.name.clone());
4508            }
4509        }
4510        #[cfg(any(test, feature = "test-support"))]
4511        self.visible_parser_alias_name_set_build_count
4512            .fetch_add(1, Ordering::Relaxed);
4513        cell.get_or_init(|| names).contains(name)
4514    }
4515
4516    pub fn parser_alias_name_may_resolve_to_target(
4517        &self,
4518        file: &ProjectFile,
4519        alias_name: &str,
4520        target: &CodeUnit,
4521    ) -> bool {
4522        let started = std::time::Instant::now();
4523        self.parser_alias_fallback_calls
4524            .fetch_add(1, Ordering::Relaxed);
4525        let key = (
4526            file.clone(),
4527            alias_name.to_string(),
4528            logical_symbol_key(target),
4529        );
4530        let cached = self
4531            .parser_alias_target_matches
4532            .read()
4533            .expect("parser alias target-match cache poisoned")
4534            .get(&key)
4535            .cloned();
4536        let cell = if let Some(cached) = cached {
4537            cached
4538        } else {
4539            let mut cells = self
4540                .parser_alias_target_matches
4541                .write()
4542                .expect("parser alias target-match cache poisoned");
4543            Arc::clone(
4544                cells
4545                    .entry(key)
4546                    .or_insert_with(|| Arc::new(OnceLock::new())),
4547            )
4548        };
4549        let matched = *cell.get_or_init(|| {
4550            any_visible_source_file_matches(
4551                &self.visible_source_files_by_root,
4552                file,
4553                |visible_file| {
4554                    self.parser_alias_fallback_files
4555                        .fetch_add(1, Ordering::Relaxed);
4556                    self.file_alias_matches(self.cpp, visible_file, alias_name, target)
4557                },
4558            )
4559        });
4560        self.parser_alias_fallback_elapsed_micros.fetch_add(
4561            started.elapsed().as_micros().min(usize::MAX as u128) as usize,
4562            Ordering::Relaxed,
4563        );
4564        matched
4565    }
4566
4567    fn callable_arities_for_target(
4568        &self,
4569        analyzer: &CppGraphSource<'_>,
4570        cpp: &dyn CppSource,
4571        file: &ProjectFile,
4572        prepared: &PreparedSyntaxTree,
4573        spec: &TargetSpec,
4574    ) -> Vec<ActivatedCallableArity> {
4575        let Some(signature) = spec.target.signature() else {
4576            return Vec::new();
4577        };
4578        let Some(candidates) = self
4579            .visible_by_identifier
4580            .get(file)
4581            .and_then(|by_name| by_name.get(&spec.member_name))
4582        else {
4583            return Vec::new();
4584        };
4585        let differing_candidates = candidates
4586            .iter()
4587            .filter(|candidate| {
4588                candidate.is_function()
4589                    && candidate.fq_name() == spec.target.fq_name()
4590                    && candidate.signature() == Some(signature)
4591            })
4592            .filter_map(|candidate| {
4593                analyzer
4594                    .signature_metadata(candidate)
4595                    .into_iter()
4596                    .find_map(|metadata| metadata.callable_arity())
4597                    .filter(|arity| Some(*arity) != spec.callable_arity)
4598                    .map(|arity| (candidate, arity))
4599            })
4600            .collect::<Vec<_>>();
4601        if differing_candidates.is_empty() {
4602            return Vec::new();
4603        }
4604        let mut arities = Vec::with_capacity(differing_candidates.len());
4605        // The activation ranges here describe the whole file rather than one
4606        // reference, so only file-level language and build facts are known.
4607        let effective_guards = |guards: Option<&HashSet<PreprocessorGuard>>| {
4608            self.effective_reference_guards(file, guards)
4609        };
4610        let reference = CallableReferenceContext {
4611            effective_guards: &effective_guards,
4612            file_guards: OnceCell::new(),
4613            file,
4614            position: None,
4615        };
4616        for (candidate, candidate_arity) in differing_candidates {
4617            let declaration_activation =
4618                callable_declaration_activation_in_file(analyzer, candidate, &reference);
4619            let Some(declaration_activation) = declaration_activation else {
4620                continue;
4621            };
4622            let activation_byte = if candidate.source() == file {
4623                Some(declaration_activation)
4624            } else {
4625                self.include_activation_for_source(cpp, file, prepared, candidate.source())
4626            };
4627            if let Some(activation_byte) = activation_byte {
4628                arities.push(ActivatedCallableArity {
4629                    activation_byte,
4630                    arity: candidate_arity,
4631                });
4632            }
4633        }
4634        arities
4635    }
4636
4637    fn callable_parameter_macro_arity(
4638        &self,
4639        target: &CodeUnit,
4640        parameter_types: Option<&[String]>,
4641    ) -> Option<CallableArity> {
4642        let parameter_types = parameter_types?;
4643        let [macro_name] = parameter_types else {
4644            return None;
4645        };
4646        if macro_name.is_empty()
4647            || !macro_name
4648                .chars()
4649                .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
4650        {
4651            return None;
4652        }
4653        let cache_key = (target.source().clone(), macro_name.clone());
4654        if let Some(cached) = self
4655            .callable_parameter_macro_arities
4656            .lock()
4657            .expect("C++ callable parameter-macro arity cache poisoned")
4658            .get(&cache_key)
4659            .copied()
4660        {
4661            return cached;
4662        }
4663        let mut visible_files = HashSet::default();
4664        collect_include_closure(
4665            &self.cpp_source(),
4666            self.cpp.include_target_index(),
4667            target.source(),
4668            &mut visible_files,
4669            None,
4670        );
4671        let mut arities = Vec::new();
4672        for visible_file in visible_files {
4673            let cell = self.macro_event_cell(&visible_file);
4674            for event in
4675                cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
4676            {
4677                let MacroEvent::Define { name, binding, .. } = event else {
4678                    continue;
4679                };
4680                if name != macro_name {
4681                    continue;
4682                }
4683                let MacroDefinition::Object { replacement } = &binding.definition else {
4684                    continue;
4685                };
4686                let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
4687                    continue;
4688                };
4689                if !arities.contains(&arity) {
4690                    arities.push(arity);
4691                }
4692            }
4693        }
4694        let resolved = (|| {
4695            let required = arities
4696                .iter()
4697                .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
4698                .min()?;
4699            let total = arities.iter().map(|arity| arity.total()).max()?;
4700            let repeated = arities
4701                .iter()
4702                .any(|arity| arity.accepts(arity.total().saturating_add(1)));
4703            // Preprocessor conditions can leave more than one object-like parameter
4704            // bundle active in the target header's include closure. Preserve their
4705            // conservative callable envelope instead of choosing whichever definition
4706            // happened to be visited first.
4707            Some(CallableArity::new(required, total, repeated))
4708        })();
4709        self.callable_parameter_macro_arities
4710            .lock()
4711            .expect("C++ callable parameter-macro arity cache poisoned")
4712            .insert(cache_key, resolved);
4713        resolved
4714    }
4715
4716    pub fn include_activation_for_source(
4717        &self,
4718        cpp: &dyn CppSource,
4719        file: &ProjectFile,
4720        prepared: &PreparedSyntaxTree,
4721        donor_source: &ProjectFile,
4722    ) -> Option<usize> {
4723        let key = (file.clone(), donor_source.clone());
4724        if let Some(cached) = self
4725            .include_activation_cells
4726            .lock()
4727            .expect("C++ include activation cache poisoned")
4728            .get(&key)
4729            .copied()
4730        {
4731            return cached;
4732        }
4733        #[cfg(any(test, feature = "test-support"))]
4734        self.include_activation_build_count
4735            .fetch_add(1, Ordering::Relaxed);
4736        let effective = OnceCell::new();
4737        let activation =
4738            find_include_activation(cpp, self.token, file, prepared, donor_source, &|| {
4739                effective
4740                    .get_or_init(|| {
4741                        self.effective_reference_guards(file, Some(&HashSet::default()))
4742                    })
4743                    .as_ref()
4744            });
4745        let mut cells = self
4746            .include_activation_cells
4747            .lock()
4748            .expect("C++ include activation cache poisoned");
4749        *cells.entry(key).or_insert(activation)
4750    }
4751
4752    pub fn conditional_include_projections_for_source(
4753        &self,
4754        file: &ProjectFile,
4755        prepared: &PreparedSyntaxTree,
4756        donor_source: &ProjectFile,
4757    ) -> Arc<[ConditionalIncludeProjection]> {
4758        static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
4759        let cell = self
4760            .conditional_include_projection_cells
4761            .lock()
4762            .expect("C++ conditional include projection cache poisoned")
4763            .entry(file.clone())
4764            .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
4765            .clone();
4766        let index = cell.get_or_build_pool_independent(|| {
4767            #[cfg(any(test, feature = "test-support"))]
4768            self.conditional_include_projection_index_build_count
4769                .fetch_add(1, Ordering::Relaxed);
4770            let effective = OnceCell::new();
4771            find_conditional_include_projection_index(
4772                self.cpp,
4773                self.token,
4774                file,
4775                prepared,
4776                &|| {
4777                    effective
4778                        .get_or_init(|| {
4779                            self.effective_reference_guards(file, Some(&HashSet::default()))
4780                        })
4781                        .as_ref()
4782                },
4783                &|| {
4784                    #[cfg(any(test, feature = "test-support"))]
4785                    self.conditional_include_projection_state_count
4786                        .fetch_add(1, Ordering::Relaxed);
4787                },
4788            )
4789        });
4790        index
4791            .get(donor_source)
4792            .cloned()
4793            .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
4794    }
4795
4796    fn include_language_guard_is_stable(&self, file: &ProjectFile) -> bool {
4797        let mut macro_events = |file: &ProjectFile| {
4798            let cell = self.macro_event_cell(file);
4799            cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice())
4800                .clone()
4801        };
4802        include_language_guard_is_stable_with(
4803            self.cpp,
4804            self.token,
4805            file,
4806            &mut macro_events,
4807            &mut |file: &ProjectFile, prepared: &PreparedSyntaxTree| {
4808                self.structured_include_facts(file, prepared)
4809            },
4810        )
4811    }
4812
4813    #[cfg(any(test, feature = "test-support"))]
4814    pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
4815        (
4816            self.conditional_include_projection_index_build_count
4817                .load(Ordering::Relaxed),
4818            self.conditional_include_projection_state_count
4819                .load(Ordering::Relaxed),
4820        )
4821    }
4822
4823    #[cfg(any(test, feature = "test-support"))]
4824    pub fn conditional_include_target_state_count_for_test(&self) -> usize {
4825        self.conditional_include_target_state_count
4826            .load(Ordering::Relaxed)
4827    }
4828
4829    #[cfg(any(test, feature = "test-support"))]
4830    pub fn include_activation_build_count_for_test(&self) -> usize {
4831        self.include_activation_build_count.load(Ordering::Relaxed)
4832    }
4833
4834    #[cfg(any(test, feature = "test-support"))]
4835    pub fn note_using_donor_activation_for_test(&self) {
4836        self.using_donor_activation_count
4837            .fetch_add(1, Ordering::Relaxed);
4838    }
4839
4840    #[cfg(not(any(test, feature = "test-support")))]
4841    pub fn note_using_donor_activation_for_test(&self) {}
4842
4843    #[cfg(any(test, feature = "test-support"))]
4844    pub fn note_using_namespace_lookup_for_test(&self) {
4845        self.using_namespace_lookup_count
4846            .fetch_add(1, Ordering::Relaxed);
4847    }
4848
4849    #[cfg(not(any(test, feature = "test-support")))]
4850    pub fn note_using_namespace_lookup_for_test(&self) {}
4851
4852    #[cfg(any(test, feature = "test-support"))]
4853    pub fn note_using_name_candidate_inspection_for_test(&self) {
4854        self.using_name_candidate_inspection_count
4855            .fetch_add(1, Ordering::Relaxed);
4856    }
4857
4858    #[cfg(not(any(test, feature = "test-support")))]
4859    pub fn note_using_name_candidate_inspection_for_test(&self) {}
4860
4861    #[cfg(any(test, feature = "test-support"))]
4862    pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
4863        (
4864            self.using_donor_activation_count.load(Ordering::Relaxed),
4865            self.using_namespace_lookup_count.load(Ordering::Relaxed),
4866            self.callable_reference_spec_build_count
4867                .load(Ordering::Relaxed),
4868            self.using_name_candidate_inspection_count
4869                .load(Ordering::Relaxed),
4870        )
4871    }
4872
4873    pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
4874        file == target.source()
4875            || self
4876                .visible_by_file
4877                .get(file)
4878                .is_some_and(|visible| visible.contains(target))
4879    }
4880
4881    /// Whether some declaration of `declaration`'s logical symbol is visible at
4882    /// `reference_byte` in `file`.
4883    ///
4884    /// The question is asked of the *logical* symbol, not of the physical unit:
4885    /// an out-of-line body in a `.cpp` nobody includes is never itself visible,
4886    /// and it does not have to be - what makes the call legal is the header
4887    /// declaration that the reference file does include. Reading that relation
4888    /// through `same_logical_callable` rather than through signature strings is
4889    /// the same #2010 correction the gates make, and it matters here because
4890    /// the body and the declaration are exactly the pair that spells one
4891    /// parameter type two ways.
4892    pub fn declaration_visible_at(
4893        &self,
4894        analyzer: &CppGraphSource<'_>,
4895        file: &ProjectFile,
4896        declaration: &CodeUnit,
4897        reference_byte: usize,
4898    ) -> bool {
4899        let reference_guards = OnceCell::new();
4900        // A prototype must itself be active. Another same-signature prototype
4901        // cannot select its include branch or compilation dialect for it.
4902        if declaration.is_callable() && !callable_target_has_definition_body(analyzer, declaration)
4903        {
4904            return self.physical_declaration_visible_at(
4905                analyzer,
4906                file,
4907                declaration,
4908                reference_byte,
4909                &reference_guards,
4910            );
4911        }
4912        self.visible_identifier_candidates(file, declaration.identifier())
4913            .filter(|candidate| {
4914                // Across dialects, a shared spelling needs the include evidence
4915                // that can connect a C declaration to an extern-C definition.
4916                // Within one dialect retain unresolved link-unit candidates;
4917                // their uncertainty is reported by the navigation consumer.
4918                (if declaration.is_callable()
4919                    && reference_uses_c_semantics(self.cpp, file)
4920                        != reference_uses_c_semantics(self.cpp, declaration.source())
4921                {
4922                    crate::identity::cpp_callable_definitions_share_identity_evidence_with_visibility(
4923                        analyzer,
4924                        self,
4925                        candidate,
4926                        declaration,
4927                        |left, right| {
4928                            crate::identity::cpp_header_body_files_are_related(
4929                                self.cpp, self.token, left, right,
4930                            )
4931                        },
4932                    )
4933                } else {
4934                    self.same_logical_callable(analyzer, candidate, declaration)
4935                })
4936                    || flattened_macro_namespace_declaration_matches(
4937                        analyzer,
4938                        self.cpp,
4939                        file,
4940                        candidate,
4941                        declaration,
4942                        reference_byte,
4943                    )
4944            })
4945            .any(|candidate| {
4946                self.physical_declaration_visible_at(
4947                    analyzer,
4948                    file,
4949                    candidate,
4950                    reference_byte,
4951                    &reference_guards,
4952                )
4953            })
4954    }
4955
4956    /// Candidate guard admission, without claiming that the guards hold.
4957    /// Structured inverse paths use this when visibility proof fails, so an
4958    /// unrelated guard preserves uncertainty while a contradiction is excluded.
4959    pub(crate) fn declaration_guards_compatible_at_reference(
4960        &self,
4961        analyzer: &CppGraphSource<'_>,
4962        file: &ProjectFile,
4963        declaration: &CodeUnit,
4964        reference: Node<'_>,
4965    ) -> bool {
4966        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4967            return false;
4968        };
4969        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4970        let reference_guards = self.effective_reference_guards(file, reference_guards.as_ref());
4971        declaration_guard_requirements(analyzer, self.cpp, declaration)
4972            .iter()
4973            .any(|(_, required)| {
4974                guards_compatible_at_reference(required, reference_guards.as_ref())
4975            })
4976    }
4977
4978    /// Whether a physical declaration is visible at an exact structured
4979    /// reference node. Inverse C field references need the reference's own
4980    /// preprocessor environment before using the callable activation path:
4981    /// callable activation can establish that a field declaration is
4982    /// nameable, but it must not admit the opposite branch of that field's
4983    /// conditional family.
4984    pub fn declaration_visible_at_reference(
4985        &self,
4986        analyzer: &CppGraphSource<'_>,
4987        file: &ProjectFile,
4988        declaration: &CodeUnit,
4989        reference: Node<'_>,
4990    ) -> bool {
4991        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4992            return false;
4993        };
4994        if !self.declaration_guards_compatible_at_reference(analyzer, file, declaration, reference)
4995        {
4996            return false;
4997        }
4998        if declaration.source() == file
4999            && !analyzer.reference_uses_c_semantics(file)
5000            && (declaration.is_field() || declaration.is_callable())
5001            && type_owner_of(analyzer, declaration).is_some_and(|owner| {
5002                self.indexed_enclosing_owner_scope(analyzer, file, reference)
5003                    .is_some_and(|scope| scope == canonical_cpp_scope_components(&owner))
5004            })
5005        {
5006            // C++ complete-class context makes members visible throughout the
5007            // class. The guard check above still excludes a declaration from
5008            // an incompatible preprocessor branch.
5009            return true;
5010        }
5011        if declaration.is_field() && declaration.source() == file {
5012            let reference_byte = reference.start_byte();
5013            let reference_function =
5014                real_function_definition_ancestor(reference, prepared.source());
5015            let guards = OnceCell::new();
5016            let effective_guards = |guards: Option<&HashSet<PreprocessorGuard>>| {
5017                self.effective_reference_guards(file, guards)
5018            };
5019            let field_reference = CallableReferenceContext {
5020                effective_guards: &effective_guards,
5021                file_guards: OnceCell::new(),
5022                file,
5023                position: Some(CallableReferencePosition {
5024                    prepared: prepared.as_ref(),
5025                    byte: reference_byte,
5026                    guards: &guards,
5027                }),
5028            };
5029            let mut has_local_declaration = false;
5030            let mut local_declaration_visible = false;
5031            for declaration in callable_declaration_nodes(analyzer, prepared.as_ref(), declaration)
5032            {
5033                let Some(declaration_function) =
5034                    real_function_definition_ancestor(declaration, prepared.source())
5035                else {
5036                    continue;
5037                };
5038                has_local_declaration = true;
5039                if reference_function.is_some_and(|reference_function| {
5040                    reference_function.start_byte() == declaration_function.start_byte()
5041                        && reference_function.end_byte() == declaration_function.end_byte()
5042                }) && callable_preprocessor_context_is_visible_for_reference(
5043                    declaration,
5044                    prepared.source(),
5045                    &field_reference,
5046                ) && declaration.end_byte() < reference_byte
5047                {
5048                    local_declaration_visible = true;
5049                    break;
5050                }
5051            }
5052            if has_local_declaration {
5053                return local_declaration_visible;
5054            }
5055        }
5056        let guards = OnceCell::new();
5057        self.physical_declaration_visible_at(
5058            analyzer,
5059            file,
5060            declaration,
5061            reference.start_byte(),
5062            &guards,
5063        )
5064    }
5065
5066    /// C forward navigation may bind a call to a later same-file definition.
5067    /// There is no earlier source declaration to activate in that legacy C
5068    /// shape, but the call's preprocessor environment must still imply the
5069    /// definition's requirements. Ordinary C++ and inverse visibility retain
5070    /// the declaration-order rule in [`Self::declaration_visible_at`].
5071    pub fn declaration_visible_for_c_forward_call(
5072        &self,
5073        analyzer: &CppGraphSource<'_>,
5074        file: &ProjectFile,
5075        declaration: &CodeUnit,
5076        reference_byte: usize,
5077    ) -> bool {
5078        if self.declaration_visible_at(analyzer, file, declaration, reference_byte) {
5079            return true;
5080        }
5081        if declaration.source() != file {
5082            return false;
5083        }
5084        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5085            return false;
5086        };
5087        let reference_guards = prepared
5088            .tree()
5089            .root_node()
5090            .descendant_for_byte_range(reference_byte, reference_byte)
5091            .and_then(|node| preprocessor_guard_environment(node, prepared.source()));
5092        declaration_guard_requirements(analyzer, self.cpp, declaration)
5093            .into_iter()
5094            .any(|(_, required)| {
5095                guard_requirements_hold_at_reference(&required, reference_guards.as_ref())
5096            })
5097    }
5098
5099    pub fn callable_arity_at_reference(
5100        &self,
5101        analyzer: &CppGraphSource<'_>,
5102        file: &ProjectFile,
5103        candidate: &CodeUnit,
5104        reference_byte: usize,
5105    ) -> Option<CallableArity> {
5106        let key = (file.clone(), logical_symbol_key(candidate));
5107        let cell = self
5108            .callable_reference_specs
5109            .lock()
5110            .expect("C++ callable reference-spec cache poisoned")
5111            .entry(key)
5112            .or_default()
5113            .clone();
5114        let spec = cell.get_or_init(|| {
5115            let prepared = self.cpp.prepared_syntax(self.token, file)?;
5116            let spec = TargetSpec::from_target(analyzer, candidate)?;
5117            let spec = spec
5118                .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
5119                .into_owned();
5120            #[cfg(any(test, feature = "test-support"))]
5121            self.callable_reference_spec_build_count
5122                .fetch_add(1, Ordering::Relaxed);
5123            Some(spec)
5124        });
5125        spec.as_ref()?.callable_arity_at(reference_byte)
5126    }
5127
5128    fn physical_declaration_visible_at(
5129        &self,
5130        analyzer: &CppGraphSource<'_>,
5131        file: &ProjectFile,
5132        declaration: &CodeUnit,
5133        reference_byte: usize,
5134        reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
5135    ) -> bool {
5136        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5137            return false;
5138        };
5139        let effective_guards = |guards: Option<&HashSet<PreprocessorGuard>>| {
5140            self.effective_reference_guards(file, guards)
5141        };
5142        let reference = CallableReferenceContext {
5143            effective_guards: &effective_guards,
5144            file_guards: OnceCell::new(),
5145            file,
5146            position: Some(CallableReferencePosition {
5147                prepared: prepared.as_ref(),
5148                byte: reference_byte,
5149                guards: reference_guards,
5150            }),
5151        };
5152        if declaration.source() == file {
5153            return callable_declaration_activation_in_file(analyzer, declaration, &reference)
5154                .or_else(|| {
5155                    self.exhaustive_guard_family_activation(analyzer, declaration, &reference)
5156                })
5157                .is_some_and(|activation| activation < reference_byte);
5158        }
5159        if self
5160            .exhaustive_guard_family_activation(analyzer, declaration, &reference)
5161            .is_some()
5162        {
5163            // Structured coverage proves this logical declaration exists in
5164            // every arm. The include path still needs its own proof.
5165            return self.foreign_declaration_reachable_at_reference(
5166                file,
5167                prepared.as_ref(),
5168                declaration.source(),
5169                &HashSet::default(),
5170                reference.guards(),
5171                reference_byte,
5172            );
5173        }
5174        if !self.foreign_callable_declaration_is_active(analyzer, declaration, &reference) {
5175            return false;
5176        }
5177        declaration_guard_requirements(analyzer, self.cpp, declaration)
5178            .into_iter()
5179            .any(|(_, declaration_guards)| {
5180                self.foreign_declaration_reachable_at_reference(
5181                    file,
5182                    prepared.as_ref(),
5183                    declaration.source(),
5184                    &declaration_guards,
5185                    reference.guards(),
5186                    reference_byte,
5187                )
5188            })
5189    }
5190
5191    /// Whether the donor's callable facts admit this candidate. Build coverage
5192    /// selects required macro facts; without it, compatibility permits discovery.
5193    /// `physical_declaration_visible_at` separately requires implication and a
5194    /// proven include path before publishing an exact answer (#6902).
5195    fn foreign_callable_declaration_is_active(
5196        &self,
5197        analyzer: &CppGraphSource<'_>,
5198        declaration: &CodeUnit,
5199        reference: &CallableReferenceContext<'_>,
5200    ) -> bool {
5201        let build_decides = !self.compile_context_is_absent(reference.file);
5202        let active = reference.guards();
5203        analyzer
5204            .declaration_source_occurrences(declaration)
5205            .into_iter()
5206            .flatten()
5207            .any(|occurrence| {
5208                if occurrence.fact.callable_activation.is_none() {
5209                    return false;
5210                }
5211                let Some(required) = occurrence
5212                    .fact
5213                    .callable_guards
5214                    .as_ref()
5215                    .and_then(cpp_guard_set_to_runtime)
5216                else {
5217                    return false;
5218                };
5219                required.is_empty()
5220                    || if build_decides {
5221                        guard_requirements_hold_at_reference(&required, active)
5222                    } else {
5223                        guards_compatible_at_reference(&required, active)
5224                    }
5225            })
5226    }
5227
5228    pub fn external_type_candidate_visible_at(
5229        &self,
5230        file: &ProjectFile,
5231        candidate: &CodeUnit,
5232        reference_byte: usize,
5233    ) -> bool {
5234        if candidate.source() == file {
5235            return true;
5236        }
5237        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5238            return false;
5239        };
5240        self.visible_identifier_candidates(file, candidate.identifier())
5241            .filter(|peer| same_logical_symbol(candidate, peer))
5242            .any(|peer| {
5243                peer.source() == file
5244                    || self
5245                        .include_activation_for_source(
5246                            self.cpp,
5247                            file,
5248                            prepared.as_ref(),
5249                            peer.source(),
5250                        )
5251                        .is_some_and(|activation| activation <= reference_byte)
5252            })
5253    }
5254
5255    pub fn external_type_declaration_visible_at(
5256        &self,
5257        file: &ProjectFile,
5258        candidate: &CodeUnit,
5259        reference_byte: usize,
5260    ) -> bool {
5261        if candidate.source() == file {
5262            return true;
5263        }
5264        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5265            return false;
5266        };
5267        self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
5268            .is_some_and(|activation| activation <= reference_byte)
5269    }
5270
5271    /// The preprocessor facts the build proves for a reference sited in
5272    /// `file` (#2011).
5273    ///
5274    /// Every `-D` that survives its command's `-D`/`-U` ordering is a positive
5275    /// `Defined` fact, and a fact holds only when every compile configuration
5276    /// that governs the file agrees on it (intersection). The facts are
5277    /// strictly additive to the reference's active guard set: they can prove a
5278    /// required guard, but the guard check itself is never weakened and no
5279    /// implication is ever inferred from source text.
5280    ///
5281    /// A file with its own database entry answers from that entry alone
5282    /// (phase 1). A header takes its context from the translation units whose
5283    /// include closure reaches it, intersected across all of them (phase 2):
5284    /// the header is compiled once per including TU, so a fact holds for a
5285    /// header-sited reference only when every one of those compilations
5286    /// proves it. A reaching TU the database does not cover proves nothing,
5287    /// which empties the intersection. A file nothing covers or reaches has
5288    /// no facts and every check runs on source structure alone.
5289    pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
5290        if let Some(cached) = self
5291            .compile_proven_guard_cells
5292            .lock()
5293            .expect("C++ compile-proven guard cache poisoned")
5294            .get(file)
5295        {
5296            return Arc::clone(cached);
5297        }
5298        let proven = Arc::new(compile_proven_guard_facts(self.cpp, file));
5299        self.compile_proven_guard_cells
5300            .lock()
5301            .expect("C++ compile-proven guard cache poisoned")
5302            .insert(file.clone(), Arc::clone(&proven));
5303        proven
5304    }
5305
5306    /// The rule a conditional `#include` path's guards face on the way to a
5307    /// reference in `file`.
5308    ///
5309    /// This is the include-edge analogue of the choice
5310    /// [`Self::foreign_callable_declaration_is_active`] makes for a foreign
5311    /// declaration's own guards (#2988), and it is decided by the same thing:
5312    /// what settles the reference's configuration. Memoized per file because
5313    /// the answer depends on nothing else, and the callers ask it once per
5314    /// candidate.
5315    fn include_path_admission(&self, file: &ProjectFile) -> IncludePathAdmission {
5316        if let Some(cached) = self
5317            .include_path_admission_cells
5318            .lock()
5319            .expect("C++ include-path admission cache poisoned")
5320            .get(file)
5321            .copied()
5322        {
5323            return cached;
5324        }
5325        let admission = if self.compile_context_is_absent(file) {
5326            IncludePathAdmission::Compatible
5327        } else {
5328            IncludePathAdmission::Proven
5329        };
5330        self.include_path_admission_cells
5331            .lock()
5332            .expect("C++ include-path admission cache poisoned")
5333            .insert(file.clone(), admission);
5334        admission
5335    }
5336
5337    /// Whether no compile data covers the compilations of `file`: it has no
5338    /// database entry of its own, and either nothing reaches it or some
5339    /// translation unit that reaches it has no entry. This is the state a
5340    /// regenerated `compile_commands.json` could decide; data that is present
5341    /// for every governing compilation but does not prove a guard is a
5342    /// decided conservative miss, not this state.
5343    fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
5344        if !self.cpp.compile_contexts_for(file).is_empty() {
5345            return false;
5346        }
5347        let translation_units = self.cpp.reaching_translation_units(file);
5348        translation_units.is_empty()
5349            || translation_units
5350                .iter()
5351                .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
5352    }
5353
5354    /// Whether a lookup miss for `identifier` in `file` is explainable by
5355    /// missing compile context (#2011): some same-name declaration is
5356    /// reachable through a conditional include whose required guards neither
5357    /// contradict the reference's active guards nor follow from them, and the
5358    /// translation unit has no compile-commands entry that could decide the
5359    /// question. Callers surface this as an explicit "requires compile
5360    /// context" incompleteness instead of an indistinguishable miss.
5361    ///
5362    /// A structurally disproven declaration (contradicting guards) and a TU
5363    /// whose compile context exists but does not prove the guard both answer
5364    /// `false`: those misses are decided, not incomplete.
5365    pub fn miss_requires_compile_context(
5366        &self,
5367        file: &ProjectFile,
5368        identifier: &str,
5369        reference: Node<'_>,
5370    ) -> bool {
5371        if !self.compile_context_is_absent(file) {
5372            return false;
5373        }
5374        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5375            return false;
5376        };
5377        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5378        let reference_byte = reference.start_byte();
5379        let mut sources = self
5380            .visible_identifier_candidates(file, identifier)
5381            .map(CodeUnit::source)
5382            .filter(|source| *source != file)
5383            .collect::<Vec<_>>();
5384        sources.sort();
5385        sources.dedup();
5386        sources.into_iter().any(|declaration_source| {
5387            self.conditional_include_projections_for_source(
5388                file,
5389                prepared.as_ref(),
5390                declaration_source,
5391            )
5392            .iter()
5393            .any(|projection| {
5394                projection.activation_byte <= reference_byte
5395                    && !guard_requirements_hold_at_reference(
5396                        &projection.required_guards,
5397                        reference_guards.as_ref(),
5398                    )
5399                    && guards_compatible_at_reference(
5400                        &projection.required_guards,
5401                        reference_guards.as_ref(),
5402                    )
5403            })
5404        })
5405    }
5406
5407    /// Supply this query's cached build facts to the shared guard constructor.
5408    fn effective_reference_guards(
5409        &self,
5410        file: &ProjectFile,
5411        reference_guards: Option<&HashSet<PreprocessorGuard>>,
5412    ) -> Option<HashSet<PreprocessorGuard>> {
5413        effective_reference_guards(
5414            self.cpp,
5415            file,
5416            reference_guards,
5417            &self.compile_proven_guards(file),
5418            &|| self.include_language_guard_is_stable(file),
5419        )
5420    }
5421
5422    /// Decide whether a declaration that lives in another file reaches a
5423    /// reference in `file`.
5424    ///
5425    /// Candidate discovery admits non-contradicting guards. Proven visibility
5426    /// additionally requires the declaration's guards to follow from the
5427    /// effective reference context; independent opaque conditions imply nothing
5428    /// about one another (#6902).
5429    fn foreign_declaration_reachable_at_reference(
5430        &self,
5431        file: &ProjectFile,
5432        prepared: &PreparedSyntaxTree,
5433        declaration_source: &ProjectFile,
5434        declaration_guards: &HashSet<PreprocessorGuard>,
5435        reference_guards: Option<&HashSet<PreprocessorGuard>>,
5436        reference_byte: usize,
5437    ) -> bool {
5438        let effective = self.effective_reference_guards(file, reference_guards);
5439        let reference_guards = effective.as_ref();
5440        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5441            eprintln!(
5442                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=foreign_guard_compatibility declaration_source={} declaration_guards={declaration_guards:?} reference_guards={reference_guards:?}",
5443                declaration_source.rel_path().display(),
5444            );
5445        }
5446        if !guard_requirements_hold_at_reference(declaration_guards, reference_guards) {
5447            return false;
5448        }
5449        if self
5450            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5451            .is_some_and(|activation| activation <= reference_byte)
5452        {
5453            return true;
5454        }
5455        let projections =
5456            self.conditional_include_projections_for_source(file, prepared, declaration_source);
5457        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5458            eprintln!(
5459                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=filtered_projection source={} declaration_guards={} proven_guards={} projections={}",
5460                declaration_source.rel_path().display(),
5461                declaration_guards.len(),
5462                self.compile_proven_guards(file).len(),
5463                projections.len(),
5464            );
5465        }
5466        let admission = IncludePathAdmission::Proven;
5467        projections.iter().any(|projection| {
5468            projection.activation_byte <= reference_byte
5469                && admission.admits(
5470                    &projection.required_guards,
5471                    &projection.partial_guards,
5472                    reference_guards,
5473                )
5474                && self.preprocessor_guards_stable_between(
5475                    file,
5476                    projection.activation_byte,
5477                    reference_byte,
5478                    &projection.required_guards,
5479                )
5480        })
5481    }
5482
5483    fn foreign_declaration_may_be_reachable_from_raw_guards(
5484        &self,
5485        file: &ProjectFile,
5486        prepared: &PreparedSyntaxTree,
5487        declaration_source: &ProjectFile,
5488        declaration_guards: &HashSet<PreprocessorGuard>,
5489        reference_guards: Option<&HashSet<PreprocessorGuard>>,
5490        reference_byte: usize,
5491    ) -> bool {
5492        let proven = self.compile_proven_guards(file);
5493        let augmented;
5494        let reference_guards = match reference_guards {
5495            Some(active) if !proven.is_empty() => {
5496                augmented = active.union(&proven).cloned().collect();
5497                Some(&augmented)
5498            }
5499            other => other,
5500        };
5501        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5502            return false;
5503        }
5504        if self
5505            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5506            .is_some_and(|activation| activation <= reference_byte)
5507        {
5508            return true;
5509        }
5510        let effective = OnceCell::new();
5511        let reachable = find_conditional_include_projection_for_source(
5512            self.cpp,
5513            self.token,
5514            file,
5515            prepared,
5516            declaration_source,
5517            self.include_path_admission(file),
5518            reference_guards,
5519            reference_byte,
5520            &|| {
5521                effective
5522                    .get_or_init(|| {
5523                        self.effective_reference_guards(file, Some(&HashSet::default()))
5524                    })
5525                    .as_ref()
5526            },
5527            &|| {
5528                #[cfg(any(test, feature = "test-support"))]
5529                self.conditional_include_target_state_count
5530                    .fetch_add(1, Ordering::Relaxed);
5531            },
5532        );
5533        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5534            eprintln!(
5535                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_projection source={} declaration_guards={} proven_guards={} raw_guards={} reachable={reachable}",
5536                declaration_source.rel_path().display(),
5537                declaration_guards.len(),
5538                proven.len(),
5539                reference_guards.map_or(0, HashSet::len),
5540            );
5541        }
5542        reachable
5543    }
5544
5545    fn foreign_declaration_reachable_from_compile_proven_guards(
5546        &self,
5547        file: &ProjectFile,
5548        prepared: &PreparedSyntaxTree,
5549        declaration_source: &ProjectFile,
5550        declaration_guards: &HashSet<PreprocessorGuard>,
5551        reference_byte: usize,
5552    ) -> bool {
5553        let proven = self.compile_proven_guards(file);
5554        if proven.is_empty()
5555            || !guard_requirements_hold_at_reference(declaration_guards, Some(proven.as_ref()))
5556        {
5557            return false;
5558        }
5559        let projections =
5560            self.conditional_include_projections_for_source(file, prepared, declaration_source);
5561        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5562            eprintln!(
5563                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=compile_proven_projection source={} declaration_guards={} proven_guards={} projections={}",
5564                declaration_source.rel_path().display(),
5565                declaration_guards.len(),
5566                proven.len(),
5567                projections.len(),
5568            );
5569        }
5570        projections.iter().any(|projection| {
5571            projection.activation_byte <= reference_byte
5572                    && guard_requirements_hold_at_reference(
5573                        &projection.required_guards,
5574                        Some(proven.as_ref()),
5575                    )
5576                    // Build facts hold at translation-unit entry. A source
5577                    // `#undef` or an earlier include may invalidate one before
5578                    // this conditional include is reached; mutations after the
5579                    // include cannot revoke declarations it already supplied.
5580                    && self.preprocessor_guards_stable_between(
5581                        file,
5582                        0,
5583                        projection.activation_byte,
5584                        &projection.required_guards,
5585                    )
5586        })
5587    }
5588
5589    /// Whether one declaration site of `site_source` introduces its name before
5590    /// `reference_byte` of `file` without consulting the reference's own guard
5591    /// environment: an unguarded site earlier in the file or reached through
5592    /// an unconditional include, or a foreign site the build's proven defines
5593    /// reach.
5594    fn declaration_site_visible_without_reference_environment(
5595        &self,
5596        file: &ProjectFile,
5597        prepared: &PreparedSyntaxTree,
5598        site_source: &ProjectFile,
5599        site_byte: usize,
5600        site_guards: &HashSet<PreprocessorGuard>,
5601        reference_byte: usize,
5602    ) -> bool {
5603        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
5604        if site_source == file {
5605            let visible = site_byte < reference_byte && site_guards.is_empty();
5606            if report_stats {
5607                eprintln!(
5608                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=true visible={visible}",
5609                    site_source.rel_path().display(),
5610                    site_guards.len(),
5611                );
5612            }
5613            return visible;
5614        }
5615        let direct = site_guards.is_empty()
5616            && self
5617                .include_activation_for_source(self.cpp, file, prepared, site_source)
5618                .is_some_and(|activation| activation <= reference_byte);
5619        let compile_proven = !direct
5620            && self.foreign_declaration_reachable_from_compile_proven_guards(
5621                file,
5622                prepared,
5623                site_source,
5624                site_guards,
5625                reference_byte,
5626            );
5627        if report_stats {
5628            eprintln!(
5629                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=false direct={direct} compile_proven={compile_proven}",
5630                site_source.rel_path().display(),
5631                site_guards.len(),
5632            );
5633        }
5634        direct || compile_proven
5635    }
5636
5637    /// Whether one declaration site of `site_source` is in effect at
5638    /// `reference_byte` of `file` under the reference's guard environment:
5639    /// `raw_reference_guards` as written, and `reference_guards_at_site` after
5640    /// the macro events before the reference.
5641    #[allow(clippy::too_many_arguments)]
5642    fn declaration_site_visible_at_reference<'g>(
5643        &self,
5644        file: &ProjectFile,
5645        prepared: &PreparedSyntaxTree,
5646        site_source: &ProjectFile,
5647        site: &DeclarationGuardSite,
5648        reference_byte: usize,
5649        raw_reference_guards: Option<&HashSet<PreprocessorGuard>>,
5650        reference_guards_at_site: impl Fn() -> Option<&'g HashSet<PreprocessorGuard>>,
5651    ) -> bool {
5652        if site_source == file {
5653            if site.byte >= reference_byte {
5654                return false;
5655            }
5656            // The reference's own conditional context must either prove the
5657            // declaration's requirements, or follow a completed family that
5658            // encloses the declaration (#3297); either way it must not
5659            // contradict them.
5660            let active = |guards: Option<&HashSet<PreprocessorGuard>>| {
5661                guard_requirements_hold_at_reference(&site.guards, guards)
5662                    || (site.completed_family_before_reference
5663                        && guards_compatible_at_reference(&site.guards, guards))
5664            };
5665            let effective_guards = |guards: Option<&HashSet<PreprocessorGuard>>| {
5666                self.effective_reference_guards(file, guards)
5667            };
5668            let raw_reference_guards = effective_guards(raw_reference_guards);
5669            if !active(raw_reference_guards.as_ref()) {
5670                return false;
5671            }
5672            let reference_guards_at_site = effective_guards(reference_guards_at_site());
5673            return active(reference_guards_at_site.as_ref())
5674                && self.preprocessor_guards_stable_between(
5675                    file,
5676                    site.byte,
5677                    reference_byte,
5678                    &site.guards,
5679                );
5680        }
5681        let raw_feasible = self.foreign_declaration_may_be_reachable_from_raw_guards(
5682            file,
5683            prepared,
5684            site_source,
5685            &site.guards,
5686            raw_reference_guards,
5687            reference_byte,
5688        );
5689        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5690            eprintln!(
5691                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_feasibility source={} declaration_guards={} feasible={raw_feasible}",
5692                site_source.rel_path().display(),
5693                site.guards.len(),
5694            );
5695        }
5696        raw_feasible
5697            && self.foreign_declaration_reachable_at_reference(
5698                file,
5699                prepared,
5700                site_source,
5701                &site.guards,
5702                reference_guards_at_site(),
5703                reference_byte,
5704            )
5705    }
5706
5707    /// Whether a scope-level binding written at `site_byte` of `site_source`
5708    /// under `site_guards` is in effect at `reference`, by the same site rule
5709    /// [`Self::external_type_candidate_visible_in_context`] applies to each
5710    /// declaration range.
5711    pub fn binding_site_visible_at_reference(
5712        &self,
5713        file: &ProjectFile,
5714        site_source: &ProjectFile,
5715        site_byte: usize,
5716        site_guards: &HashSet<PreprocessorGuard>,
5717        reference: Node<'_>,
5718    ) -> bool {
5719        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5720            return false;
5721        };
5722        if self.declaration_site_visible_without_reference_environment(
5723            file,
5724            prepared.as_ref(),
5725            site_source,
5726            site_byte,
5727            site_guards,
5728            reference.start_byte(),
5729        ) {
5730            return true;
5731        }
5732        let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
5733        let reference_guards = OnceCell::new();
5734        self.declaration_site_visible_at_reference(
5735            file,
5736            prepared.as_ref(),
5737            site_source,
5738            &DeclarationGuardSite {
5739                byte: site_byte,
5740                guards: site_guards.clone(),
5741                completed_family_before_reference: false,
5742            },
5743            reference.start_byte(),
5744            raw_reference_guards.as_ref(),
5745            || {
5746                reference_guards
5747                    .get_or_init(|| {
5748                        let environment = self.macro_environment(file, reference.start_byte());
5749                        raw_reference_guards
5750                            .clone()
5751                            .filter(|guards| environment.guard_requirements_may_hold(guards))
5752                    })
5753                    .as_ref()
5754            },
5755        )
5756    }
5757
5758    pub fn external_type_candidate_visible_in_context(
5759        &self,
5760        analyzer: &CppGraphSource<'_>,
5761        file: &ProjectFile,
5762        candidate: &CodeUnit,
5763        reference: Node<'_>,
5764    ) -> bool {
5765        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
5766        if report_stats {
5767            eprintln!(
5768                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=started fqn={} candidate_source={} reference_file={} reference_byte={}",
5769                candidate.fq_name(),
5770                candidate.source().rel_path().display(),
5771                file.rel_path().display(),
5772                reference.start_byte(),
5773            );
5774        }
5775        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5776            return false;
5777        };
5778        let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
5779        let reference_guards = OnceCell::new();
5780        let reference_guards_at_site = || {
5781            reference_guards.get_or_init(|| {
5782                let started = Instant::now();
5783                if report_stats {
5784                    eprintln!(
5785                        "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=started file={} reference_byte={} raw_guards={}",
5786                        file.rel_path().display(),
5787                        reference.start_byte(),
5788                        raw_reference_guards.as_ref().map_or(0, HashSet::len),
5789                    );
5790                }
5791                let macro_environment = self.macro_environment(file, reference.start_byte());
5792                let filtered = raw_reference_guards
5793                    .clone()
5794                    .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
5795                if report_stats {
5796                    eprintln!(
5797                        "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=completed retained={} elapsed_ms={}",
5798                        filtered.is_some(),
5799                        started.elapsed().as_millis(),
5800                    );
5801                }
5802                filtered
5803            })
5804        };
5805
5806        let peers = self
5807            .visible_identifier_candidates(file, candidate.identifier())
5808            .filter(|peer| same_logical_symbol(candidate, peer))
5809            .collect::<Vec<_>>();
5810        if report_stats {
5811            let peer_sources = peers
5812                .iter()
5813                .map(|peer| peer.source().rel_path().display().to_string())
5814                .collect::<Vec<_>>();
5815            eprintln!(
5816                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=peers fqn={} sources={peer_sources:?}",
5817                candidate.fq_name(),
5818            );
5819        }
5820        let directly_visible_without_reference_environment = peers.iter().any(|peer| {
5821            declaration_guard_requirements(analyzer, self.cpp, peer)
5822                .into_iter()
5823                .any(|(declaration_byte, declaration_guards)| {
5824                    self.declaration_site_visible_without_reference_environment(
5825                        file,
5826                        prepared.as_ref(),
5827                        peer.source(),
5828                        declaration_byte,
5829                        &declaration_guards,
5830                        reference.start_byte(),
5831                    )
5832                })
5833        });
5834        if directly_visible_without_reference_environment {
5835            if report_stats {
5836                eprintln!(
5837                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=direct_or_compile_proven fqn={}",
5838                    candidate.fq_name(),
5839                );
5840            }
5841            return true;
5842        }
5843        let directly_visible = peers.iter().any(|peer| {
5844            declaration_guard_sites(analyzer, self.cpp, peer, reference.start_byte())
5845                .into_iter()
5846                .any(|site| {
5847                    self.declaration_site_visible_at_reference(
5848                        file,
5849                        prepared.as_ref(),
5850                        peer.source(),
5851                        &site,
5852                        reference.start_byte(),
5853                        raw_reference_guards.as_ref(),
5854                        || reference_guards_at_site().as_ref(),
5855                    )
5856                })
5857        });
5858        if directly_visible {
5859            if report_stats {
5860                eprintln!(
5861                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=filtered_reference fqn={}",
5862                    candidate.fq_name(),
5863                );
5864            }
5865            return true;
5866        }
5867        let complementary = self
5868            .visible_identifier_candidates(file, candidate.identifier())
5869            .filter(|peer| {
5870                peer.kind() == candidate.kind()
5871                    && peer.fq_name() == candidate.fq_name()
5872                    && peer.source() == candidate.source()
5873            })
5874            .collect::<Vec<_>>();
5875        // A completed #if/#else family declares the shared source-level name
5876        // before this reference. A later macro mutation cannot revoke that
5877        // declaration. The family gate below rejects declarations split across
5878        // separate conditional blocks, where mutation can change coverage.
5879        let complementary_family =
5880            self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate);
5881        let raw_candidate_branch_compatible = complementary_family
5882            && raw_reference_guards.as_ref().is_some_and(|active| {
5883                declaration_guard_requirements(analyzer, self.cpp, candidate)
5884                    .iter()
5885                    .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5886            });
5887        if report_stats {
5888            eprintln!(
5889                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=complementary fqn={} candidates={} family={} raw_compatible={}",
5890                candidate.fq_name(),
5891                complementary.len(),
5892                complementary_family,
5893                raw_candidate_branch_compatible,
5894            );
5895        }
5896        let candidate_branch_compatible = raw_candidate_branch_compatible
5897            && reference_guards_at_site().as_ref().is_some_and(|active| {
5898                declaration_guard_requirements(analyzer, self.cpp, candidate)
5899                    .iter()
5900                    .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5901            });
5902        let complementary_visible = candidate_branch_compatible
5903            && if candidate.source() == file {
5904                declaration_guard_requirements(analyzer, self.cpp, candidate)
5905                    .iter()
5906                    .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
5907            } else {
5908                self.include_activation_for_source(
5909                    self.cpp,
5910                    file,
5911                    prepared.as_ref(),
5912                    candidate.source(),
5913                )
5914                .is_some_and(|activation| activation <= reference.start_byte())
5915            };
5916        if report_stats {
5917            eprintln!(
5918                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome={} fqn={}",
5919                if complementary_visible {
5920                    "complementary"
5921                } else {
5922                    "missing"
5923                },
5924                candidate.fq_name(),
5925            );
5926        }
5927        complementary_visible
5928    }
5929
5930    pub fn is_exhaustive_same_fqn_type_declaration_family(
5931        &self,
5932        analyzer: &CppGraphSource<'_>,
5933        file: &ProjectFile,
5934        candidate: &CodeUnit,
5935    ) -> bool {
5936        let candidates = self
5937            .visible_identifier_candidates(file, candidate.identifier())
5938            .filter(|peer| {
5939                peer.kind() == candidate.kind()
5940                    && peer.fq_name() == candidate.fq_name()
5941                    && peer.source() == candidate.source()
5942            })
5943            .collect::<Vec<_>>();
5944        self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
5945    }
5946
5947    /// Prove a nested type alias used as a dependent member-pointer owner when
5948    /// its owning class has mutually-exclusive declarations.  A common C++11
5949    /// compatibility shape provides the owning class in one preprocessor
5950    /// branch and aliases it to a standard-library type in the other branch;
5951    /// the nested fallback alias is therefore not itself active in every
5952    /// branch even though the qualified owner API is.
5953    ///
5954    /// This is deliberately narrower than ordinary type visibility.  The
5955    /// caller has already recovered a member-pointer owner path from the CST;
5956    /// this helper additionally requires the target's structured parent to
5957    /// match that path, physical source visibility, and exact preprocessor
5958    /// guard agreement with the parent declaration.  Only then may the
5959    /// parent's direct/complementary same-FQN visibility stand in for the
5960    /// nested terminal's active-branch check.
5961    pub fn dependent_member_pointer_alias_visible_in_context(
5962        &self,
5963        analyzer: &CppGraphSource<'_>,
5964        file: &ProjectFile,
5965        candidate: &CodeUnit,
5966        owner_components: &[String],
5967        reference: Node<'_>,
5968    ) -> bool {
5969        if !analyzer
5970            .type_alias_provider()
5971            .is_some_and(|provider| provider.is_type_alias(candidate))
5972        {
5973            return false;
5974        }
5975        let Some((terminal, owner_prefix)) = owner_components.split_last() else {
5976            return false;
5977        };
5978        if terminal != candidate.identifier()
5979            || canonical_cpp_scope_components(candidate) != owner_components
5980        {
5981            return false;
5982        }
5983        let Some(expected_parent_fq_name) =
5984            brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
5985        else {
5986            return false;
5987        };
5988        let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
5989            return false;
5990        };
5991        if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
5992            || parent_anchor.source() != candidate.source()
5993            || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
5994        {
5995            return false;
5996        }
5997
5998        // The ordinary path already handles unguarded aliases (and preserves
5999        // same-file declaration ordering).  This fallback is only for a
6000        // physically visible declaration whose guard is the owning branch's
6001        // guard, so reject a same-file declaration that appears after the
6002        // reference before considering guard compatibility.
6003        if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
6004            || candidate.source() == file
6005                && !analyzer
6006                    .ranges(candidate)
6007                    .iter()
6008                    .any(|range| range.start_byte < reference.start_byte())
6009        {
6010            return false;
6011        }
6012
6013        let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
6014        if candidate_guards.is_empty() {
6015            return false;
6016        }
6017        let same_guard_sets =
6018            |left: &[(usize, HashSet<PreprocessorGuard>)],
6019             right: &[(usize, HashSet<PreprocessorGuard>)]| {
6020                left.iter().all(|(_, left_guards)| {
6021                    right
6022                        .iter()
6023                        .any(|(_, right_guards)| left_guards == right_guards)
6024                })
6025            };
6026        let parent_candidates = self
6027            .visible_identifier_candidates(file, parent_anchor.identifier())
6028            .filter(|peer| {
6029                peer.kind() == parent_anchor.kind()
6030                    && peer.fq_name() == expected_parent_fq_name.as_str()
6031                    && peer.source() == parent_anchor.source()
6032                    && canonical_cpp_scope_components(peer) == owner_prefix
6033            })
6034            .filter_map(|peer| {
6035                let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
6036                (candidate_guards.len() == parent_guards.len()
6037                    && same_guard_sets(&candidate_guards, &parent_guards)
6038                    && same_guard_sets(&parent_guards, &candidate_guards))
6039                .then(|| (peer.clone(), parent_guards))
6040            })
6041            .collect::<Vec<_>>();
6042        let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
6043            return false;
6044        };
6045
6046        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6047            return false;
6048        };
6049        let raw = preprocessor_guard_environment(reference, prepared.source());
6050        let Some(reference_guards) = self.effective_reference_guards(file, raw.as_ref()) else {
6051            return false;
6052        };
6053        // An external header selects its declaration branch before the
6054        // reference file is parsed. Require compatible reference guards, but
6055        // do not test the header's guard expression for stability in the
6056        // reference file. Same-file aliases still require that stability.
6057        if !candidate_guards.iter().any(|(_, target_guards)| {
6058            guards_compatible_at_reference(target_guards, Some(&reference_guards))
6059                && (candidate.source() != file
6060                    || self.preprocessor_guards_stable_between(
6061                        file,
6062                        0,
6063                        reference.start_byte(),
6064                        target_guards,
6065                    ))
6066        }) {
6067            return false;
6068        }
6069
6070        self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
6071    }
6072
6073    /// Check a type candidate's preprocessor/import context without imposing
6074    /// ordinary declaration-before-reference ordering for same-file peers.
6075    ///
6076    /// C++ class scope makes member names visible throughout the complete
6077    /// class, including a trailing return type that appears before the member
6078    /// alias declaration in source order. Callers must first prove that the
6079    /// reference is inside the candidate's indexed class owner; this helper
6080    /// only relaxes the byte-order predicate while retaining guard and include
6081    /// activation checks.
6082    pub fn external_type_candidate_guard_compatible_in_context(
6083        &self,
6084        analyzer: &CppGraphSource<'_>,
6085        file: &ProjectFile,
6086        candidate: &CodeUnit,
6087        reference: Node<'_>,
6088    ) -> bool {
6089        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6090            return false;
6091        };
6092        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
6093
6094        self.visible_identifier_candidates(file, candidate.identifier())
6095            .filter(|peer| same_logical_symbol(candidate, peer))
6096            .any(|peer| {
6097                declaration_guard_requirements(analyzer, self.cpp, peer)
6098                    .into_iter()
6099                    .any(|(declaration_byte, declaration_guards)| {
6100                        if peer.source() == file {
6101                            let (start, end) = if declaration_byte <= reference.start_byte() {
6102                                (declaration_byte, reference.start_byte())
6103                            } else {
6104                                (reference.start_byte(), declaration_byte)
6105                            };
6106                            return guard_requirements_hold_at_reference(
6107                                &declaration_guards,
6108                                reference_guards.as_ref(),
6109                            ) && self.preprocessor_guards_stable_between(
6110                                file,
6111                                start,
6112                                end,
6113                                &declaration_guards,
6114                            );
6115                        }
6116                        self.foreign_declaration_reachable_at_reference(
6117                            file,
6118                            prepared.as_ref(),
6119                            peer.source(),
6120                            &declaration_guards,
6121                            reference_guards.as_ref(),
6122                            reference.start_byte(),
6123                        )
6124                    })
6125            })
6126    }
6127
6128    /// Whether a same-file callable declaration is nameable from `reference`
6129    /// after deliberately relaxing declaration-before-reference ordering.
6130    ///
6131    /// Ordinary lookup still requires an earlier declaration. Definition
6132    /// navigation for incomplete C translation units may recover a later
6133    /// definition, but only when it is at file scope and its preprocessor
6134    /// requirements hold at the call (#2404).
6135    pub fn same_file_callable_guard_compatible_ignoring_order(
6136        &self,
6137        analyzer: &CppGraphSource<'_>,
6138        file: &ProjectFile,
6139        candidate: &CodeUnit,
6140        reference: Node<'_>,
6141    ) -> bool {
6142        if candidate.source() != file || !candidate.is_callable() {
6143            return false;
6144        }
6145        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6146            return false;
6147        };
6148        let guards = OnceCell::new();
6149        let effective_guards = |guards: Option<&HashSet<PreprocessorGuard>>| {
6150            self.effective_reference_guards(file, guards)
6151        };
6152        let context = CallableReferenceContext {
6153            effective_guards: &effective_guards,
6154            file_guards: OnceCell::new(),
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        if !is_globally_qualified_cpp_name(node)
6314            && let Some(resolution) =
6315                function_local_type_resolution(&self.cpp_source(), file, node, &components, source)
6316        {
6317            return match resolution {
6318                LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
6319                LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
6320            };
6321        }
6322        self.resolve_type(file, &components.join("::"))
6323    }
6324
6325    pub fn resolve_template_arguments(
6326        &self,
6327        file: &ProjectFile,
6328        primary: CodeUnit,
6329        arguments: &[CppTemplateExpression],
6330    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6331        self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
6332    }
6333
6334    fn resolve_template_arguments_inner(
6335        &self,
6336        file: &ProjectFile,
6337        primary: CodeUnit,
6338        arguments: &[CppTemplateExpression],
6339        seen_aliases: &mut HashSet<CodeUnit>,
6340    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6341        if let Some(metadata) = self.cpp_template_metadata.get(&primary)
6342            && let Some(alias_target) = &metadata.alias_target
6343        {
6344            if !seen_aliases.insert(primary.clone()) {
6345                return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
6346            }
6347            let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
6348                .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6349            let target_name = alias_target.components.join("::");
6350            let target_primary = if alias_target.global {
6351                unique_logical_type_candidate(self.type_candidates(file, &target_name))
6352            } else {
6353                self.resolve_unique_type_for_declaration(file, &primary, &target_name)
6354            };
6355            let Some(target_primary) = target_primary else {
6356                // A dependent or external RHS cannot be canonicalized from the
6357                // indexed graph. Preserve the alias's direct identity instead
6358                // of inventing a target from its source spelling.
6359                return Ok(primary);
6360            };
6361            let Some(target_arguments) = &alias_target.arguments else {
6362                return Ok(target_primary);
6363            };
6364            let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
6365                .ok_or(CppTemplateResolutionError::Substitution)?;
6366            return self.resolve_template_arguments_inner(
6367                file,
6368                target_primary,
6369                &target_arguments,
6370                seen_aliases,
6371            );
6372        }
6373
6374        let primary_fq_name = self
6375            .cpp_template_metadata
6376            .get(&primary)
6377            .map(|metadata| metadata.primary_fq_name.clone())
6378            .unwrap_or_else(|| primary.fq_name());
6379        let has_specialization_metadata = self
6380            .cpp_template_families
6381            .get(&primary_fq_name)
6382            .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
6383        if !has_specialization_metadata {
6384            return Ok(primary);
6385        }
6386        self.select_template_specialization(file, &primary, arguments)
6387    }
6388
6389    fn select_template_specialization(
6390        &self,
6391        file: &ProjectFile,
6392        resolved: &CodeUnit,
6393        explicit_arguments: &[CppTemplateExpression],
6394    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6395        let primary_fq_name = self
6396            .cpp_template_metadata
6397            .get(resolved)
6398            .map(|metadata| metadata.primary_fq_name.clone())
6399            .unwrap_or_else(|| resolved.fq_name());
6400        let family = self
6401            .cpp_template_families
6402            .get(&primary_fq_name)
6403            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6404        let primary_candidates = family
6405            .iter()
6406            .filter_map(|unit| {
6407                let metadata = self.cpp_template_metadata.get(unit)?;
6408                (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
6409            })
6410            .collect::<Vec<_>>();
6411        let primary_unit = primary_candidates
6412            .iter()
6413            .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
6414            .or_else(|| {
6415                primary_candidates
6416                    .iter()
6417                    .map(|(unit, _)| *unit)
6418                    .min_by_key(|unit| {
6419                        (
6420                            unit.source().to_string(),
6421                            unit.signature().unwrap_or_default(),
6422                        )
6423                    })
6424            })
6425            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6426        let primary_parameters =
6427            cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
6428                .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6429        let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
6430            .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6431
6432        let mut applicable = Vec::new();
6433        for unit in family {
6434            let Some(metadata) = self.cpp_template_metadata.get(unit) else {
6435                continue;
6436            };
6437            if metadata.is_primary() || !self.is_visible(file, unit) {
6438                continue;
6439            }
6440            if !cpp_specialization_matches(metadata, &expanded) {
6441                continue;
6442            }
6443            applicable.push((unit, metadata));
6444        }
6445        if applicable.is_empty() {
6446            return Ok(primary_unit.clone());
6447        }
6448
6449        // A scalar constraint count cannot represent C++ partial ordering:
6450        // e.g. `<T*, U>` and `<T, int>` are incomparable for `<int*, int>`.
6451        // Select only a logical candidate whose structural pattern is strictly
6452        // more specialized than every other distinct applicable candidate.
6453        let winners = applicable
6454            .iter()
6455            .filter(|(candidate, candidate_metadata)| {
6456                applicable.iter().all(|(other, other_metadata)| {
6457                    same_visible_symbol(candidate, other)
6458                        || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
6459                })
6460            })
6461            .copied()
6462            .collect::<Vec<_>>();
6463        let Some((selected, _)) = winners.first() else {
6464            // Mutually incomparable applicable candidates: every one of them
6465            // is a live contender.
6466            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6467                candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
6468            });
6469        };
6470        if winners
6471            .iter()
6472            .any(|(unit, _)| !same_visible_symbol(unit, selected))
6473        {
6474            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6475                candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
6476            });
6477        }
6478        Ok((*selected).clone())
6479    }
6480
6481    pub fn resolve_type_components_lexically(
6482        &self,
6483        analyzer: &CppGraphSource<'_>,
6484        file: &ProjectFile,
6485        components: &[String],
6486        global: bool,
6487        lexical_scope: &[String],
6488    ) -> LexicalTypeResolution {
6489        self.resolve_type_components_lexically_inner(
6490            analyzer,
6491            file,
6492            components,
6493            global,
6494            lexical_scope,
6495            TypeCandidateResolution::Canonical,
6496        )
6497    }
6498
6499    pub fn resolve_type_components_lexically_for_forward(
6500        &self,
6501        analyzer: &CppGraphSource<'_>,
6502        file: &ProjectFile,
6503        components: &[String],
6504        global: bool,
6505        lexical_scope: &[String],
6506    ) -> LexicalTypeResolution {
6507        self.resolve_type_components_lexically_inner(
6508            analyzer,
6509            file,
6510            components,
6511            global,
6512            lexical_scope,
6513            TypeCandidateResolution::PreserveAlias,
6514        )
6515    }
6516
6517    pub fn resolve_type_components_lexically_for_target(
6518        &self,
6519        analyzer: &CppGraphSource<'_>,
6520        file: &ProjectFile,
6521        components: &[String],
6522        global: bool,
6523        lexical_scope: &[String],
6524        target: &CodeUnit,
6525    ) -> LexicalTypeResolution {
6526        #[cfg(any(test, feature = "test-support"))]
6527        self.target_preserving_type_resolution_count
6528            .fetch_add(1, Ordering::Relaxed);
6529        self.resolve_type_components_lexically_inner(
6530            analyzer,
6531            file,
6532            components,
6533            global,
6534            lexical_scope,
6535            TypeCandidateResolution::PreserveTarget(target),
6536        )
6537    }
6538
6539    pub fn coarse_unqualified_type_reference_may_resolve(
6540        &self,
6541        file: &ProjectFile,
6542        name: &str,
6543    ) -> bool {
6544        if name.is_empty() {
6545            return true;
6546        }
6547        self.visible_identifier_candidates(file, name)
6548            .any(|candidate| {
6549                candidate.kind() == CodeUnitType::Class
6550                    || declared_type_alias(&self.cpp_source(), candidate)
6551            })
6552            || self.visible_parser_alias_name_is_visible(file, name)
6553    }
6554
6555    #[allow(clippy::too_many_arguments)]
6556    pub fn structured_type_reference_may_resolve_to_target(
6557        &self,
6558        analyzer: &CppGraphSource<'_>,
6559        file: &ProjectFile,
6560        components: &[String],
6561        global: bool,
6562        lexical_scope: &[String],
6563        target: &CodeUnit,
6564    ) -> bool {
6565        if components.is_empty() {
6566            return true;
6567        }
6568        let Some(terminal) = components.last() else {
6569            return true;
6570        };
6571        let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
6572            .map(|qualified| qualified.join("::"))
6573            .collect::<Vec<_>>();
6574        let target_name = cpp_name_for(target);
6575        if qualified_tiers
6576            .iter()
6577            .any(|qualified| qualified == &target_name)
6578        {
6579            return true;
6580        }
6581
6582        let mut saw_shape_candidate = false;
6583        for candidate in self.visible_identifier_candidates(file, terminal) {
6584            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6585            {
6586                continue;
6587            }
6588            let candidate_name = cpp_name_for(candidate);
6589            let shape_matches = if global || components.len() > 1 {
6590                qualified_tiers
6591                    .iter()
6592                    .any(|qualified| qualified == &candidate_name)
6593            } else {
6594                true
6595            };
6596            if !shape_matches {
6597                continue;
6598            }
6599            saw_shape_candidate = true;
6600            if same_visible_symbol(candidate, target)
6601                || self.c_tag_declaration_family_matches_target(
6602                    analyzer,
6603                    file,
6604                    std::slice::from_ref(&candidate),
6605                    target,
6606                )
6607                || self.compatible_primary_template_redeclarations(candidate, target)
6608                || (declared_type_alias(analyzer, candidate)
6609                    && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
6610            {
6611                return true;
6612            }
6613        }
6614
6615        !saw_shape_candidate
6616    }
6617
6618    /// A C tag's forward declaration and complete definition are one logical
6619    /// type even when their indexed signatures and source files differ. Keep
6620    /// this identity narrow: the complete declaration must be a top-level C
6621    /// tag, every visible candidate must be its physical forward declaration,
6622    /// and the parsed tag kind must agree. Different FQNs, competing complete
6623    /// definitions, aliases, and struct/union mismatches remain ambiguous.
6624    fn cached_c_tag_kind(
6625        &self,
6626        analyzer: &CppGraphSource<'_>,
6627        candidate: &CodeUnit,
6628    ) -> Option<CppCTagKind> {
6629        if let Some(kind) = self
6630            .c_tag_kind_cache
6631            .lock()
6632            .expect("C tag kind cache poisoned")
6633            .get(candidate)
6634        {
6635            return *kind;
6636        }
6637        let kind = indexed_c_tag_kind(analyzer, candidate);
6638        self.c_tag_kind_cache
6639            .lock()
6640            .expect("C tag kind cache poisoned")
6641            .insert(candidate.clone(), kind);
6642        kind
6643    }
6644
6645    fn cached_unique_c_tag_complete_definition(
6646        &self,
6647        analyzer: &CppGraphSource<'_>,
6648        target: &CodeUnit,
6649        target_tag: CppCTagKind,
6650    ) -> Option<CodeUnit> {
6651        if let Some(definition) = self
6652            .c_tag_complete_definition_cache
6653            .lock()
6654            .expect("C tag complete-definition cache poisoned")
6655            .get(target)
6656        {
6657            return definition.clone();
6658        }
6659        let complete_definitions = analyzer
6660            .definitions(&target.fq_name())
6661            .filter(|candidate| {
6662                candidate.is_class()
6663                    && !declared_type_alias(analyzer, candidate)
6664                    && is_c_source_file(candidate.source())
6665                    && analyzer.parent_of(candidate).is_none()
6666                    && cpp_class_declaration_strength(analyzer, candidate)
6667                        == CppClassDeclarationStrength::Full
6668                    && self.cached_c_tag_kind(analyzer, candidate) == Some(target_tag)
6669            })
6670            .collect::<HashSet<_>>();
6671        let definition = (complete_definitions.len() == 1)
6672            .then(|| complete_definitions.into_iter().next())
6673            .flatten()
6674            .filter(|candidate| same_visible_symbol(candidate, target));
6675        self.c_tag_complete_definition_cache
6676            .lock()
6677            .expect("C tag complete-definition cache poisoned")
6678            .insert(target.clone(), definition.clone());
6679        definition
6680    }
6681
6682    pub fn c_tag_declaration_family_matches_target(
6683        &self,
6684        analyzer: &CppGraphSource<'_>,
6685        visible_from: &ProjectFile,
6686        candidates: &[&CodeUnit],
6687        target: &CodeUnit,
6688    ) -> bool {
6689        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
6690            let candidate_evidence = candidates
6691                .iter()
6692                .map(|candidate| {
6693                    (
6694                        candidate.fq_name(),
6695                        candidate.source().rel_path().to_path_buf(),
6696                        cpp_class_declaration_strength(analyzer, candidate),
6697                        indexed_c_tag_kind(analyzer, candidate),
6698                        self.is_physically_visible(visible_from, candidate),
6699                    )
6700                })
6701                .collect::<Vec<_>>();
6702            eprintln!(
6703                "BIFROST_CPP_C_TAG_FAMILY_STATS visible_from={} target=({}, {}, {:?}, {:?}) candidates={candidate_evidence:?}",
6704                visible_from.rel_path().display(),
6705                target.fq_name(),
6706                target.source().rel_path().display(),
6707                cpp_class_declaration_strength(analyzer, target),
6708                indexed_c_tag_kind(analyzer, target),
6709            );
6710        }
6711        if candidates.is_empty()
6712            || !target.is_class()
6713            || declared_type_alias(analyzer, target)
6714            || !is_c_source_file(target.source())
6715            || analyzer.parent_of(target).is_some()
6716            || cpp_class_declaration_strength(analyzer, target) != CppClassDeclarationStrength::Full
6717        {
6718            return false;
6719        }
6720        let Some(target_tag) = self.cached_c_tag_kind(analyzer, target) else {
6721            return false;
6722        };
6723        if self
6724            .cached_unique_c_tag_complete_definition(analyzer, target, target_tag)
6725            .is_none()
6726        {
6727            return false;
6728        }
6729        let mut saw_visible_forward = false;
6730        for candidate in candidates.iter().copied() {
6731            if candidate == target {
6732                continue;
6733            }
6734            if !candidate.is_class()
6735                || declared_type_alias(analyzer, candidate)
6736                || candidate.fq_name() != target.fq_name()
6737                || analyzer.parent_of(candidate).is_some()
6738                || cpp_class_declaration_strength(analyzer, candidate)
6739                    != CppClassDeclarationStrength::Forward
6740                || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6741                || !self.is_physically_visible(visible_from, candidate)
6742            {
6743                return false;
6744            }
6745            saw_visible_forward = true;
6746        }
6747        saw_visible_forward
6748    }
6749
6750    /// Collapse one visible complete C tag and its visible forward declarations
6751    /// before ordinary lexical resolution sees their different signatures as
6752    /// competing types. A second complete definition, a different FQN/tag
6753    /// kind, or an unknown declaration shape remains ambiguous.
6754    fn unique_c_tag_declaration_family(
6755        &self,
6756        analyzer: &CppGraphSource<'_>,
6757        visible_from: &ProjectFile,
6758        candidates: &[&CodeUnit],
6759    ) -> Option<CodeUnit> {
6760        let first = candidates.first()?;
6761        let target_fq_name = first.fq_name();
6762        let target_tag = self.cached_c_tag_kind(analyzer, first)?;
6763        let mut full = None;
6764        let mut saw_forward = false;
6765        for candidate in candidates.iter().copied() {
6766            if !candidate.is_class()
6767                || declared_type_alias(analyzer, candidate)
6768                || candidate.fq_name() != target_fq_name
6769                || analyzer.parent_of(candidate).is_some()
6770                || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6771            {
6772                return None;
6773            }
6774            match cpp_class_declaration_strength(analyzer, candidate) {
6775                CppClassDeclarationStrength::Full
6776                    if is_c_source_file(candidate.source())
6777                        && full.replace(candidate.clone()).is_none() => {}
6778                CppClassDeclarationStrength::Forward
6779                    if self.is_physically_visible(visible_from, candidate) =>
6780                {
6781                    saw_forward = true
6782                }
6783                _ => return None,
6784            }
6785        }
6786        if saw_forward { full } else { None }
6787    }
6788
6789    pub fn target_preserving_reference_namespace(
6790        &self,
6791        analyzer: &CppGraphSource<'_>,
6792        file: &ProjectFile,
6793        identifier: &str,
6794        target: &CodeUnit,
6795    ) -> Option<Vec<String>> {
6796        let mut namespace = None;
6797        for candidate in self.visible_identifier_candidates(file, identifier) {
6798            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6799            {
6800                continue;
6801            }
6802            if !(same_visible_symbol(candidate, target)
6803                || self.compatible_primary_template_redeclarations(candidate, target)
6804                || declared_type_alias(analyzer, candidate)
6805                    && self.structured_alias_primary_preserves_target(
6806                        analyzer, file, candidate, target,
6807                    ))
6808            {
6809                continue;
6810            }
6811            if namespace
6812                .as_ref()
6813                .is_some_and(|existing| existing != candidate.package_name())
6814            {
6815                return None;
6816            }
6817            namespace = Some(candidate.package_name().to_string());
6818        }
6819        let namespace = namespace?;
6820        Some(
6821            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6822                brokk_bifrost_core::analyzer::Language::Cpp,
6823                &namespace,
6824            ),
6825        )
6826    }
6827
6828    pub fn resolve_imported_type_candidate(
6829        &self,
6830        analyzer: &CppGraphSource<'_>,
6831        file: &ProjectFile,
6832        target: &CodeUnit,
6833        target_components: &[String],
6834        direct_target: Option<&CodeUnit>,
6835        preserve_alias: bool,
6836    ) -> LexicalTypeResolution {
6837        let candidates = [target];
6838        let resolution = if preserve_alias {
6839            TypeCandidateResolution::PreserveAlias
6840        } else {
6841            direct_target.map_or(
6842                TypeCandidateResolution::Canonical,
6843                TypeCandidateResolution::PreserveTarget,
6844            )
6845        };
6846        // One candidate goes in, so a failure here is never "choose one of
6847        // these": it is the alias chain leaving the index, which must answer
6848        // missing rather than ambiguous (#1828).
6849        match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6850            Ok(unit) => LexicalTypeResolution::Resolved {
6851                unit,
6852                components: target_components.to_vec(),
6853                candidates: vec![target.clone()],
6854            },
6855            Err(failure) => failure.lexical_resolution(),
6856        }
6857    }
6858
6859    fn resolve_type_components_lexically_inner(
6860        &self,
6861        analyzer: &CppGraphSource<'_>,
6862        file: &ProjectFile,
6863        components: &[String],
6864        global: bool,
6865        lexical_scope: &[String],
6866        resolution: TypeCandidateResolution<'_>,
6867    ) -> LexicalTypeResolution {
6868        if components.is_empty() {
6869            return LexicalTypeResolution::Missing;
6870        }
6871        // A C++ class injects its own name into the class scope.  The indexed
6872        // FqName for that declaration is the class path itself (for example,
6873        // `n::raw_hash_set`), not a synthetic child named
6874        // `n::raw_hash_set::raw_hash_set`.  Ordinary lexical tiers append the
6875        // requested identifier to every scope component, so they cannot
6876        // represent that injected binding when the enclosing class is the
6877        // closest scope.  Recover the binding from the structured class path
6878        // before allowing lookup to fall through to an outer same-spelled
6879        // declaration.
6880        let mut injected = self.resolve_injected_class_name(
6881            analyzer,
6882            file,
6883            components,
6884            global,
6885            lexical_scope,
6886            resolution,
6887        );
6888        for qualified in lexical_component_tiers(components, global, lexical_scope) {
6889            let prefix_len = qualified.len().saturating_sub(components.len());
6890            if injected
6891                .as_ref()
6892                .is_some_and(|(owner_len, _)| prefix_len <= *owner_len)
6893            {
6894                return injected
6895                    .take()
6896                    .expect("injected class resolution was just present")
6897                    .1;
6898            }
6899            let qualified_name = qualified.join("::");
6900            let candidates = self
6901                .type_candidates(file, &qualified_name)
6902                .into_iter()
6903                .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6904                .collect::<Vec<_>>();
6905            if candidates.is_empty() {
6906                if !global && components.len() == 1 {
6907                    match self.resolve_inherited_type_for_lexical_scope(
6908                        analyzer,
6909                        file,
6910                        &qualified[..prefix_len],
6911                        &components[0],
6912                        resolution,
6913                    ) {
6914                        LexicalTypeResolution::Missing => {}
6915                        inherited => return inherited,
6916                    }
6917                }
6918                continue;
6919            }
6920            let candidates =
6921                self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6922            let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6923                Ok(unit) => unit,
6924                Err(failure) => return failure.lexical_resolution(),
6925            };
6926            return LexicalTypeResolution::Resolved {
6927                unit,
6928                components: qualified,
6929                candidates: candidates.into_iter().cloned().collect(),
6930            };
6931        }
6932        LexicalTypeResolution::Missing
6933    }
6934
6935    fn resolve_injected_class_name(
6936        &self,
6937        analyzer: &CppGraphSource<'_>,
6938        file: &ProjectFile,
6939        components: &[String],
6940        global: bool,
6941        lexical_scope: &[String],
6942        resolution: TypeCandidateResolution<'_>,
6943    ) -> Option<(usize, LexicalTypeResolution)> {
6944        if global
6945            || components.len() != 1
6946            || file.rel_path().extension().is_some_and(|ext| ext == "c")
6947            || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
6948        {
6949            return None;
6950        }
6951        let name = components.first()?;
6952        let mut matches: Vec<&CodeUnit> = Vec::new();
6953        let mut owner_len = 0;
6954        for candidate in self.visible_identifier_candidates(file, name) {
6955            if !candidate.is_class()
6956                || declared_type_alias(analyzer, candidate)
6957                || candidate.identifier() != name
6958            {
6959                continue;
6960            }
6961            let candidate_scope = canonical_cpp_scope_components(candidate);
6962            if candidate_scope.len() > lexical_scope.len()
6963                || !lexical_scope.starts_with(&candidate_scope)
6964                || candidate_scope.last().is_none_or(|last| last != name)
6965            {
6966                continue;
6967            }
6968            if candidate_scope.len() > owner_len {
6969                owner_len = candidate_scope.len();
6970                matches.clear();
6971            }
6972            if candidate_scope.len() == owner_len
6973                && !matches
6974                    .iter()
6975                    .any(|existing| same_logical_symbol(existing, candidate))
6976            {
6977                matches.push(candidate);
6978            }
6979        }
6980        if matches.is_empty() {
6981            return None;
6982        }
6983        // The exact current class boundary is the most important injected-name
6984        // case. Ordinary lexical lookup appends the requested name and can
6985        // otherwise select a synthetic constructor-shaped child such as
6986        // `Portal::Impl::Impl` instead of the enclosing `Portal::Impl` class.
6987        // The global and single-component gates above keep qualified receiver
6988        // and static-qualifier contexts out of this recovery.
6989        let owner_components = lexical_scope[..owner_len].to_vec();
6990        let matches = self.candidates_for_type_resolution(analyzer, file, &matches, resolution);
6991        let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
6992            Ok(unit) => LexicalTypeResolution::Resolved {
6993                unit,
6994                components: owner_components,
6995                candidates: matches.into_iter().cloned().collect(),
6996            },
6997            Err(failure) => failure.lexical_resolution(),
6998        };
6999        Some((owner_len, resolution))
7000    }
7001
7002    fn resolve_inherited_type_for_lexical_scope(
7003        &self,
7004        analyzer: &CppGraphSource<'_>,
7005        file: &ProjectFile,
7006        lexical_scope: &[String],
7007        name: &str,
7008        resolution: TypeCandidateResolution<'_>,
7009    ) -> LexicalTypeResolution {
7010        let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
7011            return LexicalTypeResolution::Missing;
7012        };
7013        let lexical_owner_name = lexical_scope.join("::");
7014        if lexical_owner_name.is_empty() {
7015            return LexicalTypeResolution::Missing;
7016        }
7017        let owner_candidates = self
7018            .type_candidates(file, &lexical_owner_name)
7019            .into_iter()
7020            .filter(|candidate| {
7021                canonical_cpp_name_matches(candidate, &lexical_owner_name)
7022                    && !declared_type_alias(analyzer, candidate)
7023            })
7024            .collect::<Vec<_>>();
7025        if owner_candidates.is_empty() {
7026            return LexicalTypeResolution::Missing;
7027        }
7028        // A visible forward declaration and the physical class definition share
7029        // one FQN, but only the definition owns hierarchy facts. When lookup is
7030        // physically inside that definition, do not let an earlier header
7031        // forward declaration erase its base edges (#2240).
7032        let physical_owner_candidates = owner_candidates
7033            .iter()
7034            .copied()
7035            .filter(|candidate| candidate.source() == file)
7036            .collect::<Vec<_>>();
7037        let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
7038            owner_candidates
7039        } else {
7040            physical_owner_candidates
7041        };
7042        let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
7043            return LexicalTypeResolution::Ambiguous;
7044        };
7045
7046        let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
7047        let mut visited_owners = HashSet::default();
7048        while !frontier.is_empty() {
7049            let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
7050            let mut next_frontier = Vec::new();
7051            for owner in frontier {
7052                if !visited_owners.insert(owner.fq_name()) {
7053                    continue;
7054                }
7055                let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
7056                let candidates = self
7057                    .type_candidates(file, &qualified_name)
7058                    .into_iter()
7059                    .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
7060                    .collect::<Vec<_>>();
7061                if candidates.is_empty() {
7062                    for ancestor in hierarchy.get_direct_ancestors(&owner) {
7063                        if !next_frontier
7064                            .iter()
7065                            .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
7066                        {
7067                            next_frontier.push(ancestor);
7068                        }
7069                    }
7070                    continue;
7071                }
7072                let candidates =
7073                    self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
7074                let unit =
7075                    match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
7076                        Ok(unit) => unit,
7077                        Err(failure) => return failure.lexical_resolution(),
7078                    };
7079                level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
7080            }
7081            if let Some((unit, candidates)) = level_matches.first().cloned() {
7082                let Some(first_declaration) = candidates.first() else {
7083                    return LexicalTypeResolution::Ambiguous;
7084                };
7085                if !level_matches.iter().all(|(_, declarations)| {
7086                    declarations
7087                        .iter()
7088                        .all(|declaration| same_logical_symbol(first_declaration, declaration))
7089                }) {
7090                    return LexicalTypeResolution::Ambiguous;
7091                }
7092                let mut components = lexical_scope.to_vec();
7093                components.push(name.to_string());
7094                return LexicalTypeResolution::Resolved {
7095                    unit,
7096                    components,
7097                    candidates,
7098                };
7099            }
7100            frontier = next_frontier;
7101        }
7102        LexicalTypeResolution::Missing
7103    }
7104
7105    /// Resolve a base class through its injected class name at the nearest
7106    /// inheritance tier. Distinct same-named bases at that tier are ambiguous.
7107    ///
7108    /// A base whose canonical full definition cannot be pinned from `file` -
7109    /// a forward declaration the include closure completes with two different
7110    /// full definitions, or an alias chain that leaves the index - stops the
7111    /// walk only when that base is spelled `injected_name`. The mem-initializer
7112    /// names a base by that base's own injected class name, so a base spelled
7113    /// differently can never be the one it names, whichever definition it would
7114    /// have turned out to be; aborting the level on its account instead loses
7115    /// the sibling base that *is* named (#2543). A base that is spelled
7116    /// `injected_name` still fails closed, because choosing a deeper same-named
7117    /// ancestor over it would bind the initializer to the wrong constructor.
7118    /// The skipped base carries its own ancestors out of the walk with it: with
7119    /// no canonical unit, the repeated-base accounting below cannot tell one
7120    /// inherited path through it from two.
7121    pub fn inherited_injected_class_owner(
7122        &self,
7123        analyzer: &CppGraphSource<'_>,
7124        file: &ProjectFile,
7125        enclosing_owner: &CodeUnit,
7126        injected_name: &str,
7127    ) -> Option<CodeUnit> {
7128        let hierarchy = analyzer.type_hierarchy_provider()?;
7129        let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
7130        let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
7131        while !frontier.is_empty() {
7132            let mut level_matches = Vec::new();
7133            let mut next_frontier = Vec::new();
7134            for raw_owner in frontier {
7135                let Some(owner) = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
7136                else {
7137                    if raw_owner.identifier() == injected_name {
7138                        return None;
7139                    }
7140                    continue;
7141                };
7142                let propagated = propagated_counts.entry(owner.clone()).or_default();
7143                if *propagated == 2 {
7144                    continue;
7145                }
7146                *propagated += 1;
7147                if owner.identifier() == injected_name {
7148                    level_matches.push(owner.clone());
7149                }
7150                next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
7151            }
7152            match level_matches.as_slice() {
7153                [owner] => return Some(owner.clone()),
7154                [_, ..] => return None,
7155                [] => {}
7156            }
7157            frontier = next_frontier;
7158        }
7159        None
7160    }
7161
7162    /// The one type the candidates name under `resolution`, or why they do not
7163    /// name one. The two preserving modes only ever reject candidates that
7164    /// disagree with each other, which is ambiguity; canonicalization can also
7165    /// fail because the alias chain leaves the index (#1828).
7166    fn resolve_type_candidates(
7167        &self,
7168        analyzer: &CppGraphSource<'_>,
7169        file: &ProjectFile,
7170        candidates: &[&CodeUnit],
7171        resolution: TypeCandidateResolution<'_>,
7172    ) -> Result<CodeUnit, TypeCandidateFailure> {
7173        if !matches!(resolution, TypeCandidateResolution::PreserveTarget(_))
7174            && let Some(unit) = self.unique_c_tag_declaration_family(analyzer, file, candidates)
7175        {
7176            return Ok(unit);
7177        }
7178        match resolution {
7179            TypeCandidateResolution::Canonical => {
7180                self.canonical_type_candidate_resolution(analyzer, file, candidates)
7181            }
7182            TypeCandidateResolution::PreserveAlias => {
7183                // A generated index can retain identical alias spellings from
7184                // mutually exclusive headers. When the reference file
7185                // physically reaches exactly one of those source declarations,
7186                // include closure is the structured evidence that selects it;
7187                // treating the two source spellings as an overload set makes a
7188                // reachable alias appear ambiguous (#1844).
7189                let same_fqn_alias_family = candidates.len() > 1
7190                    && candidates.iter().all(|candidate| {
7191                        declared_type_alias(analyzer, candidate)
7192                            && same_logical_symbol(candidates[0], candidate)
7193                    })
7194                    && candidates
7195                        .iter()
7196                        .any(|candidate| candidate.source() != candidates[0].source());
7197                if same_fqn_alias_family {
7198                    let physically_visible = candidates
7199                        .iter()
7200                        .copied()
7201                        .filter(|candidate| self.is_physically_visible(file, candidate))
7202                        .collect::<Vec<_>>();
7203                    // The family is one logical declaration only when the
7204                    // reachable spellings agree. Two same-FQN aliases whose
7205                    // written targets differ (`using Choice = Canonical;` in
7206                    // one header, `using Choice = ::Canonical;` in another)
7207                    // are a genuine conflict, and choosing the first indexed
7208                    // one silently binds the reference to an arbitrary owner
7209                    // (#2398). Collapse only a single reachable declaration
7210                    // or reachable declarations with one structured target;
7211                    // everything else stays ambiguous below.
7212                    if let Some(alias) = select_alias_spelling_with_agreed_targets(
7213                        &physically_visible,
7214                        |candidate| self.structured_alias_target(analyzer, candidate),
7215                    ) {
7216                        return Ok(alias.clone());
7217                    }
7218                }
7219                unique_type_candidate_preserving_alias(analyzer, file, candidates)
7220                    .ok_or(TypeCandidateFailure::Ambiguous)
7221            }
7222            TypeCandidateResolution::PreserveTarget(target) => self
7223                .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
7224                .ok_or(TypeCandidateFailure::Ambiguous),
7225        }
7226    }
7227
7228    fn candidates_for_type_resolution<'b>(
7229        &self,
7230        analyzer: &CppGraphSource<'_>,
7231        file: &ProjectFile,
7232        candidates: &[&'b CodeUnit],
7233        resolution: TypeCandidateResolution<'_>,
7234    ) -> Vec<&'b CodeUnit> {
7235        if matches!(resolution, TypeCandidateResolution::PreserveAlias) && candidates.len() > 1 {
7236            let compile_proven = self.compile_proven_type_candidates(analyzer, file, candidates);
7237            if compile_proven.len() == 1 {
7238                return compile_proven;
7239            }
7240        }
7241        candidates.to_vec()
7242    }
7243
7244    /// Narrow a same-name forward lookup to the declaration selected by the
7245    /// translation unit's compile command. The lexical resolver intentionally
7246    /// does not receive a reference node, so this is the only compile-context
7247    /// evidence available at that stage. A single selected candidate is safe:
7248    /// the caller still checks include activation and the reference's own
7249    /// guards before reporting the result as visible.
7250    fn compile_proven_type_candidates<'b>(
7251        &self,
7252        analyzer: &CppGraphSource<'_>,
7253        file: &ProjectFile,
7254        candidates: &[&'b CodeUnit],
7255    ) -> Vec<&'b CodeUnit> {
7256        let proven = self.compile_proven_guards(file);
7257        if proven.is_empty() {
7258            return Vec::new();
7259        }
7260        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
7261            return Vec::new();
7262        };
7263        candidates
7264            .iter()
7265            .copied()
7266            .filter(|candidate| {
7267                let declaration_guards =
7268                    declaration_guard_requirements(analyzer, self.cpp, candidate);
7269                if declaration_guards.is_empty() {
7270                    return false;
7271                }
7272                if candidate.source() == file {
7273                    return declaration_guards.iter().any(|(_, required)| {
7274                        guard_requirements_hold_at_reference(required, Some(proven.as_ref()))
7275                    });
7276                }
7277                declaration_guards.iter().any(|(_, required)| {
7278                    self.foreign_declaration_reachable_from_compile_proven_guards(
7279                        file,
7280                        prepared.as_ref(),
7281                        candidate.source(),
7282                        required,
7283                        usize::MAX,
7284                    )
7285                })
7286            })
7287            .collect()
7288    }
7289
7290    pub fn resolve_callable_value_components_lexically(
7291        &self,
7292        analyzer: &CppGraphSource<'_>,
7293        file: &ProjectFile,
7294        owner_components: &[String],
7295        member_name: &str,
7296        global: bool,
7297        lexical_scope: &[String],
7298    ) -> LexicalCallableValueResolution {
7299        if owner_components.is_empty() || member_name.is_empty() {
7300            return LexicalCallableValueResolution::Missing;
7301        }
7302        for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
7303            let owner_name = qualified_owner.join("::");
7304            let type_candidates = self
7305                .type_candidates(file, &owner_name)
7306                .into_iter()
7307                .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
7308                .collect::<Vec<_>>();
7309            let resolved_type = if type_candidates.is_empty() {
7310                None
7311            } else {
7312                let Some(unit) =
7313                    self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
7314                else {
7315                    return LexicalCallableValueResolution::Ambiguous;
7316                };
7317                Some(unit)
7318            };
7319
7320            let mut qualified_callable = qualified_owner;
7321            qualified_callable.push(member_name.to_string());
7322            let callable_name = qualified_callable.join("::");
7323            let free_function = self
7324                .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
7325                .into_iter()
7326                .find(|candidate| {
7327                    canonical_cpp_name_matches(candidate, &callable_name)
7328                        && type_owner_of(analyzer, candidate).is_none()
7329                })
7330                .cloned();
7331
7332            match (resolved_type, free_function) {
7333                (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
7334                (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
7335                (None, Some(function)) => {
7336                    return LexicalCallableValueResolution::FreeFunction(function);
7337                }
7338                (None, None) => {}
7339            }
7340        }
7341        LexicalCallableValueResolution::Missing
7342    }
7343
7344    fn resolve_type_for_declaration(
7345        &self,
7346        visible_from: &ProjectFile,
7347        declaration: &CodeUnit,
7348        raw_name: &str,
7349    ) -> Option<CodeUnit> {
7350        let normalized = normalize_reference_name(raw_name)?;
7351        if !normalized.contains("::")
7352            && let Some(namespace) = cpp_namespace_for(declaration)
7353        {
7354            for prefix in namespace_prefixes(&namespace) {
7355                let qualified = format!("{prefix}::{normalized}");
7356                if let Some(unit) = self
7357                    .type_candidates(visible_from, &qualified)
7358                    .into_iter()
7359                    .next()
7360                {
7361                    return Some(unit.clone());
7362                }
7363            }
7364        }
7365        self.resolve_type(visible_from, raw_name)
7366    }
7367
7368    fn resolve_unique_canonical_type_for_declaration(
7369        &self,
7370        analyzer: &CppGraphSource<'_>,
7371        visible_from: &ProjectFile,
7372        declaration: &CodeUnit,
7373        raw_name: &str,
7374    ) -> Option<CodeUnit> {
7375        let current = self.resolve_defining_type_for_declaration(
7376            analyzer,
7377            visible_from,
7378            declaration,
7379            raw_name,
7380        )?;
7381        self.canonical_type_resolution(analyzer, visible_from, &current)
7382            .ok()
7383    }
7384
7385    pub fn canonical_type_unit(
7386        &self,
7387        analyzer: &CppGraphSource<'_>,
7388        visible_from: &ProjectFile,
7389        unit: &CodeUnit,
7390    ) -> Option<CodeUnit> {
7391        self.canonical_type_resolution(analyzer, visible_from, unit)
7392            .ok()
7393    }
7394
7395    /// Follow an alias only when it is visible at `reference`.
7396    ///
7397    /// The consumer need not spell the alias target. In particular, a
7398    /// conditional include can make `PublicPtr` visible at the reference while
7399    /// ordinary physical-include visibility is false. Prove the public alias
7400    /// with the reference's guard environment, then use the existing structured
7401    /// alias-chain resolver over the consumer's bounded declaration index.
7402    pub fn canonical_type_unit_in_context(
7403        &self,
7404        analyzer: &CppGraphSource<'_>,
7405        visible_from: &ProjectFile,
7406        reference: Node<'_>,
7407        unit: &CodeUnit,
7408    ) -> Option<CodeUnit> {
7409        if !self.external_type_candidate_visible_in_context(analyzer, visible_from, unit, reference)
7410        {
7411            return None;
7412        }
7413        self.canonical_type_resolution(analyzer, visible_from, unit)
7414            .ok()
7415    }
7416
7417    /// Canonicalize an alias chain at `reference` after its guards select
7418    /// conditional alias arms (#6902).
7419    ///
7420    /// `#if X using T = A; #else using T = B; #endif` indexes one alias
7421    /// declaration per arm. Canonicalizing the arms together reports `A` and
7422    /// `B` as an ambiguity everywhere, and following the first arm of a nested
7423    /// family attributes every use to `A`. Each arm, at every hop of the chain,
7424    /// therefore faces the reference's own guards plus the defines the build
7425    /// proves before its target is followed. An arm whose guards the evidence
7426    /// implies is proven when it is also visible at the reference; an arm
7427    /// whose guards are only compatible stays a candidate; a contradicted arm
7428    /// is dropped. Compatibility never establishes proof.
7429    ///
7430    /// Returns `None` when no hop of the chain is a family of alias arms with
7431    /// different targets; ordinary canonicalization owns that case.
7432    pub fn guarded_alias_selection_at_reference(
7433        &self,
7434        analyzer: &CppGraphSource<'_>,
7435        file: &ProjectFile,
7436        reference: Node<'_>,
7437        candidates: &[CodeUnit],
7438    ) -> Option<GuardedAliasSelection> {
7439        const MAX_GUARDED_ALIAS_STEPS: usize = 64;
7440        let mut conditional = false;
7441        let mut complete = true;
7442        let mut selected = Vec::new();
7443        let mut terminals: Vec<(CodeUnit, bool)> = Vec::new();
7444        let mut visited = HashSet::default();
7445        // Each pending entry is a unit, whether every hop before it was
7446        // proven, its hop depth, and whether it is a top-level candidate.
7447        let mut pending = candidates
7448            .iter()
7449            .map(|candidate| (candidate.clone(), true, 0, true))
7450            .collect::<Vec<_>>();
7451        let mut steps = 0;
7452        while let Some((unit, proven, depth, top_level)) = pending.pop() {
7453            if !visited.insert((unit.clone(), proven)) {
7454                continue;
7455            }
7456            steps += 1;
7457            if steps > MAX_GUARDED_ALIAS_STEPS || depth > MAX_COMPARABLE_ALIAS_HOPS {
7458                return None;
7459            }
7460            let arms = match self.conditional_alias_arms(analyzer, file, &unit) {
7461                Some(arms) => {
7462                    conditional = true;
7463                    let evidence = arms
7464                        .into_iter()
7465                        .map(|arm| {
7466                            let evidence = self.alias_arm_evidence(analyzer, file, &arm, reference);
7467                            (arm, evidence)
7468                        })
7469                        .collect::<Vec<_>>();
7470                    // A translation unit declares at most one arm of an alias
7471                    // with conflicting targets, so an implied arm excludes
7472                    // every merely compatible one.
7473                    let implied = evidence
7474                        .iter()
7475                        .any(|(_, evidence)| *evidence == AliasArmEvidence::Implied);
7476                    for (arm, _) in &evidence {
7477                        visited.insert((arm.clone(), proven));
7478                    }
7479                    evidence
7480                        .into_iter()
7481                        .filter_map(|(arm, evidence)| match evidence {
7482                            AliasArmEvidence::Implied => Some((arm, proven)),
7483                            AliasArmEvidence::Compatible if !implied => Some((arm, false)),
7484                            AliasArmEvidence::Compatible | AliasArmEvidence::Contradicted => None,
7485                        })
7486                        .collect::<Vec<_>>()
7487                }
7488                None => vec![(unit, proven)],
7489            };
7490            for (arm, proven) in arms {
7491                if top_level {
7492                    selected.push(arm.clone());
7493                }
7494                match self.structured_alias_target(analyzer, &arm) {
7495                    None | Some(StructuredAliasTarget::Builtin) if arm.is_class() => {
7496                        terminals.push((arm, proven));
7497                    }
7498                    None | Some(StructuredAliasTarget::Builtin) => complete = false,
7499                    Some(target) => {
7500                        match self.structured_alias_target_resolution(file, &arm, &target) {
7501                            Ok(next) => pending.push((next, proven, depth + 1, false)),
7502                            Err(_) => complete = false,
7503                        }
7504                    }
7505                }
7506            }
7507        }
7508        if !conditional {
7509            return None;
7510        }
7511        let mut targets: Vec<CodeUnit> = Vec::new();
7512        for (terminal, _) in &terminals {
7513            if !targets
7514                .iter()
7515                .any(|existing| same_visible_symbol(existing, terminal))
7516            {
7517                targets.push(terminal.clone());
7518            }
7519        }
7520        if complete
7521            && let [unit] = targets.as_slice()
7522            && terminals.iter().all(|(_, proven)| *proven)
7523        {
7524            return Some(GuardedAliasSelection::Proven {
7525                unit: unit.clone(),
7526                candidates: selected,
7527            });
7528        }
7529        Some(GuardedAliasSelection::Alternatives(targets))
7530    }
7531
7532    /// Every visible arm of `unit`'s alias family when the arms disagree about
7533    /// their target. Template aliases keep their own argument machinery.
7534    fn conditional_alias_arms(
7535        &self,
7536        analyzer: &CppGraphSource<'_>,
7537        file: &ProjectFile,
7538        unit: &CodeUnit,
7539    ) -> Option<Vec<CodeUnit>> {
7540        if !declared_type_alias(analyzer, unit) || self.cpp_template_metadata.contains_key(unit) {
7541            return None;
7542        }
7543        let mut arms = Vec::new();
7544        for candidate in self
7545            .visible_identifier_candidates(file, unit.identifier())
7546            .filter(|candidate| {
7547                candidate.kind() == unit.kind()
7548                    && candidate.fq_name() == unit.fq_name()
7549                    && declared_type_alias(analyzer, candidate)
7550            })
7551        {
7552            if self.cpp_template_metadata.contains_key(candidate) {
7553                return None;
7554            }
7555            if !arms.contains(candidate) {
7556                arms.push(candidate.clone());
7557            }
7558        }
7559        let refs = arms.iter().collect::<Vec<_>>();
7560        (arms.len() > 1
7561            && select_alias_spelling_with_agreed_targets(&refs, |candidate| {
7562                self.structured_alias_target(analyzer, candidate)
7563            })
7564            .is_none())
7565        .then_some(arms)
7566    }
7567
7568    /// Item 8's proof boundary for one alias arm: implication by the
7569    /// reference's effective guards proves the arm,
7570    /// compatibility only keeps it a candidate (#6902).
7571    fn alias_arm_evidence(
7572        &self,
7573        analyzer: &CppGraphSource<'_>,
7574        file: &ProjectFile,
7575        arm: &CodeUnit,
7576        reference: Node<'_>,
7577    ) -> AliasArmEvidence {
7578        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
7579            return AliasArmEvidence::Compatible;
7580        };
7581        let effective = self.effective_reference_guards(
7582            file,
7583            preprocessor_guard_environment(reference, prepared.source()).as_ref(),
7584        );
7585        let requirements = declaration_guard_requirements(analyzer, self.cpp, arm);
7586        if requirements
7587            .iter()
7588            .any(|(_, required)| guard_requirements_hold_at_reference(required, effective.as_ref()))
7589            && self.external_type_candidate_visible_in_context(analyzer, file, arm, reference)
7590        {
7591            AliasArmEvidence::Implied
7592        } else if requirements.is_empty()
7593            || requirements
7594                .iter()
7595                .any(|(_, required)| guards_compatible_at_reference(required, effective.as_ref()))
7596        {
7597            AliasArmEvidence::Compatible
7598        } else {
7599            AliasArmEvidence::Contradicted
7600        }
7601    }
7602
7603    /// Follow `unit`'s alias chain to the class it names, or report why the
7604    /// chain does not end at one indexed class.
7605    ///
7606    /// A chain that leaves the index - an alias to a template parameter, to a
7607    /// standard-library type, or to any other declaration the workspace does
7608    /// not hold - is `Unresolvable`, not `Ambiguous` (#1828). So is a cycle:
7609    /// there is still nothing to choose between.
7610    fn canonical_type_resolution(
7611        &self,
7612        analyzer: &CppGraphSource<'_>,
7613        visible_from: &ProjectFile,
7614        unit: &CodeUnit,
7615    ) -> Result<CodeUnit, TypeCandidateFailure> {
7616        let mut current = unit.clone();
7617        let mut seen_aliases = HashSet::default();
7618        loop {
7619            let Some(target) = self.structured_alias_target(analyzer, &current) else {
7620                return current
7621                    .is_class()
7622                    .then_some(current)
7623                    .ok_or(TypeCandidateFailure::Unresolvable);
7624            };
7625            if matches!(target, StructuredAliasTarget::Builtin) {
7626                return current
7627                    .is_class()
7628                    .then_some(current)
7629                    .ok_or(TypeCandidateFailure::Unresolvable);
7630            }
7631            if !seen_aliases.insert(current.clone()) {
7632                return Err(TypeCandidateFailure::Unresolvable);
7633            }
7634            current = self.structured_alias_target_resolution(visible_from, &current, &target)?;
7635        }
7636    }
7637
7638    pub fn canonical_visible_full_type_unit(
7639        &self,
7640        analyzer: &CppGraphSource<'_>,
7641        visible_from: &ProjectFile,
7642        unit: &CodeUnit,
7643    ) -> Option<CodeUnit> {
7644        let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
7645        if cpp_class_declaration_strength(analyzer, &canonical)
7646            != CppClassDeclarationStrength::Forward
7647        {
7648            return Some(canonical);
7649        }
7650        let mut full = Vec::new();
7651        for candidate in self
7652            .visible_identifier_candidates(visible_from, canonical.identifier())
7653            .filter(|candidate| {
7654                candidate.is_class()
7655                    && candidate.fq_name() == canonical.fq_name()
7656                    && cpp_class_declaration_strength(analyzer, candidate)
7657                        == CppClassDeclarationStrength::Full
7658            })
7659        {
7660            if !full.iter().any(|existing| same_symbol(existing, candidate)) {
7661                full.push(candidate.clone());
7662            }
7663        }
7664        match full.len() {
7665            0 => Some(canonical),
7666            1 => full.pop(),
7667            _ => None,
7668        }
7669    }
7670
7671    fn resolve_structured_alias_target(
7672        &self,
7673        visible_from: &ProjectFile,
7674        declaration: &CodeUnit,
7675        target: &StructuredAliasTarget,
7676    ) -> Option<CodeUnit> {
7677        self.structured_alias_target_resolution(visible_from, declaration, target)
7678            .ok()
7679    }
7680
7681    fn structured_alias_target_resolution(
7682        &self,
7683        visible_from: &ProjectFile,
7684        declaration: &CodeUnit,
7685        target: &StructuredAliasTarget,
7686    ) -> Result<CodeUnit, TypeCandidateFailure> {
7687        let primary =
7688            self.structured_alias_primary_resolution(visible_from, declaration, target)?;
7689        let StructuredAliasTarget::Named { arguments, .. } = target else {
7690            return Err(TypeCandidateFailure::Unresolvable);
7691        };
7692        match arguments {
7693            Some(arguments) => self
7694                .resolve_template_arguments(visible_from, primary, arguments)
7695                .map_err(|error| match error {
7696                    CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
7697                        TypeCandidateFailure::Ambiguous
7698                    }
7699                    _ => TypeCandidateFailure::Unresolvable,
7700                }),
7701            None => Ok(primary),
7702        }
7703    }
7704
7705    fn resolve_structured_alias_primary(
7706        &self,
7707        visible_from: &ProjectFile,
7708        declaration: &CodeUnit,
7709        target: &StructuredAliasTarget,
7710    ) -> Option<CodeUnit> {
7711        self.structured_alias_primary_resolution(visible_from, declaration, target)
7712            .ok()
7713    }
7714
7715    fn structured_alias_primary_resolution(
7716        &self,
7717        visible_from: &ProjectFile,
7718        declaration: &CodeUnit,
7719        target: &StructuredAliasTarget,
7720    ) -> Result<CodeUnit, TypeCandidateFailure> {
7721        let StructuredAliasTarget::Named {
7722            components, global, ..
7723        } = target
7724        else {
7725            return Err(TypeCandidateFailure::Unresolvable);
7726        };
7727        let qualified = components.join("::");
7728        let candidates = if *global {
7729            // `::A::B` anchors at the root scope, so a candidate whose
7730            // canonical path merely ends with the spelled components does not
7731            // qualify. Without this filter a global `::Canonical` target also
7732            // collects `alpha::Canonical`, the lookup reports a false
7733            // ambiguity, and the alias arm silently drops out of its
7734            // conflicting family instead of proving the conflict (#2398).
7735            let mut candidates = self.type_candidates(visible_from, &qualified);
7736            candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
7737            candidates
7738        } else {
7739            self.type_candidates_for_declaration(visible_from, declaration, &qualified)
7740        };
7741        logical_type_candidate(candidates)
7742    }
7743
7744    pub fn structured_alias_primary_preserves_target(
7745        &self,
7746        analyzer: &CppGraphSource<'_>,
7747        visible_from: &ProjectFile,
7748        candidate: &CodeUnit,
7749        target: &CodeUnit,
7750    ) -> bool {
7751        let mut current = candidate.clone();
7752        let mut seen = HashSet::default();
7753        let mut matched_target = false;
7754        loop {
7755            if same_visible_symbol(&current, target)
7756                || self.compatible_primary_template_redeclarations(&current, target)
7757            {
7758                matched_target = true;
7759            }
7760            if !seen.insert(current.clone()) {
7761                return false;
7762            }
7763            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
7764                return matched_target;
7765            };
7766            if matches!(alias_target, StructuredAliasTarget::Builtin) {
7767                return matched_target;
7768            };
7769            let Some(primary) =
7770                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
7771            else {
7772                // A dependent member target such as `Detector<T>::type`
7773                // cannot be reduced to an indexed primary, but a preceding
7774                // structured alias hop may already have proven the requested
7775                // alias identity. Cycles still resolve a primary and are
7776                // rejected by `seen` above.
7777                return matched_target;
7778            };
7779            current = primary;
7780        }
7781    }
7782
7783    fn lexical_type_candidates_match_target(
7784        &self,
7785        analyzer: &CppGraphSource<'_>,
7786        unit: &CodeUnit,
7787        candidates: &[CodeUnit],
7788        target: &CodeUnit,
7789    ) -> bool {
7790        same_visible_symbol(unit, target)
7791            || self.same_template_member_identity(analyzer, unit, target)
7792            || candidates.iter().any(|candidate| {
7793                same_visible_symbol(candidate, target)
7794                    || self.same_template_member_identity(analyzer, candidate, target)
7795            })
7796    }
7797
7798    pub fn structured_class_alias_resolves_to_target(
7799        &self,
7800        analyzer: &CppGraphSource<'_>,
7801        visible_from: &ProjectFile,
7802        alias: &CodeUnit,
7803        target: &CodeUnit,
7804    ) -> bool {
7805        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7806            return false;
7807        };
7808        let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
7809            return false;
7810        };
7811        let StructuredAliasTarget::Named {
7812            components, global, ..
7813        } = &alias_target
7814        else {
7815            return false;
7816        };
7817        let lexical_scope = canonical_cpp_scope_components(&owner);
7818        match self.resolve_type_components_lexically_for_target(
7819            analyzer,
7820            visible_from,
7821            components,
7822            *global,
7823            &lexical_scope,
7824            target,
7825        ) {
7826            LexicalTypeResolution::Resolved {
7827                unit, candidates, ..
7828            } => self.lexical_type_candidates_match_target(analyzer, &unit, &candidates, target),
7829            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7830                self.structured_alias_primary_preserves_target(
7831                    analyzer,
7832                    visible_from,
7833                    alias,
7834                    target,
7835                ) || self.flattened_macro_namespace_alias_target_matches(
7836                    analyzer,
7837                    visible_from,
7838                    alias,
7839                    &alias_target,
7840                    target,
7841                )
7842            }
7843        }
7844    }
7845
7846    /// Return true when a class-owned alias names the requested type as one
7847    /// structured qualifier in its target path.
7848    ///
7849    /// A dependent target such as `Primary<T>::Type` cannot resolve to one
7850    /// indexed class. Forward lookup can still retain `Primary` as its bounded
7851    /// canonical identity. Inverse lookup needs the same evidence when later
7852    /// references use only the alias spelling.
7853    pub fn structured_class_alias_path_preserves_target(
7854        &self,
7855        analyzer: &CppGraphSource<'_>,
7856        visible_from: &ProjectFile,
7857        alias: &CodeUnit,
7858        target: &CodeUnit,
7859    ) -> bool {
7860        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7861            return false;
7862        };
7863        let Some(StructuredAliasTarget::Named {
7864            components, global, ..
7865        }) = self.structured_alias_target(analyzer, alias)
7866        else {
7867            return false;
7868        };
7869        let lexical_scope = canonical_cpp_scope_components(&owner);
7870        (1..components.len()).rev().any(|component_count| {
7871            matches!(
7872                self.resolve_type_components_lexically_for_target(
7873                    analyzer,
7874                    visible_from,
7875                    &components[..component_count],
7876                    global,
7877                    &lexical_scope,
7878                    target,
7879                ),
7880                LexicalTypeResolution::Resolved {
7881                    ref unit,
7882                    ref candidates,
7883                    ..
7884                } if self.lexical_type_candidates_match_target(
7885                    analyzer,
7886                    unit,
7887                    candidates,
7888                    target,
7889                )
7890            )
7891        })
7892    }
7893
7894    fn flattened_macro_namespace_alias_target_matches(
7895        &self,
7896        analyzer: &CppGraphSource<'_>,
7897        visible_from: &ProjectFile,
7898        alias: &CodeUnit,
7899        alias_target: &StructuredAliasTarget,
7900        target: &CodeUnit,
7901    ) -> bool {
7902        let StructuredAliasTarget::Named {
7903            components,
7904            global: false,
7905            arguments: None,
7906        } = alias_target
7907        else {
7908            return false;
7909        };
7910        let Some((target_name, namespace_components)) = components.split_last() else {
7911            return false;
7912        };
7913        if namespace_components.is_empty()
7914            || target_name != target.identifier()
7915            || alias.source() != target.source()
7916            || alias.source() != visible_from
7917            || !target.is_class()
7918            || declared_type_alias(analyzer, target)
7919        {
7920            return false;
7921        }
7922        if self
7923            .resolve_structured_alias_target(visible_from, alias, alias_target)
7924            .is_some()
7925        {
7926            return false;
7927        }
7928
7929        let Some(alias_start) = analyzer
7930            .declaration_source_occurrences(alias)
7931            .into_iter()
7932            .flatten()
7933            .map(|occurrence| occurrence.range.start_byte)
7934            .min()
7935        else {
7936            return false;
7937        };
7938        let has_matching_declaration = analyzer
7939            .declaration_source_occurrences(target)
7940            .into_iter()
7941            .flatten()
7942            .any(|occurrence| {
7943                occurrence.range.end_byte <= alias_start
7944                    && occurrence.fact.flattened_macro_namespace.as_deref()
7945                        == Some(namespace_components)
7946            });
7947        if !has_matching_declaration {
7948            return false;
7949        }
7950
7951        let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
7952        let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
7953        guard_requirement_sets_match(&alias_guards, &target_guards)
7954    }
7955
7956    pub fn template_alias_arguments_preserve_target(
7957        &self,
7958        analyzer: &CppGraphSource<'_>,
7959        visible_from: &ProjectFile,
7960        alias: &CodeUnit,
7961        arguments: &[CppTemplateExpression],
7962        target: &CodeUnit,
7963    ) -> bool {
7964        let Some(metadata) = self.cpp_template_metadata.get(alias) else {
7965            return false;
7966        };
7967        if metadata.alias_target.is_none()
7968            || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
7969        {
7970            return false;
7971        }
7972        self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
7973    }
7974
7975    pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
7976        self.cpp_template_metadata
7977            .get(unit)
7978            .is_some_and(CppTemplateMetadata::is_primary)
7979    }
7980
7981    pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
7982        self.cpp_template_metadata
7983            .get(unit)
7984            .is_some_and(CppTemplateMetadata::is_specialization)
7985    }
7986
7987    pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
7988        same_visible_symbol(left, right)
7989            || self.compatible_primary_template_redeclarations(left, right)
7990    }
7991
7992    pub fn same_template_member_identity(
7993        &self,
7994        analyzer: &CppGraphSource<'_>,
7995        left: &CodeUnit,
7996        right: &CodeUnit,
7997    ) -> bool {
7998        if same_visible_symbol(left, right) {
7999            return true;
8000        }
8001        if left.kind() != right.kind()
8002            || left.identifier() != right.identifier()
8003            || left.signature() != right.signature()
8004        {
8005            return false;
8006        }
8007        let (Some(left_owner), Some(right_owner)) =
8008            (analyzer.parent_of(left), analyzer.parent_of(right))
8009        else {
8010            return false;
8011        };
8012        left_owner.is_class()
8013            && right_owner.is_class()
8014            && self.same_template_owner_identity(&left_owner, &right_owner)
8015    }
8016
8017    fn unique_canonical_type_candidate(
8018        &self,
8019        analyzer: &CppGraphSource<'_>,
8020        visible_from: &ProjectFile,
8021        candidates: &[&CodeUnit],
8022    ) -> Option<CodeUnit> {
8023        self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
8024            .ok()
8025    }
8026
8027    fn canonical_type_candidate_resolution(
8028        &self,
8029        analyzer: &CppGraphSource<'_>,
8030        visible_from: &ProjectFile,
8031        candidates: &[&CodeUnit],
8032    ) -> Result<CodeUnit, TypeCandidateFailure> {
8033        let mut canonical = Vec::new();
8034        for candidate in candidates {
8035            let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
8036            if canonical
8037                .iter()
8038                .any(|existing| same_visible_symbol(existing, &resolved))
8039            {
8040                continue;
8041            }
8042            if let Some(existing) = canonical.iter_mut().find(|existing| {
8043                self.compatible_primary_template_redeclarations(existing, &resolved)
8044            }) {
8045                // A forward declaration and its full primary-template
8046                // definition are one C++ type even when they live in
8047                // different headers and alpha-rename their parameters. The
8048                // target-preserving path already reconciles this family; do
8049                // the same for ordinary canonical lookup so an out-of-line
8050                // member's lexical owner is not made ambiguous by its own
8051                // forward declaration. Retain the strongest physical
8052                // declaration for later owner/range queries.
8053                if matches!(
8054                    (
8055                        cpp_class_declaration_strength(analyzer, existing),
8056                        cpp_class_declaration_strength(analyzer, &resolved),
8057                    ),
8058                    (
8059                        CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
8060                        CppClassDeclarationStrength::Full,
8061                    ) | (
8062                        CppClassDeclarationStrength::Unknown,
8063                        CppClassDeclarationStrength::Forward,
8064                    )
8065                ) {
8066                    *existing = resolved;
8067                }
8068                continue;
8069            }
8070            canonical.push(resolved);
8071            if canonical.len() > 1 {
8072                return Err(TypeCandidateFailure::Ambiguous);
8073            }
8074        }
8075        canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
8076    }
8077
8078    pub fn unique_type_candidate_preserving_target(
8079        &self,
8080        analyzer: &CppGraphSource<'_>,
8081        visible_from: &ProjectFile,
8082        candidates: &[&CodeUnit],
8083        target: &CodeUnit,
8084    ) -> Option<CodeUnit> {
8085        // C++ headers often expose one logical type through mutually exclusive
8086        // physical declarations, for example a class in the fallback branch
8087        // and a `using` alias to the standard-library type in the configured
8088        // branch. The index intentionally retains both declarations so forward
8089        // lookup can report each target. Preserve the requested target when
8090        // that is the only ambiguity: every candidate has the same type kind,
8091        // exact canonical FQN, and source file, and the requested declaration
8092        // itself is one of the physical candidates. Do not merge same-named
8093        // declarations from different files or namespaces; those remain
8094        // ambiguous and fail closed below.
8095        if self.c_tag_declaration_family_matches_target(analyzer, visible_from, candidates, target)
8096            || self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
8097        {
8098            return Some(target.clone());
8099        }
8100        let mut resolved_candidates = Vec::new();
8101        for candidate in candidates {
8102            // An ifdef branch that aliases an unindexed system type (for
8103            // example `typedef pthread_mutex_t k5_os_mutex`) cannot be
8104            // canonicalized. That branch does not name `target`. Dropping it
8105            // keeps the branch that does. Failing the whole family here would
8106            // deny every usage of the reachable spelling (#2368).
8107            let Some(resolved) =
8108                self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
8109            else {
8110                continue;
8111            };
8112            if resolved_candidates
8113                .iter()
8114                .any(|existing| same_visible_symbol(existing, &resolved))
8115            {
8116                continue;
8117            }
8118            resolved_candidates.push(resolved);
8119        }
8120        match resolved_candidates.as_slice() {
8121            [] => None,
8122            [single] => Some(single.clone()),
8123            // The branches disagree about what the name aliases. When they are
8124            // spellings of one entity (#1845) that disagreement is a build
8125            // configuration, not a choice between types, so it must not deny
8126            // the requested target its reference.
8127            _ => self
8128                .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
8129                .map(|_| target.clone()),
8130        }
8131    }
8132
8133    /// The declaration a same-file same-FQN family stands for when a reference
8134    /// names `target`, or `None` when the candidates are not one family or the
8135    /// family does not name `target`.
8136    ///
8137    /// A translation unit cannot hold two different types under one qualified
8138    /// name, so several same-kind declarations of one FQN in one file are
8139    /// alternate spellings of one entity - the configuration branches of an
8140    /// `#if` family, for example log4cxx's `logchar`, which aliases `char` in
8141    /// the UTF-8 branch and `UniChar` in the unichar branch. Their alias
8142    /// targets differ; canonicalizing each branch on its own and then demanding
8143    /// agreement reports an ambiguity that denies every declaration in the
8144    /// family its usages (#1845). The family names `target` when it declares
8145    /// it, or when one branch's alias chain reaches it.
8146    ///
8147    /// Declarations in different files or namespaces are distinct entities and
8148    /// are deliberately excluded: their disagreement is a real ambiguity.
8149    pub fn same_fqn_type_spelling_for_target<'b>(
8150        &self,
8151        analyzer: &CppGraphSource<'_>,
8152        visible_from: &ProjectFile,
8153        candidates: &[&'b CodeUnit],
8154        target: &CodeUnit,
8155    ) -> Option<&'b CodeUnit> {
8156        let [first, rest @ ..] = candidates else {
8157            return None;
8158        };
8159        if rest.is_empty()
8160            || !rest.iter().all(|candidate| {
8161                candidate.kind() == first.kind()
8162                    && candidate.fq_name() == first.fq_name()
8163                    && candidate.source() == first.source()
8164            })
8165        {
8166            return None;
8167        }
8168        candidates
8169            .iter()
8170            .copied()
8171            .find(|candidate| same_symbol(candidate, target))
8172            .or_else(|| {
8173                candidates.iter().copied().find(|candidate| {
8174                    self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
8175                        .is_some_and(|resolved| same_visible_symbol(&resolved, target))
8176                })
8177            })
8178    }
8179
8180    pub fn alternate_same_fqn_type_declarations(
8181        &self,
8182        analyzer: &CppGraphSource<'_>,
8183        candidates: &[&CodeUnit],
8184        target: &CodeUnit,
8185    ) -> bool {
8186        let Some(first) = candidates.first() else {
8187            return false;
8188        };
8189        let same_api = first.kind() == target.kind()
8190            && first.fq_name() == target.fq_name()
8191            && first.source() == target.source()
8192            && candidates.iter().all(|candidate| {
8193                candidate.kind() == target.kind()
8194                    && candidate.fq_name() == target.fq_name()
8195                    && candidate.source() == target.source()
8196            })
8197            && candidates
8198                .iter()
8199                .any(|candidate| same_symbol(candidate, target))
8200            && candidates
8201                .iter()
8202                .any(|candidate| !same_logical_symbol(candidate, target));
8203        if !same_api {
8204            return false;
8205        }
8206
8207        let requirements = candidates
8208            .iter()
8209            .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
8210            .collect::<Vec<_>>();
8211        requirements.len() > 1
8212            && requirements
8213                .iter()
8214                .all(|requirement| !requirement.is_empty())
8215            && requirements.iter().enumerate().all(|(index, left)| {
8216                requirements[index + 1..].iter().all(|right| {
8217                    left.iter().all(|(_, left_guards)| {
8218                        right.iter().all(|(_, right_guards)| {
8219                            merge_preprocessor_guards(left_guards, right_guards).is_none()
8220                        })
8221                    })
8222                })
8223            })
8224    }
8225
8226    fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
8227        let mut pending = vec![terms.to_vec()];
8228        while let Some(branch_terms) = pending.pop() {
8229            let mut normalized = Vec::new();
8230            let mut covers_branch = false;
8231            for term in branch_terms {
8232                if term.iter().any(|guard| term.contains(&guard.negated())) {
8233                    continue;
8234                }
8235                if term.is_empty() {
8236                    covers_branch = true;
8237                    break;
8238                }
8239                if !normalized.iter().any(|existing| existing == &term) {
8240                    normalized.push(term);
8241                }
8242            }
8243            if covers_branch {
8244                continue;
8245            }
8246            let Some(split_guard) = normalized
8247                .iter()
8248                .flat_map(|term| term.iter())
8249                .next()
8250                .cloned()
8251            else {
8252                return false;
8253            };
8254            let negated_guard = split_guard.negated();
8255            let mut when_defined = Vec::new();
8256            let mut when_undefined = Vec::new();
8257            for term in normalized {
8258                if term.contains(&negated_guard) {
8259                    // This term cannot hold when `split_guard` is true.
8260                } else if term.contains(&split_guard) {
8261                    let mut reduced = term.clone();
8262                    reduced.remove(&split_guard);
8263                    when_defined.push(reduced);
8264                } else {
8265                    when_defined.push(term.clone());
8266                }
8267                if term.contains(&split_guard) {
8268                    // This term cannot hold when `split_guard` is false.
8269                } else if term.contains(&negated_guard) {
8270                    let mut reduced = term;
8271                    reduced.remove(&negated_guard);
8272                    when_undefined.push(reduced);
8273                } else {
8274                    when_undefined.push(term);
8275                }
8276            }
8277            pending.push(when_defined);
8278            pending.push(when_undefined);
8279        }
8280        true
8281    }
8282
8283    /// The byte range of the one `#if` family with a terminal `#else` that holds
8284    /// every physical declaration of every candidate, or `None` when they do not
8285    /// share one such family.
8286    ///
8287    /// Guard terms alone cannot distinguish one `#if` family from separate blocks
8288    /// whose macros changed between declarations. Require every physical range to
8289    /// belong to one syntax-tree family with a terminal `#else` before the terms
8290    /// can prove branch coverage.
8291    fn declarations_share_exhaustive_conditional_family(
8292        &self,
8293        analyzer: &CppGraphSource<'_>,
8294        candidates: &[&CodeUnit],
8295    ) -> Option<(usize, usize)> {
8296        let mut family_range = None;
8297        for candidate in candidates {
8298            let mut candidate_family = None;
8299            for occurrence in analyzer.declaration_source_occurrences(candidate)? {
8300                let key = occurrence.exhaustive_family?;
8301                if candidate_family.is_some_and(|existing| existing != key) {
8302                    return None;
8303                }
8304                candidate_family = Some(key);
8305            }
8306            let candidate_family = candidate_family?;
8307            if family_range.is_some_and(|existing| existing != candidate_family) {
8308                return None;
8309            }
8310            family_range = Some(candidate_family);
8311        }
8312        family_range
8313    }
8314
8315    pub fn complementary_same_fqn_type_declarations(
8316        &self,
8317        analyzer: &CppGraphSource<'_>,
8318        candidates: &[&CodeUnit],
8319        target: &CodeUnit,
8320    ) -> bool {
8321        if candidates.len() < 2
8322            || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
8323            || self
8324                .declarations_share_exhaustive_conditional_family(analyzer, candidates)
8325                .is_none()
8326        {
8327            return false;
8328        }
8329        Self::preprocessor_guard_terms_cover_all_paths(
8330            &self.declaration_family_guard_terms(analyzer, candidates),
8331        )
8332    }
8333
8334    fn declaration_family_guard_terms(
8335        &self,
8336        analyzer: &CppGraphSource<'_>,
8337        candidates: &[&CodeUnit],
8338    ) -> Vec<HashSet<PreprocessorGuard>> {
8339        candidates
8340            .iter()
8341            .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
8342            .map(|(_, guards)| guards)
8343            .collect()
8344    }
8345
8346    /// A callable name declared on every branch of one completed `#if`/`#else`
8347    /// family is declared on every configuration path, so a reference below the
8348    /// whole family sees one of the branches whatever the preprocessor decides.
8349    /// Answer the family's end byte: only past `#endif` is every branch's
8350    /// declaration behind the reference.
8351    ///
8352    /// This is the callable analogue of `complementary_same_fqn_type_declarations`
8353    /// and shares both of its primitives. It does not require two distinct
8354    /// `CodeUnit`s: branches that declare the same signature can collapse into
8355    /// one unit carrying one physical range per branch.
8356    ///
8357    /// The branches are alternate spellings of one declaration, never competing
8358    /// declarations, so only the first branch stands for the family. Reporting
8359    /// every branch as visible would turn a name the source declares exactly
8360    /// once into an ambiguity between build configurations.
8361    fn exhaustive_guard_family_activation(
8362        &self,
8363        analyzer: &CppGraphSource<'_>,
8364        candidate: &CodeUnit,
8365        reference: &CallableReferenceContext<'_>,
8366    ) -> Option<usize> {
8367        // Branch coverage says nothing about scope: a block-local declaration
8368        // stays invisible however many branches declare it.
8369        if !analyzer
8370            .declaration_source_occurrences(candidate)?
8371            .iter()
8372            .any(|occurrence| occurrence.fact.callable_activation.is_some())
8373        {
8374            return None;
8375        }
8376        let family = self
8377            .visible_identifier_candidates(reference.file, candidate.identifier())
8378            .filter(|peer| {
8379                peer.kind() == candidate.kind()
8380                    && peer.fq_name() == candidate.fq_name()
8381                    && peer.source() == candidate.source()
8382            })
8383            .collect::<Vec<_>>();
8384        let (_, family_end) =
8385            self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
8386        if !Self::preprocessor_guard_terms_cover_all_paths(
8387            &self.declaration_family_guard_terms(analyzer, &family),
8388        ) {
8389            return None;
8390        }
8391        // A reference whose effective context picks one branch already reaches
8392        // that branch through the ordinary same-guard path; the family must not
8393        // resurrect the branch the reference contradicts. The context includes
8394        // the translation unit's stable `__cplusplus` state and build-proven
8395        // defines, not only the reference's source guards: a C file below
8396        // `#ifdef __cplusplus ... #else ... #endif` takes the `#else` arm
8397        // however the family enumerates its branches.
8398        let effective = self.effective_reference_guards(reference.file, reference.guards());
8399        if !declaration_guard_requirements(analyzer, self.cpp, candidate)
8400            .iter()
8401            .any(|(_, guards)| guards_compatible_at_reference(guards, effective.as_ref()))
8402        {
8403            return None;
8404        }
8405        (first_declaration_byte(analyzer, candidate)?
8406            == family
8407                .iter()
8408                .filter_map(|peer| first_declaration_byte(analyzer, peer))
8409                .min()?)
8410        .then_some(family_end)
8411    }
8412
8413    fn type_candidate_preserving_target(
8414        &self,
8415        analyzer: &CppGraphSource<'_>,
8416        visible_from: &ProjectFile,
8417        candidate: &CodeUnit,
8418        target: &CodeUnit,
8419    ) -> Option<CodeUnit> {
8420        let mut current = candidate.clone();
8421        let mut matched_target = same_visible_symbol(&current, target)
8422            || self.compatible_primary_template_redeclarations(&current, target);
8423        let mut seen = HashSet::default();
8424        loop {
8425            if !seen.insert(current.clone()) {
8426                return None;
8427            }
8428            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
8429                return matched_target
8430                    .then(|| target.clone())
8431                    .or_else(|| current.is_class().then_some(current));
8432            };
8433            if self.flattened_macro_namespace_alias_target_matches(
8434                analyzer,
8435                visible_from,
8436                &current,
8437                &alias_target,
8438                target,
8439            ) {
8440                return Some(target.clone());
8441            }
8442            if matches!(alias_target, StructuredAliasTarget::Builtin) {
8443                return matched_target
8444                    .then(|| target.clone())
8445                    .or_else(|| current.is_class().then_some(current));
8446            }
8447            // A non-template alias can name a template alias with explicit
8448            // arguments (for example, `using Result = Expected<int>`).  When
8449            // the requested target is that alias's primary declaration, keep
8450            // the primary identity before expanding the RHS arguments.  The
8451            // expansion would otherwise canonicalize through the underlying
8452            // implementation type and lose the target spelling used by the
8453            // forward resolver.
8454            if !self.cpp_template_metadata.contains_key(&current)
8455                && let Some(primary) =
8456                    self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
8457                && (same_visible_symbol(&primary, target)
8458                    || self.compatible_primary_template_redeclarations(&primary, target))
8459            {
8460                return Some(target.clone());
8461            }
8462            if same_visible_symbol(&current, target) {
8463                return Some(target.clone());
8464            }
8465            if self.cpp_template_metadata.contains_key(&current) {
8466                return None;
8467            }
8468            let Some(next) =
8469                self.resolve_structured_alias_target(visible_from, &current, &alias_target)
8470            else {
8471                return matched_target.then(|| target.clone());
8472            };
8473            current = next;
8474            matched_target |= same_visible_symbol(&current, target)
8475                || self.compatible_primary_template_redeclarations(&current, target);
8476        }
8477    }
8478
8479    fn compatible_primary_template_redeclarations(
8480        &self,
8481        left: &CodeUnit,
8482        right: &CodeUnit,
8483    ) -> bool {
8484        let (Some(left_metadata), Some(right_metadata)) = (
8485            self.cpp_template_metadata.get(left),
8486            self.cpp_template_metadata.get(right),
8487        ) else {
8488            return false;
8489        };
8490        left_metadata.primary_fq_name == right_metadata.primary_fq_name
8491            && left_metadata.is_primary()
8492            && right_metadata.is_primary()
8493            && cpp_reconcile_primary_template_parameters(
8494                &[(left, left_metadata), (right, right_metadata)],
8495                right,
8496            )
8497            .is_some()
8498    }
8499
8500    fn alias_candidate_may_preserve_target(
8501        &self,
8502        analyzer: &CppGraphSource<'_>,
8503        visible_from: &ProjectFile,
8504        candidate: &CodeUnit,
8505        target: &CodeUnit,
8506    ) -> bool {
8507        // A hop that lands on a conditional alias family follows every arm:
8508        // this is a may-reach gate, and the reference's guards choose the arm
8509        // later (#6902).
8510        let mut pending = vec![candidate.clone()];
8511        let mut seen = HashSet::default();
8512        while let Some(current) = pending.pop() {
8513            if same_visible_symbol(&current, target)
8514                || self.compatible_primary_template_redeclarations(&current, target)
8515            {
8516                return true;
8517            }
8518            if self.cpp_template_metadata.contains_key(&current) {
8519                return true;
8520            }
8521            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
8522                continue;
8523            };
8524            let StructuredAliasTarget::Named {
8525                components,
8526                global,
8527                arguments,
8528            } = alias_target
8529            else {
8530                continue;
8531            };
8532            if arguments.is_some() || !seen.insert(current.clone()) {
8533                return true;
8534            }
8535            let qualified = components.join("::");
8536            let next = if global {
8537                unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
8538            } else {
8539                self.resolve_unique_type_for_declaration(visible_from, &current, &qualified)
8540            };
8541            let Some(next) = next else {
8542                return true;
8543            };
8544            match self.conditional_alias_arms(analyzer, visible_from, &next) {
8545                Some(arms) => pending.extend(arms),
8546                None => pending.push(next),
8547            }
8548        }
8549        false
8550    }
8551
8552    fn alias_candidate_structurally_reaches_target(
8553        &self,
8554        analyzer: &CppGraphSource<'_>,
8555        visible_from: &ProjectFile,
8556        candidate: &CodeUnit,
8557        target: &CodeUnit,
8558    ) -> bool {
8559        // Every arm of a conditional alias family on the chain may reach the
8560        // target; the reference's guards choose among them later (#6902).
8561        let mut pending = vec![candidate.clone()];
8562        let mut seen = HashSet::default();
8563        while let Some(current) = pending.pop() {
8564            if same_visible_symbol(&current, target)
8565                || self.compatible_primary_template_redeclarations(&current, target)
8566                || self
8567                    .cpp_template_metadata
8568                    .get(&current)
8569                    .zip(self.cpp_template_metadata.get(target))
8570                    .is_some_and(|(current, target)| {
8571                        current.primary_fq_name == target.primary_fq_name
8572                    })
8573            {
8574                return true;
8575            }
8576            if !seen.insert(current.clone()) {
8577                continue;
8578            }
8579            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
8580                continue;
8581            };
8582            if let StructuredAliasTarget::Named { components, .. } = &alias_target
8583                && components[..components.len().saturating_sub(1)]
8584                    .iter()
8585                    .any(|component| {
8586                        self.type_reference_component_directly_names_target(component, target)
8587                    })
8588                && self.structured_class_alias_path_preserves_target(
8589                    analyzer,
8590                    visible_from,
8591                    &current,
8592                    target,
8593                )
8594            {
8595                return true;
8596            }
8597            if matches!(alias_target, StructuredAliasTarget::Builtin) {
8598                continue;
8599            }
8600            let Some(primary) =
8601                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
8602            else {
8603                continue;
8604            };
8605            match self.conditional_alias_arms(analyzer, visible_from, &primary) {
8606                Some(arms) => pending.extend(arms),
8607                None => pending.push(primary),
8608            }
8609        }
8610        false
8611    }
8612
8613    /// Whether one indexed alias component in a qualified type reference has
8614    /// a structured path to `target`.
8615    ///
8616    /// Alias terminal names such as `type` are deliberately common. A
8617    /// file-wide name set cannot distinguish `Gen<T>::type`, whose indexed RHS
8618    /// names the target, from thousands of unrelated `Traits<T>::type`
8619    /// references. Match every qualified prefix against the alias's canonical
8620    /// scope before following its structured RHS. An unresolved dependent RHS
8621    /// fails this coarse gate only when no other component names the target;
8622    /// references such as `Identity<Target>::type` are admitted by that target
8623    /// component before this helper is called.
8624    pub fn qualified_alias_reference_may_reach_target(
8625        &self,
8626        analyzer: &CppGraphSource<'_>,
8627        file: &ProjectFile,
8628        components: &[String],
8629        global: bool,
8630        target: &CodeUnit,
8631    ) -> bool {
8632        for end in 0..components.len() {
8633            let spelled = &components[..=end];
8634            for candidate in self
8635                .visible_identifier_candidates(file, &components[end])
8636                .filter(|candidate| declared_type_alias(analyzer, candidate))
8637            {
8638                let candidate_components = canonical_cpp_scope_components(candidate);
8639                let shape_matches = if global {
8640                    candidate_components == spelled
8641                } else {
8642                    candidate_components.ends_with(spelled)
8643                };
8644                if shape_matches
8645                    && self.alias_candidate_structurally_reaches_target(
8646                        analyzer, file, candidate, target,
8647                    )
8648                {
8649                    return true;
8650                }
8651            }
8652        }
8653        false
8654    }
8655
8656    /// Every indexed type declaration `raw_name` names when it is written in
8657    /// `declaration`'s namespace: the innermost enclosing namespace that holds
8658    /// the name wins, otherwise the name is looked up unqualified.
8659    fn type_candidates_for_declaration<'b>(
8660        &'b self,
8661        visible_from: &ProjectFile,
8662        declaration: &CodeUnit,
8663        raw_name: &str,
8664    ) -> Vec<&'b CodeUnit> {
8665        let Some(normalized) = normalize_reference_name(raw_name) else {
8666            return Vec::new();
8667        };
8668        if let Some(namespace) = cpp_namespace_for(declaration) {
8669            for prefix in namespace_prefixes(&namespace) {
8670                let qualified = format!("{prefix}::{normalized}");
8671                let candidates = self.type_candidates(visible_from, &qualified);
8672                if !candidates.is_empty() {
8673                    return candidates;
8674                }
8675            }
8676        }
8677        self.type_candidates(visible_from, &normalized)
8678    }
8679
8680    fn resolve_unique_type_for_declaration(
8681        &self,
8682        visible_from: &ProjectFile,
8683        declaration: &CodeUnit,
8684        raw_name: &str,
8685    ) -> Option<CodeUnit> {
8686        unique_logical_type_candidate(self.type_candidates_for_declaration(
8687            visible_from,
8688            declaration,
8689            raw_name,
8690        ))
8691    }
8692
8693    /// The type `raw_name` names at `declaration`, answered with the
8694    /// declaration that defines it.
8695    ///
8696    /// C spells one type twice. `typedef struct T T;` in the header that
8697    /// publishes the handle indexes an incomplete `struct T` beside the
8698    /// complete `struct T { ... }` in the header that declares the members, and
8699    /// both carry the same kind and fully qualified name, so
8700    /// [`logical_type_candidate`] is free to answer with either. mbedtls writes
8701    /// exactly that: `include/mbedtls/ssl.h` forward-declares
8702    /// `mbedtls_ssl_handshake_params` for the pointer member
8703    /// `mbedtls_ssl_handshake_params *MBEDTLS_PRIVATE(handshake)` while
8704    /// `library/ssl_misc.h` defines its fields. A receiver typed from a field
8705    /// declaration is asked for its members on the next link of the chain, and
8706    /// only the definition owns them (#2982).
8707    ///
8708    /// Competing definitions leave nothing to choose between, so keep the
8709    /// logical answer rather than picking one of them.
8710    fn resolve_defining_type_for_declaration(
8711        &self,
8712        analyzer: &CppGraphSource<'_>,
8713        visible_from: &ProjectFile,
8714        declaration: &CodeUnit,
8715        raw_name: &str,
8716    ) -> Option<CodeUnit> {
8717        let candidates = self.type_candidates_for_declaration(visible_from, declaration, raw_name);
8718        let logical = unique_logical_type_candidate(candidates.clone())?;
8719        let mut defining = candidates.into_iter().filter(|candidate| {
8720            candidate.is_class()
8721                && cpp_class_declaration_strength(analyzer, candidate)
8722                    == CppClassDeclarationStrength::Full
8723        });
8724        match (defining.next(), defining.next()) {
8725            (Some(unique_definition), None) => Some(unique_definition.clone()),
8726            _ => Some(logical),
8727        }
8728    }
8729
8730    pub fn resolves_to_type(
8731        &self,
8732        analyzer: &CppGraphSource<'_>,
8733        file: &ProjectFile,
8734        raw_name: &str,
8735        target: &CodeUnit,
8736    ) -> bool {
8737        let Some(normalized) = normalize_reference_name(raw_name) else {
8738            return false;
8739        };
8740        let candidates = self.type_candidates(file, &normalized);
8741        if candidates.is_empty() {
8742            return self.parser_alias_resolves_to_type(file, raw_name, target);
8743        }
8744        let Some(resolved) =
8745            self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
8746        else {
8747            return false;
8748        };
8749        same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
8750    }
8751
8752    pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
8753        let mut current = alias.clone();
8754        let mut visited = HashSet::default();
8755        while visited.insert(current.clone()) {
8756            let raw_target = cpp_canonical_alias_target_text(&self.cpp_source(), &current)?;
8757            let resolved =
8758                self.resolve_type_for_declaration(current.source(), &current, &raw_target)?;
8759            if resolved.is_class() {
8760                return Some(resolved);
8761            }
8762            if !declared_type_alias(&self.cpp_source(), &resolved) {
8763                return None;
8764            }
8765            current = resolved;
8766        }
8767        None
8768    }
8769
8770    /// Whether two callable declarations declare one function.
8771    ///
8772    /// [`same_logical_symbol`] compares the persisted signature strings, which
8773    /// embed each parameter type exactly as it was spelled. A header
8774    /// declaration written inside `namespace zmq { class dist_t { ... } }` says
8775    /// `send_to_matching(msg_t *)` while its out-of-line body at file scope
8776    /// says `zmq::msg_t *`, so the string comparison reports two symbols where
8777    /// C++ ([basic.def], [dcl.fct]) sees one declaration and one definition.
8778    /// This resolves the written parameter names before comparing them and
8779    /// reports the same answer the language does for the cases it can prove.
8780    ///
8781    /// Everything it cannot prove stays two symbols: a template declaration, a
8782    /// parameter with no comparable shape, a name that resolves on one side
8783    /// only, and an alias chain it cannot follow safely (#2010).
8784    pub fn same_logical_callable(
8785        &self,
8786        analyzer: &CppGraphSource<'_>,
8787        left: &CodeUnit,
8788        right: &CodeUnit,
8789    ) -> bool {
8790        if same_logical_symbol(left, right) {
8791            return true;
8792        }
8793        if left.kind() != right.kind()
8794            || !left.is_callable()
8795            || !right.is_callable()
8796            || left.fq_name() != right.fq_name()
8797        {
8798            return false;
8799        }
8800        let (Some(left_comparable), Some(right_comparable)) = (
8801            self.callable_comparable(analyzer, left),
8802            self.callable_comparable(analyzer, right),
8803        ) else {
8804            return false;
8805        };
8806        // A template declaration and its out-of-line body can also diverge
8807        // outside the parameter list - `template <class T>` against
8808        // `template <typename U>` - and the template head is part of the
8809        // persisted signature. The structured template levels decide whether
8810        // the heads and owner arguments agree, and bind each side's parameter
8811        // names for the parameter comparison below. A template whose levels
8812        // cannot be read keeps string identity.
8813        let binding = match (&left_comparable.template, &right_comparable.template) {
8814            (Some(left_template), Some(right_template))
8815                if left_template.is_templated() || right_template.is_templated() =>
8816            {
8817                match left_template.reconcile(right_template) {
8818                    CppTemplateReconciliation::Equivalent(binding) => binding,
8819                    CppTemplateReconciliation::Distinct
8820                    | CppTemplateReconciliation::Unsupported => return false,
8821                }
8822            }
8823            (Some(_), Some(_)) => CppTemplateBinding::default(),
8824            _ if self.callable_is_template_declaration(analyzer, left)
8825                || self.callable_is_template_declaration(analyzer, right) =>
8826            {
8827                return false;
8828            }
8829            _ => CppTemplateBinding::default(),
8830        };
8831        // The trailing member `const`, ref-qualifier, `noexcept`, trailing
8832        // return type and requires-clause are part of C++ callable identity and
8833        // an out-of-line definition repeats them verbatim, so they must agree
8834        // as written.
8835        if left_comparable.suffix != right_comparable.suffix
8836            || left_comparable.shapes.len() != right_comparable.shapes.len()
8837        {
8838            return false;
8839        }
8840        left_comparable
8841            .shapes
8842            .iter()
8843            .zip(right_comparable.shapes.iter())
8844            .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
8845                (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
8846                (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
8847                    self.comparable_shapes_agree(analyzer, &binding, left_shape, right_shape)
8848                }
8849                // An unstructured parameter records that the reduction failed,
8850                // not that the two spellings mean the same type, so it agrees
8851                // with nothing - including another unstructured parameter.
8852                _ => false,
8853            })
8854    }
8855
8856    /// Whether two complete class definitions in different files define one
8857    /// class.
8858    ///
8859    /// The one-definition rule makes every definition of a class with linkage
8860    /// the same entity, and it requires those definitions to agree token for
8861    /// token ([basic.def.odr]). A shared qualified name is only the spelling of
8862    /// that claim, so it is never enough. The evidence is structural: both
8863    /// definitions are complete, their template levels reconcile (heads, and
8864    /// the arguments that select a specialization), their base specifiers
8865    /// agree, and they declare the same members -- the same kinds, names and
8866    /// signatures, which embed each member's template head as written. Two
8867    /// classes that share a name but differ in any of these stay two classes.
8868    pub fn same_logical_class_definition(
8869        &self,
8870        analyzer: &CppGraphSource<'_>,
8871        left: &CodeUnit,
8872        right: &CodeUnit,
8873    ) -> bool {
8874        if same_symbol(left, right) {
8875            return true;
8876        }
8877        if !left.is_class() || !right.is_class() || left.fq_name() != right.fq_name() {
8878            return false;
8879        }
8880        let key = if left <= right {
8881            (left.clone(), right.clone())
8882        } else {
8883            (right.clone(), left.clone())
8884        };
8885        if let Some(cached) = self
8886            .class_definition_agreements
8887            .lock()
8888            .expect("C++ class definition agreement cache poisoned")
8889            .get(&key)
8890        {
8891            return *cached;
8892        }
8893        let agree = self.class_definitions_agree(analyzer, left, right);
8894        self.class_definition_agreements
8895            .lock()
8896            .expect("C++ class definition agreement cache poisoned")
8897            .insert(key, agree);
8898        agree
8899    }
8900
8901    fn class_definitions_agree(
8902        &self,
8903        analyzer: &CppGraphSource<'_>,
8904        left: &CodeUnit,
8905        right: &CodeUnit,
8906    ) -> bool {
8907        let complete_definition = |class: &CodeUnit| {
8908            let mut facts = analyzer
8909                .declaration_source_properties(class)?
8910                .into_iter()
8911                .filter(|fact| fact.class_strength == CppClassDeclarationStrength::Full);
8912            let fact = facts.next()?;
8913            // Two complete definitions in one file are a conditional family
8914            // this comparison does not choose between.
8915            facts.next().is_none().then_some(fact)
8916        };
8917        let (Some(left_fact), Some(right_fact)) =
8918            (complete_definition(left), complete_definition(right))
8919        else {
8920            return false;
8921        };
8922        if left_fact.bases != right_fact.bases {
8923            return false;
8924        }
8925        let (Some(left_template), Some(right_template)) = (
8926            self.declaration_template_context(left, left_fact.declaration),
8927            self.declaration_template_context(right, right_fact.declaration),
8928        ) else {
8929            return false;
8930        };
8931        if !matches!(
8932            left_template.reconcile(&right_template),
8933            CppTemplateReconciliation::Equivalent(_)
8934        ) {
8935            return false;
8936        }
8937        let members = |class: &CodeUnit| {
8938            let mut members = analyzer
8939                .direct_children(class)
8940                .into_iter()
8941                .filter(|member| member.source() == class.source())
8942                .map(|member| {
8943                    (
8944                        member.kind(),
8945                        member.identifier().to_owned(),
8946                        member.signature().map(str::to_owned),
8947                    )
8948                })
8949                .collect::<Vec<_>>();
8950            members.sort();
8951            members
8952        };
8953        members(left) == members(right)
8954    }
8955
8956    /// Whether `file` sees a definition of `class` through an identical
8957    /// definition in another header.
8958    ///
8959    /// The include graph relates a header to the translation units that
8960    /// include it. When a class is defined identically in two headers, a
8961    /// translation unit that includes only one copy still defines members of
8962    /// the class the other copy declares: the two definitions are one class
8963    /// ([`Self::same_logical_class_definition`]). Each source declaration
8964    /// keeps its own identity; only the relation crosses.
8965    pub fn class_definition_bridges_to_file(
8966        &self,
8967        analyzer: &CppGraphSource<'_>,
8968        class: &CodeUnit,
8969        file: &ProjectFile,
8970        header_body_related: &dyn Fn(&ProjectFile, &ProjectFile) -> bool,
8971    ) -> bool {
8972        class.is_class()
8973            && class.source() != file
8974            && crate::identity::cpp_source_path_is_header(class.source())
8975            && analyzer
8976                .index
8977                .definitions_by_structured_name(class.fq(), Language::Cpp)
8978                .into_iter()
8979                .any(|copy| {
8980                    // The copy's own header holds the copy's declarations,
8981                    // which keep their own provenance; only files beyond it
8982                    // are reached through the copy.
8983                    copy.is_class()
8984                        && copy.source() != class.source()
8985                        && copy.source() != file
8986                        && crate::identity::cpp_source_path_is_header(copy.source())
8987                        && header_body_related(copy.source(), file)
8988                        && self.same_logical_class_definition(analyzer, class, &copy)
8989                })
8990    }
8991
8992    /// Whether the header member declaration `declaration` reaches `body`
8993    /// through an identical definition of its class
8994    /// ([`Self::class_definition_bridges_to_file`]).
8995    pub fn member_declaration_bridges_to_body(
8996        &self,
8997        analyzer: &CppGraphSource<'_>,
8998        declaration: &CodeUnit,
8999        body: &CodeUnit,
9000        header_body_related: &dyn Fn(&ProjectFile, &ProjectFile) -> bool,
9001    ) -> bool {
9002        declaration.is_callable()
9003            && self
9004                .cpp
9005                .structural_parent_of(declaration)
9006                .is_some_and(|owner| {
9007                    self.class_definition_bridges_to_file(
9008                        analyzer,
9009                        &owner,
9010                        body.source(),
9011                        header_body_related,
9012                    )
9013                })
9014    }
9015
9016    /// Compare two parameter shapes node by node with an explicit paired stack.
9017    ///
9018    /// Shape variants and cv-qualifiers must agree exactly at every level; only
9019    /// the named leaves may be spelled differently, and they agree when they
9020    /// resolve to one type declaration.
9021    fn comparable_shapes_agree(
9022        &self,
9023        analyzer: &CppGraphSource<'_>,
9024        binding: &CppTemplateBinding,
9025        left: &CppComparableParameter,
9026        right: &CppComparableParameter,
9027    ) -> bool {
9028        let mut stack = vec![(left.root(), right.root())];
9029        while let Some((left_index, right_index)) = stack.pop() {
9030            match (left.node(left_index), right.node(right_index)) {
9031                (
9032                    CppComparableNode::Named {
9033                        name: left_name,
9034                        primitive: left_primitive,
9035                        konst: left_konst,
9036                        volatil: left_volatil,
9037                    },
9038                    CppComparableNode::Named {
9039                        name: right_name,
9040                        primitive: right_primitive,
9041                        konst: right_konst,
9042                        volatil: right_volatil,
9043                    },
9044                ) => {
9045                    if left_konst != right_konst
9046                        || left_volatil != right_volatil
9047                        || left_primitive != right_primitive
9048                        || !self.comparable_names_agree(
9049                            analyzer,
9050                            binding,
9051                            left_name,
9052                            right_name,
9053                            *left_primitive,
9054                        )
9055                    {
9056                        return false;
9057                    }
9058                }
9059                (
9060                    CppComparableNode::Pointer {
9061                        inner: left_inner,
9062                        konst: left_konst,
9063                        volatil: left_volatil,
9064                    },
9065                    CppComparableNode::Pointer {
9066                        inner: right_inner,
9067                        konst: right_konst,
9068                        volatil: right_volatil,
9069                    },
9070                ) => {
9071                    if left_konst != right_konst || left_volatil != right_volatil {
9072                        return false;
9073                    }
9074                    stack.push((*left_inner, *right_inner));
9075                }
9076                (
9077                    CppComparableNode::Reference { inner: left_inner },
9078                    CppComparableNode::Reference { inner: right_inner },
9079                )
9080                | (
9081                    CppComparableNode::Array { inner: left_inner },
9082                    CppComparableNode::Array { inner: right_inner },
9083                ) => stack.push((*left_inner, *right_inner)),
9084                (
9085                    CppComparableNode::Generic {
9086                        base: left_base,
9087                        arguments: left_arguments,
9088                    },
9089                    CppComparableNode::Generic {
9090                        base: right_base,
9091                        arguments: right_arguments,
9092                    },
9093                ) => {
9094                    if left_arguments.len() != right_arguments.len() {
9095                        return false;
9096                    }
9097                    stack.push((*left_base, *right_base));
9098                    stack.extend(
9099                        left_arguments.iter().zip(right_arguments.iter()).map(
9100                            |(left_argument, right_argument)| (*left_argument, *right_argument),
9101                        ),
9102                    );
9103                }
9104                _ => return false,
9105            }
9106        }
9107        true
9108    }
9109
9110    /// Whether two written type names denote one type.
9111    ///
9112    /// A primitive denotes the same type in every scope, so its recorded
9113    /// lexical scope is noise and its spelling decides. A nominal name is
9114    /// resolved on each side independently: two resolved names agree when they
9115    /// reach one type declaration, and two unresolved names agree only on
9116    /// exact agreement of what was written, which is no weaker than the
9117    /// whole-signature string equality this comparison replaces. Resolution on
9118    /// one side only is evidence of difference, never of agreement.
9119    ///
9120    /// A template parameter hides every outer name it spells, so a path that
9121    /// starts with one is decided by `binding` alone: both sides must start
9122    /// with the parameters the reconciled heads pair, and agree on the rest
9123    /// of the path (`typename T::value_type`).
9124    fn comparable_names_agree(
9125        &self,
9126        analyzer: &CppGraphSource<'_>,
9127        binding: &CppTemplateBinding,
9128        left: &StructuredTypeName,
9129        right: &StructuredTypeName,
9130        primitive: bool,
9131    ) -> bool {
9132        if primitive {
9133            return left.path() == right.path();
9134        }
9135        if !binding.is_empty() {
9136            let (Some(left_head), Some(right_head)) = (left.path().first(), right.path().first())
9137            else {
9138                return false;
9139            };
9140            match (
9141                binding.is_left_parameter(left_head),
9142                binding.is_right_parameter(right_head),
9143            ) {
9144                (true, true) => {
9145                    return !left.is_absolute()
9146                        && !right.is_absolute()
9147                        && binding.binds(right_head, left_head)
9148                        && left.path()[1..] == right.path()[1..];
9149                }
9150                (false, false) => {}
9151                _ => return false,
9152            }
9153        }
9154        match (
9155            self.comparable_name_terminal(analyzer, left),
9156            self.comparable_name_terminal(analyzer, right),
9157        ) {
9158            (Some(left_terminal), Some(right_terminal)) => {
9159                same_logical_symbol(&left_terminal, &right_terminal)
9160            }
9161            (None, None) => {
9162                left.path() == right.path() && left.is_absolute() == right.is_absolute()
9163            }
9164            _ => false,
9165        }
9166    }
9167
9168    /// The class declaration a written type name denotes, or `None` when the
9169    /// workspace cannot prove one.
9170    ///
9171    /// The lookup is a closure-independent lexical-scope prefix walk over the
9172    /// stored C++ identifier index rather than a visibility lookup: a body's
9173    /// `.cpp` is almost never in the reference file's include closure. Any name
9174    /// this walk resolves is one an enclosing-scope lookup could resolve, so it
9175    /// cannot invent a type the compiler could not see; `using`-directives are
9176    /// not modelled, and a name that needs one stays unresolved.
9177    fn comparable_name_terminal(
9178        &self,
9179        analyzer: &CppGraphSource<'_>,
9180        name: &StructuredTypeName,
9181    ) -> Option<CodeUnit> {
9182        let mut current = self.comparable_name_declaration(analyzer, name)?;
9183        let mut visited = HashSet::default();
9184        for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
9185            // The alias question is asked before the class question, and
9186            // through `declared_type_alias` rather than `is_type_alias`,
9187            // because extraction records `using A8 = A7;` as a *Class* unit
9188            // whose signature is the alias declaration. Reading the kind first
9189            // would end the chase on the alias itself and report an alias
9190            // spelling and its underlying class as two types (#2010).
9191            if !declared_type_alias(analyzer, &current) {
9192                return current.is_class().then_some(current);
9193            }
9194            if !visited.insert(current.clone()) {
9195                return None;
9196            }
9197            let facts = analyzer.declaration_source_properties(&current)?;
9198            if facts.iter().any(|fact| fact.adds_indirection) {
9199                return None;
9200            }
9201            let raw_target = cpp_canonical_alias_target_text(analyzer, &current)?;
9202            current = self.comparable_alias_target(analyzer, &current, &raw_target)?;
9203        }
9204        None
9205    }
9206
9207    /// The declaration one alias hop lands on: the type `raw_target` names,
9208    /// looked up from the alias declaration's own enclosing namespace.
9209    ///
9210    /// The hop takes the same closure-independent prefix walk the first lookup
9211    /// took, and deliberately not `resolve_type_for_declaration`: that one
9212    /// answers out of the `VisibilityIndex`, which is rooted at the reference
9213    /// file, while the alias declaration this hop starts from is reached
9214    /// through the workspace definition index and its file need not be in that
9215    /// root's include closure - where the visibility lookup answers nothing and
9216    /// the chase would stop on the alias itself (#2010).
9217    fn comparable_alias_target(
9218        &self,
9219        analyzer: &CppGraphSource<'_>,
9220        alias: &CodeUnit,
9221        raw_target: &str,
9222    ) -> Option<CodeUnit> {
9223        // `raw_target` is the alias declaration's written type text, so it is a
9224        // plain `::`-joined qualified-id: the same domain the shared symbol-path
9225        // parser reads, and the same leading `::` that marks an absolute name
9226        // everywhere else this crate normalizes a reference.
9227        let absolute = raw_target.trim_start().starts_with("::");
9228        let normalized = normalize_reference_name(raw_target)?;
9229        let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9230            brokk_bifrost_core::analyzer::Language::Cpp,
9231            &normalized,
9232        );
9233        let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
9234            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9235                brokk_bifrost_core::analyzer::Language::Cpp,
9236                &namespace,
9237            )
9238        });
9239        let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
9240        self.comparable_name_declaration(analyzer, &name)
9241    }
9242
9243    /// The one type declaration `name` names, by enclosing scope, innermost
9244    /// first.
9245    ///
9246    /// The first prefix depth that names anything decides: an inner scope hides
9247    /// an outer one, so a match there is the answer even when an outer scope
9248    /// also declares the name. Several logically distinct declarations at that
9249    /// depth are an ambiguity this comparison must not guess at.
9250    fn comparable_name_declaration(
9251        &self,
9252        analyzer: &CppGraphSource<'_>,
9253        name: &StructuredTypeName,
9254    ) -> Option<CodeUnit> {
9255        // The same parameter-name pair is commonly compared once for every
9256        // physical declaration in a candidate set. Keep the index lookup under
9257        // the cache lock so concurrent workers cannot all miss the same key
9258        // and repeat the workspace candidate filter. `None` is a meaningful
9259        // result too: unresolved and ambiguous names must remain unresolved on
9260        // every later comparison.
9261        let mut cache = self
9262            .comparable_name_declarations
9263            .lock()
9264            .expect("C++ comparable name declaration cache poisoned");
9265        if let Some(cached) = cache.get(name) {
9266            return cached.clone();
9267        }
9268        let interner = segment_interner();
9269        let identifier = name.path().last()?;
9270        let candidates_by_identifier = self.cpp.visibility_identifier_candidates(identifier);
9271        let first_depth = if name.is_absolute() {
9272            0
9273        } else {
9274            name.lexical_scope().len()
9275        };
9276        let mut resolved = None;
9277        for depth in (0..=first_depth).rev() {
9278            let mut structured = FqName::new();
9279            for component in name.lexical_scope()[..depth].iter().chain(name.path()) {
9280                structured.push(interner.intern(component, SegmentKind::Unknown));
9281            }
9282            let mut candidates = candidates_by_identifier
9283                .iter()
9284                .filter(|unit| unit.fq().same_segment_texts(&structured))
9285                .filter(|unit| {
9286                    unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
9287                })
9288                .cloned();
9289            let Some(first) = candidates.next() else {
9290                continue;
9291            };
9292            resolved = candidates
9293                .all(|unit| same_logical_symbol(&unit, &first))
9294                .then_some(first);
9295            break;
9296        }
9297        cache.insert(name.clone(), resolved.clone());
9298        resolved
9299    }
9300
9301    /// The comparison inputs of one callable declaration, extracted once.
9302    ///
9303    /// The comparison itself runs only when two candidates share kind and fully
9304    /// qualified name but not signature, which is rare; re-reading the same
9305    /// declaration for every pair in a candidate set is not.
9306    fn callable_comparable(
9307        &self,
9308        analyzer: &CppGraphSource<'_>,
9309        unit: &CodeUnit,
9310    ) -> Option<Arc<ExtractedComparable>> {
9311        if let Some(cached) = self
9312            .callable_comparables
9313            .lock()
9314            .expect("C++ callable comparable cache poisoned")
9315            .get(unit)
9316            .cloned()
9317        {
9318            return cached;
9319        }
9320        let extracted = self
9321            .extract_callable_comparable(analyzer, unit)
9322            .map(Arc::new);
9323        self.callable_comparables
9324            .lock()
9325            .expect("C++ callable comparable cache poisoned")
9326            .insert(unit.clone(), extracted.clone());
9327        extracted
9328    }
9329
9330    fn extract_callable_comparable(
9331        &self,
9332        analyzer: &CppGraphSource<'_>,
9333        unit: &CodeUnit,
9334    ) -> Option<ExtractedComparable> {
9335        let fact = analyzer
9336            .declaration_source_properties(unit)?
9337            .into_iter()
9338            .find(|fact| fact.callable_comparable_shapes.is_some())?;
9339        let template = self.declaration_template_context(unit, fact.declaration);
9340        Some(ExtractedComparable {
9341            shapes: fact.callable_comparable_shapes?,
9342            suffix: fact.callable_identity_suffix?,
9343            template,
9344        })
9345    }
9346
9347    /// The template levels of one source declaration of `unit`, read from
9348    /// the file's prepared syntax at the declaration's exact occurrence.
9349    pub fn declaration_template_context(
9350        &self,
9351        unit: &CodeUnit,
9352        declaration: SourceDeclarationId,
9353    ) -> Option<CppTemplateContext> {
9354        let facts = self
9355            .cpp
9356            .declaration_source_facts(self.token, unit.source())?;
9357        let occurrence = facts.source.declaration(declaration).occurrence;
9358        let range = facts.source.occurrence(occurrence).range;
9359        let syntax = self.cpp.prepared_syntax(self.token, unit.source())?;
9360        let mut node = node_for_exact_range(syntax.tree().root_node(), &range)?;
9361        // An occurrence may span the template wrapper; the context starts at
9362        // the declaration it wraps.
9363        while node.kind() == "template_declaration" {
9364            node = node.named_child(node.named_child_count().checked_sub(1)?)?;
9365        }
9366        cpp_declaration_template_context(node, syntax.source())
9367    }
9368
9369    pub fn canonical_type_for_reference(
9370        &self,
9371        file: &ProjectFile,
9372        raw_name: &str,
9373    ) -> Option<CodeUnit> {
9374        let resolved = self.resolve_type(file, raw_name)?;
9375        self.alias_target(&resolved).or(Some(resolved))
9376    }
9377
9378    pub fn parser_alias_resolves_to_type(
9379        &self,
9380        file: &ProjectFile,
9381        raw_name: &str,
9382        target: &CodeUnit,
9383    ) -> bool {
9384        let Some(alias_name) = normalize_reference_name(raw_name) else {
9385            return false;
9386        };
9387        any_visible_source_file_matches(&self.visible_source_files_by_root, file, |source_file| {
9388            self.file_alias_matches(self.cpp, source_file, &alias_name, target)
9389        })
9390    }
9391
9392    fn file_alias_matches(
9393        &self,
9394        cpp: &dyn CppSource,
9395        file: &ProjectFile,
9396        alias_name: &str,
9397        target: &CodeUnit,
9398    ) -> bool {
9399        self.canonical_alias_cell(cpp, file).is_some_and(|cell| {
9400            cell.get()
9401                .expect("published canonical aliases")
9402                .iter()
9403                .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
9404        })
9405    }
9406
9407    fn canonical_alias_cell(
9408        &self,
9409        cpp: &dyn CppSource,
9410        file: &ProjectFile,
9411    ) -> Option<Arc<OnceLock<Box<[CppAlias]>>>> {
9412        let cell = {
9413            let mut cells = self.alias_cells.lock().expect("alias cell map lock");
9414            Arc::clone(
9415                cells
9416                    .entry(file.clone())
9417                    .or_insert_with(|| Arc::new(OnceLock::new())),
9418            )
9419        };
9420        if cell.get().is_none() {
9421            let aliases = aliases_from_canonical_source(cpp, self.token, file)?;
9422            cell.get_or_init(|| {
9423                self.parser_alias_source_parses
9424                    .fetch_add(1, Ordering::Relaxed);
9425                #[cfg(any(test, feature = "test-support"))]
9426                {
9427                    *self
9428                        .alias_source_parse_counts
9429                        .lock()
9430                        .expect("alias source parse count lock")
9431                        .entry(file.clone())
9432                        .or_default() += 1;
9433                }
9434                aliases.into_boxed_slice()
9435            });
9436        }
9437        Some(cell)
9438    }
9439
9440    #[cfg(any(test, feature = "test-support"))]
9441    pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
9442        self.visible_source_files_by_root
9443            .get(file)
9444            .cloned()
9445            .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
9446    }
9447
9448    #[cfg(any(test, feature = "test-support"))]
9449    pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
9450        self.alias_source_parse_counts
9451            .lock()
9452            .expect("alias source parse count lock")
9453            .get(file)
9454            .copied()
9455            .unwrap_or(0)
9456    }
9457
9458    #[cfg(any(test, feature = "test-support"))]
9459    pub fn parser_alias_fallback_file_count_for_test(&self) -> usize {
9460        self.parser_alias_fallback_files.load(Ordering::Relaxed)
9461    }
9462
9463    pub fn resolve_named(
9464        &self,
9465        file: &ProjectFile,
9466        raw_name: &str,
9467        kind: TargetKind,
9468    ) -> Option<CodeUnit> {
9469        let normalized = normalize_reference_name(raw_name)?;
9470        self.named_candidates_for_normalized(file, &normalized, kind)
9471            .into_iter()
9472            .next()
9473            .cloned()
9474    }
9475
9476    pub fn contains_named_symbol(
9477        &self,
9478        file: &ProjectFile,
9479        raw_name: &str,
9480        kind: TargetKind,
9481        target: &CodeUnit,
9482    ) -> bool {
9483        let Some(normalized) = normalize_reference_name(raw_name) else {
9484            return false;
9485        };
9486        self.named_candidates_for_normalized(file, &normalized, kind)
9487            .into_iter()
9488            .any(|unit| {
9489                matches_kind_for_lookup(unit, kind)
9490                    && reference_matches_unit(&normalized, unit)
9491                    && same_visible_symbol(unit, target)
9492            })
9493    }
9494
9495    pub fn named_candidates(
9496        &self,
9497        file: &ProjectFile,
9498        raw_name: &str,
9499        kind: TargetKind,
9500    ) -> Vec<CodeUnit> {
9501        let Some(normalized) = normalize_reference_name(raw_name) else {
9502            return Vec::new();
9503        };
9504        self.named_candidates_for_normalized(file, &normalized, kind)
9505            .into_iter()
9506            .cloned()
9507            .collect()
9508    }
9509
9510    pub fn resolve_known_non_target(
9511        &self,
9512        file: &ProjectFile,
9513        raw_name: &str,
9514        kind: TargetKind,
9515        target: &CodeUnit,
9516    ) -> bool {
9517        let Some(normalized) = normalize_reference_name(raw_name) else {
9518            return false;
9519        };
9520        normalized.contains("::")
9521            && self
9522                .named_candidates_for_normalized(file, &normalized, kind)
9523                .into_iter()
9524                .any(|unit| {
9525                    matches_kind_for_lookup(unit, kind)
9526                        && reference_matches_unit(&normalized, unit)
9527                        && !same_visible_symbol(unit, target)
9528                })
9529    }
9530
9531    pub fn resolve_call_return_binding(
9532        &self,
9533        analyzer: &CppGraphSource<'_>,
9534        file: &ProjectFile,
9535        raw_name: &str,
9536        arity: usize,
9537        lexical_namespace: Option<&str>,
9538        direct_type: Option<&CodeUnit>,
9539    ) -> Option<CppScanBinding> {
9540        let normalized = normalize_reference_name(raw_name)?;
9541        let mut candidates = Vec::new();
9542        for function in
9543            self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
9544        {
9545            if cpp_callable_arity(analyzer, function).is_none_or(|known| known.accepts(arity))
9546                && !direct_type.is_some_and(|direct_type| {
9547                    self.callable_is_constructor_declaration(analyzer, function)
9548                        && type_owner_of(analyzer, function)
9549                            .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
9550                })
9551            {
9552                candidates.push(function.clone());
9553            }
9554        }
9555        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
9556        unanimous_return_binding(analyzer, self, file, &candidates)
9557    }
9558
9559    pub fn resolve_call_return_binding_without_arity(
9560        &self,
9561        analyzer: &CppGraphSource<'_>,
9562        file: &ProjectFile,
9563        raw_name: &str,
9564        lexical_namespace: Option<&str>,
9565        direct_type: Option<&CodeUnit>,
9566    ) -> (bool, Option<CppScanBinding>) {
9567        let Some(normalized) = normalize_reference_name(raw_name) else {
9568            return (false, None);
9569        };
9570        let mut candidates = self
9571            .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
9572            .into_iter()
9573            .filter(|function| {
9574                function.is_function()
9575                    && !direct_type.is_some_and(|direct_type| {
9576                        self.callable_is_constructor_declaration(analyzer, function)
9577                            && type_owner_of(analyzer, function)
9578                                .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
9579                    })
9580            })
9581            .cloned()
9582            .collect::<Vec<_>>();
9583        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
9584        let has_candidates = !candidates.is_empty();
9585        (
9586            has_candidates,
9587            unanimous_return_binding(analyzer, self, file, &candidates),
9588        )
9589    }
9590
9591    pub fn visible_identifier_candidates<'b>(
9592        &'b self,
9593        file: &ProjectFile,
9594        identifier: &str,
9595    ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
9596        self.visible_by_identifier
9597            .get(file)
9598            .and_then(|by_name| by_name.get(identifier))
9599            .into_iter()
9600            .flatten()
9601    }
9602
9603    /// Whether a grammar component is the direct spelling of `target`.
9604    ///
9605    /// Concrete template specializations are indexed with their arguments in
9606    /// `identifier`, while a reference's name component contains only the
9607    /// primary name. Argument matching remains the later resolver's job.
9608    pub fn type_reference_component_directly_names_target(
9609        &self,
9610        component: &str,
9611        target: &CodeUnit,
9612    ) -> bool {
9613        component == target.identifier()
9614            || self
9615                .cpp_template_metadata
9616                .get(target)
9617                .is_some_and(|metadata| component == metadata.primary_name)
9618    }
9619
9620    /// Return terminal reference names that can denote `target` from `file`.
9621    ///
9622    /// The indexed candidate table covers ordinary declarations and aliases;
9623    /// Parser-only aliases are tested lazily when their spelling is actually
9624    /// encountered in a scanned type node. Enumerating them here would parse
9625    /// every source in the include closure even when the target's direct name
9626    /// is the only spelling present in the file.
9627    pub fn visible_type_reference_component_names_for_target(
9628        &self,
9629        analyzer: &CppGraphSource<'_>,
9630        file: &ProjectFile,
9631        target: &CodeUnit,
9632    ) -> HashSet<String> {
9633        let mut names = HashSet::from_iter([target.identifier().to_string()]);
9634        if let Some(metadata) = self.cpp_template_metadata.get(target) {
9635            names.insert(metadata.primary_name.clone());
9636        }
9637
9638        if let Some(by_identifier) = self.visible_by_identifier.get(file) {
9639            for (identifier, candidates) in by_identifier {
9640                if candidates.iter().any(|candidate| {
9641                    (candidate.is_class()
9642                        && (same_visible_symbol(candidate, target)
9643                            || self.compatible_primary_template_redeclarations(candidate, target)))
9644                        || (declared_type_alias(analyzer, candidate)
9645                            && self.alias_candidate_structurally_reaches_target(
9646                                analyzer, file, candidate, target,
9647                            ))
9648                }) {
9649                    names.insert(identifier.clone());
9650                }
9651            }
9652        }
9653
9654        names
9655    }
9656
9657    pub fn indexed_structural_class_scope(
9658        &self,
9659        file: &ProjectFile,
9660        class: Node<'_>,
9661        source: &str,
9662    ) -> Option<Vec<String>> {
9663        let key = (file.clone(), class.start_byte(), class.end_byte());
9664        if let Some(cached) = self
9665            .indexed_structural_class_scopes
9666            .lock()
9667            .expect("C++ indexed structural-class scope cache poisoned")
9668            .get(&key)
9669            .cloned()
9670        {
9671            return cached;
9672        }
9673        let resolved = (|| {
9674            let name = class.child_by_field_name("name")?;
9675            let identifier = if name.kind() == "template_type" {
9676                node_text(name.child_by_field_name("name")?, source).to_string()
9677            } else {
9678                let mut components = Vec::new();
9679                append_cpp_name_components(name, source, &mut components)?;
9680                components.last()?.clone()
9681            };
9682            let visible = self
9683                .visible_identifier_candidates(file, &identifier)
9684                .cloned()
9685                .collect::<Vec<_>>();
9686            let mut visible = visible;
9687            for candidate in
9688                self.visible_by_file
9689                    .get(file)
9690                    .into_iter()
9691                    .flatten()
9692                    .filter(|candidate| {
9693                        self.cpp_template_metadata
9694                            .get(candidate)
9695                            .is_some_and(|metadata| metadata.primary_name == identifier)
9696                    })
9697            {
9698                if !visible
9699                    .iter()
9700                    .any(|existing| same_logical_symbol(existing, candidate))
9701                {
9702                    visible.push(candidate.clone());
9703                }
9704            }
9705            // Built once per call rather than per candidate; `cpp_source` rebuilds
9706            // the five-field source from the same `self.cpp` on every call.
9707            let cpp_source = self.cpp_source();
9708            let candidates = visible
9709                .iter()
9710                .filter(|candidate| {
9711                    candidate.source() == file
9712                        && candidate.is_class()
9713                        && !declared_type_alias(&cpp_source, candidate)
9714                        && self.cpp.ranges(candidate).iter().any(|range| {
9715                            range.start_byte <= class.start_byte()
9716                                && class.end_byte() <= range.end_byte
9717                        })
9718                })
9719                .collect::<Vec<_>>();
9720            let owner = if name.kind() == "template_type" {
9721                let expected = normalize_cpp_whitespace(node_text(name, source));
9722                let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
9723                let exact = candidates
9724                    .iter()
9725                    .copied()
9726                    .filter(|candidate| {
9727                        candidate
9728                            .fq()
9729                            .segments()
9730                            .iter()
9731                            .rev()
9732                            .find_map(|&segment| {
9733                                let (text, kind) = interner.resolve(segment);
9734                                matches!(
9735                                    kind,
9736                                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
9737                                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
9738                                )
9739                                .then_some(text)
9740                            })
9741                            .is_some_and(|text| text == expected)
9742                    })
9743                    .collect::<Vec<_>>();
9744                unique_logical_type_candidate(exact)
9745                    .or_else(|| unique_logical_type_candidate(candidates.clone()))?
9746            } else {
9747                unique_logical_type_candidate(candidates)?
9748            };
9749            Some(canonical_cpp_scope_components(&owner))
9750        })();
9751        self.indexed_structural_class_scopes
9752            .lock()
9753            .expect("C++ indexed structural-class scope cache poisoned")
9754            .insert(key, resolved.clone());
9755        resolved
9756    }
9757
9758    pub fn indexed_enclosing_owner_scope(
9759        &self,
9760        analyzer: &CppGraphSource<'_>,
9761        file: &ProjectFile,
9762        node: Node<'_>,
9763    ) -> Option<Vec<String>> {
9764        let anchor = std::iter::successors(Some(node), |current| current.parent())
9765            .find(|current| {
9766                matches!(
9767                    current.kind(),
9768                    "function_definition"
9769                        | "class_specifier"
9770                        | "struct_specifier"
9771                        | "union_specifier"
9772                )
9773            })
9774            .unwrap_or(node);
9775        let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
9776        if let Some(cached) = self
9777            .indexed_enclosing_owner_scopes
9778            .lock()
9779            .expect("C++ indexed enclosing-owner scope cache poisoned")
9780            .get(&key)
9781            .cloned()
9782        {
9783            return cached;
9784        }
9785        let resolved = (|| {
9786            let range = Range {
9787                start_byte: node.start_byte(),
9788                end_byte: node.end_byte(),
9789                start_line: node.start_position().row,
9790                end_line: node.end_position().row,
9791            };
9792            let start = analyzer.enclosing_code_unit(file, &range)?;
9793            let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
9794                start,
9795                |unit| self.cached_precise_parent_of(analyzer, unit),
9796            )
9797            .find(|unit| {
9798                unit.is_class()
9799                    && !analyzer
9800                        .type_alias_provider()
9801                        .is_some_and(|provider| provider.is_type_alias(unit))
9802            })?;
9803            Some(canonical_cpp_scope_components(&owner))
9804        })();
9805        self.indexed_enclosing_owner_scopes
9806            .lock()
9807            .expect("C++ indexed enclosing-owner scope cache poisoned")
9808            .insert(key, resolved.clone());
9809        resolved
9810    }
9811
9812    fn cached_precise_parent_of(
9813        &self,
9814        analyzer: &CppGraphSource<'_>,
9815        code_unit: &CodeUnit,
9816    ) -> Option<CodeUnit> {
9817        if let Some(cached) = self
9818            .precise_parent_cache
9819            .lock()
9820            .expect("C++ precise-parent cache poisoned")
9821            .get(code_unit)
9822            .cloned()
9823        {
9824            return cached;
9825        }
9826        let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
9827        self.precise_parent_cache
9828            .lock()
9829            .expect("C++ precise-parent cache poisoned")
9830            .insert(code_unit.clone(), resolved.clone());
9831        resolved
9832    }
9833
9834    pub fn callable_is_constructor_declaration(
9835        &self,
9836        analyzer: &CppGraphSource<'_>,
9837        candidate: &CodeUnit,
9838    ) -> bool {
9839        if !candidate.is_function() {
9840            return false;
9841        }
9842        if analyzer
9843            .declaration_source_properties(candidate)
9844            .is_some_and(|facts| facts.iter().any(|fact| fact.callable_is_constructor))
9845        {
9846            return true;
9847        }
9848        let metadata = analyzer.signature_metadata(candidate);
9849        !metadata.is_empty()
9850            && metadata
9851                .iter()
9852                .all(|signature| signature.return_type_text().is_none())
9853    }
9854
9855    /// Whether a callable declaration is a class-template deduction guide.
9856    ///
9857    /// Tree-sitter represents `Box(T) -> Box<T>;` as a declaration with no
9858    /// type field whose function declarator owns a trailing return type. This
9859    /// structured shape distinguishes a guide from both a constructor (no
9860    /// trailing return) and an ordinary trailing-return function (an `auto`
9861    /// type field).
9862    pub fn callable_is_deduction_guide_declaration(
9863        &self,
9864        analyzer: &CppGraphSource<'_>,
9865        candidate: &CodeUnit,
9866    ) -> bool {
9867        callable_declaration_has_fact(analyzer, candidate, |fact| fact.callable_is_deduction_guide)
9868    }
9869
9870    /// Whether a callable occurrence is directly wrapped by a C++ template
9871    /// declaration. This deliberately inspects declaration syntax instead of
9872    /// inferring template status from the rendered signature.
9873    pub fn callable_is_template_declaration(
9874        &self,
9875        analyzer: &CppGraphSource<'_>,
9876        candidate: &CodeUnit,
9877    ) -> bool {
9878        callable_declaration_has_fact(analyzer, candidate, |fact| fact.callable_is_template)
9879    }
9880
9881    pub fn type_name_candidates<'b>(
9882        &'b self,
9883        file: &ProjectFile,
9884        normalized: &str,
9885    ) -> Vec<&'b CodeUnit> {
9886        self.candidate_units(file, normalized, TargetKind::Type)
9887    }
9888
9889    pub fn visible_members_for_owner_name<'b>(
9890        &'b self,
9891        file: &ProjectFile,
9892        owner: &CodeUnit,
9893        name: &str,
9894    ) -> Vec<&'b CodeUnit> {
9895        self.visible_identifier_candidates(file, name)
9896            .filter(|unit| {
9897                // Structured owner pop on the unit's own `fq()` (shared with
9898                // `CodeUnitIndex::parent_of`), not a re-split of its rendered fqn
9899                // string.
9900                brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
9901                    .is_some_and(|parent| parent == owner.fq_name())
9902            })
9903            .collect()
9904    }
9905
9906    pub fn visible_member_for_owner_name(
9907        &self,
9908        file: &ProjectFile,
9909        owner: &CodeUnit,
9910        name: &str,
9911    ) -> VisibleMemberResolution {
9912        let candidates = self.visible_members_for_owner_name(file, owner, name);
9913        let mut callables = Vec::new();
9914        let mut non_callable = None;
9915        for candidate in candidates {
9916            if candidate.is_function() {
9917                callables.push(candidate.clone());
9918            } else if non_callable.is_none() {
9919                non_callable = Some(candidate.clone());
9920            }
9921        }
9922        match (callables.is_empty(), non_callable) {
9923            (false, None) => VisibleMemberResolution::Callable(callables),
9924            (true, Some(_)) => VisibleMemberResolution::NonCallable,
9925            (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
9926            (true, None) => VisibleMemberResolution::Missing,
9927        }
9928    }
9929
9930    fn field_declared_type_fact(
9931        &self,
9932        analyzer: &CppGraphSource<'_>,
9933        field: &CodeUnit,
9934    ) -> Option<DeclaredFieldTypeFact> {
9935        if let Some(cached) = self
9936            .field_type_facts
9937            .lock()
9938            .expect("C++ field type fact cache poisoned")
9939            .get(field)
9940            .cloned()
9941        {
9942            return cached;
9943        }
9944        let decoded = decode_field_declared_type_fact(analyzer, field);
9945        self.field_type_facts
9946            .lock()
9947            .expect("C++ field type fact cache poisoned")
9948            .insert(field.clone(), decoded.clone());
9949        decoded
9950    }
9951
9952    fn structured_alias_target(
9953        &self,
9954        analyzer: &CppGraphSource<'_>,
9955        unit: &CodeUnit,
9956    ) -> Option<StructuredAliasTarget> {
9957        if let Some(cached) = self
9958            .structured_alias_targets
9959            .lock()
9960            .expect("C++ structured alias target cache poisoned")
9961            .get(unit)
9962            .cloned()
9963        {
9964            return cached;
9965        }
9966        let decoded = decode_structured_alias_target(analyzer, unit);
9967        self.structured_alias_targets
9968            .lock()
9969            .expect("C++ structured alias target cache poisoned")
9970            .insert(unit.clone(), decoded.clone());
9971        decoded
9972    }
9973
9974    pub fn type_candidates<'b>(
9975        &'b self,
9976        file: &ProjectFile,
9977        normalized: &str,
9978    ) -> Vec<&'b CodeUnit> {
9979        // A bare name reaches a function-local class only through block-scope
9980        // lookup at a reference inside its function
9981        // (`function_local_type_resolution`), never through the visible-name
9982        // table. Its canonical qualified spelling still matches below.
9983        let qualified = normalized.contains("::");
9984        let mut candidates = self
9985            .candidate_units(file, normalized, TargetKind::Type)
9986            .into_iter()
9987            .filter(|unit| {
9988                unit.kind() == CodeUnitType::Class || declared_type_alias(&self.cpp_source(), unit)
9989            })
9990            .filter(|unit| qualified || !is_function_local_unit(unit))
9991            .collect::<Vec<_>>();
9992        dedup_unit_refs(&mut candidates);
9993        candidates
9994    }
9995
9996    pub fn named_candidates_for_normalized<'b>(
9997        &'b self,
9998        file: &ProjectFile,
9999        normalized: &str,
10000        kind: TargetKind,
10001    ) -> Vec<&'b CodeUnit> {
10002        let mut candidates = self
10003            .candidate_units(file, normalized, kind)
10004            .into_iter()
10005            .filter(|unit| {
10006                matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
10007            })
10008            .collect::<Vec<_>>();
10009        dedup_unit_refs(&mut candidates);
10010        candidates
10011    }
10012
10013    pub fn candidate_units<'b>(
10014        &'b self,
10015        file: &ProjectFile,
10016        normalized: &str,
10017        kind: TargetKind,
10018    ) -> Vec<&'b CodeUnit> {
10019        if normalized.contains("::") {
10020            // `normalized` comes from `normalize_cpp_reference_text`, which
10021            // truncates at the first `(`/`{`/`<`, leaving a plain `::`-joined
10022            // qualified-id with no embedded `.`/`/`/`\` and operator tokens
10023            // kept intact by the shared splitter's operator merge — the same
10024            // domain `cpp_reference_fqn_candidates` below already parses with
10025            // the shared splitter. Re-tokenizing and taking the last segment
10026            // reproduces `rsplit("::").find(non-empty)`'s terminal-component
10027            // scan exactly.
10028            let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10029                brokk_bifrost_core::analyzer::Language::Cpp,
10030                normalized,
10031            );
10032            let Some(identifier) = parts.last() else {
10033                return Vec::new();
10034            };
10035            let fqns = cpp_reference_fqn_candidates_from_parts(&parts, kind);
10036            return self
10037                .visible_identifier_candidates(file, identifier)
10038                .filter(|unit| {
10039                    #[cfg(any(test, feature = "test-support"))]
10040                    self.qualified_candidate_inspections
10041                        .fetch_add(1, Ordering::Relaxed);
10042                    fqns.iter().any(|fqn| unit.fq_name() == *fqn)
10043                        || canonical_cpp_name_matches(unit, normalized)
10044                })
10045                .collect();
10046        }
10047        self.visible_identifier_candidates(file, normalized)
10048            .collect()
10049    }
10050
10051    #[cfg(any(test, feature = "test-support"))]
10052    pub fn reset_qualified_candidate_inspections(&self) {
10053        self.qualified_candidate_inspections
10054            .store(0, Ordering::Relaxed);
10055    }
10056
10057    #[cfg(any(test, feature = "test-support"))]
10058    pub fn qualified_candidate_inspections(&self) -> usize {
10059        self.qualified_candidate_inspections.load(Ordering::Relaxed)
10060    }
10061
10062    #[cfg(any(test, feature = "test-support"))]
10063    pub fn visibility_identifier_lookup_count(&self) -> usize {
10064        self.visibility_identifier_lookup_count
10065    }
10066
10067    #[cfg(any(test, feature = "test-support"))]
10068    pub fn visibility_identifier_batch_count(&self) -> usize {
10069        self.visibility_identifier_batch_count
10070    }
10071
10072    #[cfg(any(test, feature = "test-support"))]
10073    pub fn reset_target_preserving_type_resolution_count(&self) {
10074        self.target_preserving_type_resolution_count
10075            .store(0, Ordering::Relaxed);
10076    }
10077
10078    #[cfg(any(test, feature = "test-support"))]
10079    pub fn target_preserving_type_resolution_count(&self) -> usize {
10080        self.target_preserving_type_resolution_count
10081            .load(Ordering::Relaxed)
10082    }
10083
10084    #[cfg(any(test, feature = "test-support"))]
10085    pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
10086        self.visible_parser_alias_name_set_build_count
10087            .load(Ordering::Relaxed)
10088    }
10089}
10090
10091fn callable_declaration_has_fact(
10092    analyzer: &CppGraphSource<'_>,
10093    candidate: &CodeUnit,
10094    matches: impl Fn(&brokk_bifrost_core::analyzer::cpp_facts::CppDeclarationSourceFact) -> bool,
10095) -> bool {
10096    candidate.is_function()
10097        && analyzer
10098            .declaration_source_properties(candidate)
10099            .is_some_and(|facts| facts.iter().any(matches))
10100}
10101
10102#[derive(Default)]
10103struct IncludeGraph {
10104    targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
10105}
10106
10107impl IncludeGraph {
10108    fn extend_with<F>(
10109        &mut self,
10110        root: &ProjectFile,
10111        cancellation: Option<&CancellationToken>,
10112        targets_for: &mut F,
10113    ) where
10114        F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
10115    {
10116        let mut stack = vec![root.clone()];
10117        while let Some(file) = stack.pop() {
10118            if cancellation.is_some_and(CancellationToken::is_cancelled) {
10119                break;
10120            }
10121            if self.targets_by_file.contains_key(&file) {
10122                continue;
10123            }
10124            let targets = targets_for(&file);
10125            stack.extend(targets.iter().cloned());
10126            self.targets_by_file.insert(file, targets);
10127        }
10128    }
10129
10130    fn files(&self) -> impl Iterator<Item = &ProjectFile> {
10131        self.targets_by_file.keys()
10132    }
10133
10134    fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
10135        self.targets_by_file
10136            .get(file)
10137            .map(Vec::as_slice)
10138            .unwrap_or_default()
10139    }
10140
10141    fn reachable_files(
10142        &self,
10143        root: &ProjectFile,
10144        cancellation: Option<&CancellationToken>,
10145    ) -> HashSet<ProjectFile> {
10146        let mut pending = vec![root.clone()];
10147        let mut visited = HashSet::default();
10148        while let Some(file) = pending.pop() {
10149            if cancellation.is_some_and(CancellationToken::is_cancelled) {
10150                break;
10151            }
10152            if visited.insert(file.clone()) {
10153                pending.extend(self.targets(&file).iter().cloned());
10154            }
10155        }
10156        visited
10157    }
10158}
10159
10160fn build_bounded_visible_declarations(
10161    cpp: &dyn CppSource,
10162    token: QueryToken<'_>,
10163    analyzer: &CppGraphSource<'_>,
10164    roots: &HashSet<ProjectFile>,
10165    visible_sources: &HashMap<ProjectFile, HashSet<ProjectFile>>,
10166    cancellation: Option<&CancellationToken>,
10167    stats: &mut BoundedVisibilityStats,
10168) -> HashMap<ProjectFile, HashSet<CodeUnit>> {
10169    let mut candidates_by_identifier = HashMap::default();
10170    let mut declarations_by_source_and_reading: HashMap<
10171        (ProjectFile, bool),
10172        BoundedVisibilityDeclarations,
10173    > = HashMap::default();
10174    let mut dependency_names_by_unit: HashMap<CodeUnit, HashSet<String>> = HashMap::default();
10175    roots
10176        .iter()
10177        .map(|root| {
10178            let reading_is_c = analyzer.reference_uses_c_semantics(root);
10179            let root_declarations = declarations_by_source_and_reading
10180                .entry((root.clone(), reading_is_c))
10181                .or_insert_with(|| {
10182                    bounded_visibility_declarations(cpp, analyzer, root, reading_is_c, stats)
10183                });
10184            let mut visible = root_declarations
10185                .all
10186                .iter()
10187                .cloned()
10188                .collect::<HashSet<_>>();
10189            let mut pending_names = HashSet::default();
10190            if let Some(prepared) = cpp.prepared_syntax(token, root) {
10191                // One cursor for the whole file walk: `named_child(index)`
10192                // re-steps the sibling list from the first child on every
10193                // access, which made this whole-file scan quadratic in the
10194                // fan-out of each node (#3097).
10195                let mut cursor = prepared.tree().walk();
10196                let mut pending_nodes = vec![prepared.tree().root_node()];
10197                while let Some(node) = pending_nodes.pop() {
10198                    if matches!(
10199                        node.kind(),
10200                        "identifier"
10201                            | "type_identifier"
10202                            | "field_identifier"
10203                            | "namespace_identifier"
10204                    ) {
10205                        pending_names.insert(node_text(node, prepared.source()).to_string());
10206                    }
10207                    if node.kind() == "preproc_arg" {
10208                        for reference in
10209                            object_macro_replacement_type_references(node, prepared.source())
10210                        {
10211                            pending_names.extend(reference.components);
10212                        }
10213                    }
10214                    pending_nodes.extend(node.named_children(&mut cursor));
10215                }
10216            }
10217            stats.root_names += pending_names.len();
10218            let mut completed_names = HashSet::default();
10219            while !pending_names.is_empty() {
10220                stats.rounds += 1;
10221                let round_names = std::mem::take(&mut pending_names);
10222                let mut requested_names_by_source: HashMap<ProjectFile, HashSet<String>> =
10223                    HashMap::default();
10224                let mut identifiers = Vec::new();
10225                for identifier in round_names {
10226                    if !completed_names.insert(identifier.clone())
10227                        || cancellation.is_some_and(CancellationToken::is_cancelled)
10228                    {
10229                        continue;
10230                    }
10231                    identifiers.push(identifier);
10232                }
10233                let missing_identifiers = identifiers
10234                    .iter()
10235                    .filter(|identifier| !candidates_by_identifier.contains_key(*identifier))
10236                    .cloned()
10237                    .collect::<HashSet<_>>();
10238                if !missing_identifiers.is_empty() {
10239                    let lookup_started = Instant::now();
10240                    let mut candidates = cpp
10241                        .visibility_identifier_candidates_batch(&missing_identifiers, cancellation);
10242                    stats.lookup_elapsed += lookup_started.elapsed();
10243                    stats.identifier_lookups += missing_identifiers.len();
10244                    stats.identifier_batches += 1;
10245                    if cancellation.is_some_and(CancellationToken::is_cancelled) {
10246                        break;
10247                    }
10248                    for identifier in missing_identifiers {
10249                        let units = candidates.remove(&identifier).unwrap_or_default();
10250                        let candidate_count = units.len();
10251                        let candidate_sources = units
10252                            .into_iter()
10253                            .map(|unit| unit.source().clone())
10254                            .collect::<HashSet<_>>();
10255                        candidates_by_identifier
10256                            .insert(identifier, (candidate_sources, candidate_count));
10257                    }
10258                }
10259                for identifier in identifiers {
10260                    let (candidate_sources, candidate_count) = candidates_by_identifier
10261                        .get(&identifier)
10262                        .expect("every missing identifier was inserted after the batch lookup");
10263                    stats.candidate_units += *candidate_count;
10264                    for source in candidate_sources.iter().cloned() {
10265                        if source != *root
10266                            && visible_sources
10267                                .get(root)
10268                                .is_some_and(|files| files.contains(&source))
10269                        {
10270                            requested_names_by_source
10271                                .entry(source)
10272                                .or_default()
10273                                .insert(identifier.clone());
10274                        }
10275                    }
10276                }
10277                stats.candidate_sources += requested_names_by_source.len();
10278                for (source, requested_names) in requested_names_by_source {
10279                    let declarations = declarations_by_source_and_reading
10280                        .entry((source.clone(), reading_is_c))
10281                        .or_insert_with(|| {
10282                            bounded_visibility_declarations(
10283                                cpp,
10284                                analyzer,
10285                                &source,
10286                                reading_is_c,
10287                                stats,
10288                            )
10289                        });
10290                    let mut selected = HashSet::default();
10291                    for name in requested_names {
10292                        if let Some(units) = declarations.by_identifier.get(&name) {
10293                            selected.extend(units.iter().cloned());
10294                        }
10295                    }
10296                    for unit in selected {
10297                        stats.selected_units += 1;
10298                        let dependency_names = dependency_names_by_unit
10299                            .entry(unit.clone())
10300                            .or_insert_with(|| {
10301                                let facts_started = Instant::now();
10302                                let mut dependency_names = HashSet::default();
10303                                if let Some(facts) = analyzer.declaration_source_properties(&unit) {
10304                                    for fact in facts {
10305                                        for name in fact.dependency_type_names {
10306                                            stats.dependency_source_names += 1;
10307                                            #[cfg(any(test, feature = "test-support"))]
10308                                            BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT
10309                                                .with(|count| count.set(count.get() + 1));
10310                                            dependency_names.insert(name);
10311                                        }
10312                                    }
10313                                    stats.dependency_source_elapsed += facts_started.elapsed();
10314                                }
10315                                dependency_names
10316                            });
10317                        for name in dependency_names.iter() {
10318                            if !completed_names.contains(name) && pending_names.insert(name.clone())
10319                            {
10320                                stats.dependency_names += 1;
10321                            }
10322                        }
10323                        visible.insert(unit);
10324                    }
10325                }
10326            }
10327            (root.clone(), visible)
10328        })
10329        .collect()
10330}
10331
10332struct BoundedVisibilityDeclarations {
10333    all: Vec<CodeUnit>,
10334    by_identifier: HashMap<String, Vec<CodeUnit>>,
10335}
10336
10337fn bounded_visibility_declarations(
10338    cpp: &dyn CppSource,
10339    analyzer: &CppGraphSource<'_>,
10340    file: &ProjectFile,
10341    c_semantics: bool,
10342    stats: &mut BoundedVisibilityStats,
10343) -> BoundedVisibilityDeclarations {
10344    let declarations_started = Instant::now();
10345    let all = bounded_visibility_declarations_in_reading(analyzer, file, c_semantics)
10346        .into_iter()
10347        .collect::<Vec<_>>();
10348    stats.declaration_elapsed += declarations_started.elapsed();
10349    stats.declaration_reads += 1;
10350    stats.declaration_units += all.len();
10351
10352    let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
10353    for unit in &all {
10354        by_identifier
10355            .entry(unit.identifier().to_string())
10356            .or_default()
10357            .push(unit.clone());
10358        if unit.is_class()
10359            && let Some(metadata) = cpp.template_metadata(unit)
10360            && metadata.primary_name != unit.identifier()
10361        {
10362            by_identifier
10363                .entry(metadata.primary_name)
10364                .or_default()
10365                .push(unit.clone());
10366        }
10367    }
10368    BoundedVisibilityDeclarations { all, by_identifier }
10369}
10370
10371#[derive(Default)]
10372struct BoundedVisibilityStats {
10373    rounds: usize,
10374    root_names: usize,
10375    identifier_lookups: usize,
10376    identifier_batches: usize,
10377    candidate_units: usize,
10378    candidate_sources: usize,
10379    declaration_reads: usize,
10380    declaration_units: usize,
10381    selected_units: usize,
10382    dependency_source_names: usize,
10383    dependency_names: usize,
10384    lookup_elapsed: Duration,
10385    declaration_elapsed: Duration,
10386    dependency_source_elapsed: Duration,
10387}
10388
10389fn bounded_visibility_declarations_in_reading(
10390    analyzer: &CppGraphSource<'_>,
10391    file: &ProjectFile,
10392    c_semantics: bool,
10393) -> BTreeSet<CodeUnit> {
10394    #[cfg(any(test, feature = "test-support"))]
10395    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(count.get() + 1));
10396    analyzer.declarations_in_reading(file, c_semantics)
10397}
10398
10399#[cfg(any(test, feature = "test-support"))]
10400pub fn reset_bounded_visibility_declaration_read_count_for_test() {
10401    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(0));
10402}
10403
10404#[cfg(any(test, feature = "test-support"))]
10405pub fn bounded_visibility_declaration_read_count_for_test() -> usize {
10406    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(Cell::get)
10407}
10408
10409#[cfg(any(test, feature = "test-support"))]
10410pub fn reset_bounded_visibility_dependency_ast_node_count_for_test() {
10411    BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(|count| count.set(0));
10412}
10413
10414#[cfg(any(test, feature = "test-support"))]
10415pub fn bounded_visibility_dependency_ast_node_count_for_test() -> usize {
10416    BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(Cell::get)
10417}
10418
10419/// Admit the class that an out-of-line definition proves is in scope.
10420///
10421/// `Owner::member(...) { ... }` in a file is structured proof that `Owner`
10422/// names a class-like entity in that file's scope: a member declaration can
10423/// live in a file other than its class's only when it is written out of line.
10424/// A file a build concatenates rather than compiles carries no `#include` edge
10425/// to the header declaring `Owner` -- google/wuffs
10426/// `internal/cgen/auxiliary/image.cc` defines
10427/// `DecodeImageResult::DecodeImageResult` and never includes `image.hh` -- so
10428/// every unqualified member and constructor reference in it had no candidate at
10429/// all (#1832).
10430///
10431/// The evidence is the indexed declaration's own owner name, taken from its
10432/// `FqName`, so this stays a structured answer rather than a text fallback.
10433/// Only an owner the file cannot already see is admitted: that is what keeps a
10434/// header declaring its own class from additionally seeing every same-named
10435/// class in the workspace, and it makes the pass free for the ordinary file
10436/// whose owners are all visible.
10437#[derive(Default)]
10438struct OutOfLineOwnerBindingStats {
10439    unseen_owners: usize,
10440    definition_lookups: usize,
10441    admitted: usize,
10442}
10443
10444fn extend_with_out_of_line_owner_bindings(
10445    cpp: &dyn CppSource,
10446    visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
10447) -> OutOfLineOwnerBindingStats {
10448    let mut stats = OutOfLineOwnerBindingStats::default();
10449    for (file, visible) in visible_by_file.iter_mut() {
10450        // The include-closure walk seeds every root with its own declarations,
10451        // so the file's members are already here; re-reading them from the
10452        // analyzer would pay for the same declaration set twice.
10453        let mut unseen_owners: HashSet<String> = visible
10454            .iter()
10455            .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
10456            .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
10457            .collect();
10458        if unseen_owners.is_empty() {
10459            continue;
10460        }
10461        for unit in visible.iter().filter(|unit| unit.is_class()) {
10462            unseen_owners.remove(&unit.fq_name());
10463        }
10464        stats.unseen_owners += unseen_owners.len();
10465        stats.definition_lookups += unseen_owners.len();
10466        let admitted = unseen_owners
10467            .iter()
10468            .flat_map(|owner| cpp.definitions(owner))
10469            .filter(CodeUnit::is_class)
10470            .collect::<Vec<_>>();
10471        stats.admitted += admitted.len();
10472        visible.extend(admitted);
10473    }
10474    stats
10475}
10476
10477pub enum VisibleMemberResolution {
10478    Callable(Vec<CodeUnit>),
10479    NonCallable,
10480    AmbiguousKind,
10481    Missing,
10482}
10483
10484#[derive(Clone)]
10485pub enum EnclosingMemberOwnerResolution {
10486    Owner(CodeUnit),
10487    Ambiguous,
10488    Missing,
10489}
10490
10491pub fn resolve_declaring_member_owner(
10492    analyzer: &CppGraphSource<'_>,
10493    visibility: &VisibilityIndex<'_>,
10494    file: &ProjectFile,
10495    receiver_owner: &CodeUnit,
10496    member_name: &str,
10497) -> EnclosingMemberOwnerResolution {
10498    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
10499        return EnclosingMemberOwnerResolution::Missing;
10500    };
10501    let Some(receiver_owner) =
10502        visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
10503    else {
10504        return EnclosingMemberOwnerResolution::Ambiguous;
10505    };
10506    let resolve_level = |frontier: &[CodeUnit]| {
10507        let mut member_owners = Vec::new();
10508        for raw_owner in frontier {
10509            let Some(owner) =
10510                visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
10511            else {
10512                return EnclosingMemberOwnerResolution::Ambiguous;
10513            };
10514            for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
10515                // A type nested in an owner shares the owner's member-name
10516                // index, but it cannot be the value receiver of `owner.name`.
10517                // Keep value members here so an anonymous aggregate field
10518                // does not become ambiguous with its promoted receiver type.
10519                if !member.is_field() && !member.is_function() {
10520                    continue;
10521                }
10522                let Some(member_owner) = type_owner_of(analyzer, member) else {
10523                    return EnclosingMemberOwnerResolution::Ambiguous;
10524                };
10525                if !member_owners
10526                    .iter()
10527                    .any(|existing| same_visible_symbol(existing, &member_owner))
10528                {
10529                    member_owners.push(member_owner);
10530                }
10531            }
10532        }
10533        match member_owners.len() {
10534            0 => EnclosingMemberOwnerResolution::Missing,
10535            1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
10536            _ => EnclosingMemberOwnerResolution::Ambiguous,
10537        }
10538    };
10539    // The first declaration on each structured base path hides deeper names,
10540    // regardless of whether its callable overload is applicable at a particular
10541    // call site. Applicability is checked only after this owner is established.
10542    let direct = resolve_level(std::slice::from_ref(&receiver_owner));
10543    if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
10544        return direct;
10545    }
10546    let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
10547    let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
10548    let mut path_matches = Vec::new();
10549    while let Some(raw_owner) = stack.pop() {
10550        let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
10551        else {
10552            return EnclosingMemberOwnerResolution::Ambiguous;
10553        };
10554        // Persisted hierarchy edges do not encode virtual-base or base-subobject paths.
10555        // Propagate at most two occurrences of each owner: that preserves the distinction
10556        // between one and multiple resolving base paths without exponential diamond walks.
10557        let propagated = propagated_counts.entry(owner.clone()).or_default();
10558        if *propagated == 2 {
10559            continue;
10560        }
10561        *propagated += 1;
10562        match resolve_level(std::slice::from_ref(&owner)) {
10563            EnclosingMemberOwnerResolution::Owner(owner) => {
10564                path_matches.push(owner);
10565                if path_matches.len() == 2 {
10566                    return EnclosingMemberOwnerResolution::Ambiguous;
10567                }
10568            }
10569            EnclosingMemberOwnerResolution::Ambiguous => {
10570                return EnclosingMemberOwnerResolution::Ambiguous;
10571            }
10572            EnclosingMemberOwnerResolution::Missing => {
10573                stack.extend(hierarchy.get_direct_ancestors(&owner));
10574            }
10575        }
10576    }
10577    match path_matches.len() {
10578        0 => EnclosingMemberOwnerResolution::Missing,
10579        1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
10580        _ => unreachable!("base-path matches are capped at one before returning"),
10581    }
10582}
10583
10584/// Resolve the declaring owner of a callable after applying a member
10585/// `using <Base>::<member>;` declaration to one exact call arity.
10586///
10587/// Ordinary member lookup is intentionally name-based: the first class that
10588/// declares a name hides the same name on deeper bases. A member
10589/// using-declaration is the one exception. When none of the declarations on
10590/// that first owner accepts the call arity, it can reintroduce an applicable
10591/// overload from the named base. If a declaration on the first owner does
10592/// accept the arity, argument types would be needed to choose between it and
10593/// a same-arity introduced overload, so this resolver conservatively keeps the
10594/// ordinary owner (#1835/#1843).
10595///
10596/// The caller supplies ordinary name-based owner resolution so a file scan can
10597/// reuse its existing owner cache before applying this callable-only exception.
10598pub fn resolve_declaring_callable_owner(
10599    analyzer: &CppGraphSource<'_>,
10600    visibility: &VisibilityIndex<'_>,
10601    file: &ProjectFile,
10602    ordinary: EnclosingMemberOwnerResolution,
10603    member_name: &str,
10604    call_arity: usize,
10605) -> EnclosingMemberOwnerResolution {
10606    let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
10607        return ordinary;
10608    };
10609    if visibility
10610        .visible_members_for_owner_name(file, ordinary_owner, member_name)
10611        .into_iter()
10612        .any(|unit| {
10613            unit.is_function()
10614                && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(call_arity))
10615        })
10616    {
10617        return ordinary;
10618    }
10619
10620    let mut pending = match member_using_declaration_bases(
10621        analyzer,
10622        visibility,
10623        file,
10624        ordinary_owner,
10625        member_name,
10626    ) {
10627        Ok(bases) => bases,
10628        Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
10629    };
10630    let mut visited = HashSet::default();
10631    let mut introduced_owners = Vec::new();
10632    while let Some(owner) = pending.pop() {
10633        if !visited.insert(owner.clone()) {
10634            continue;
10635        }
10636        let accepts_arity = visibility
10637            .visible_members_for_owner_name(file, &owner, member_name)
10638            .into_iter()
10639            .any(|unit| {
10640                unit.is_function()
10641                    && cpp_callable_arity(analyzer, unit)
10642                        .is_none_or(|known| known.accepts(call_arity))
10643            });
10644        if accepts_arity {
10645            if !introduced_owners
10646                .iter()
10647                .any(|existing| same_visible_symbol(existing, &owner))
10648            {
10649                introduced_owners.push(owner);
10650            }
10651            continue;
10652        }
10653        match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
10654            Ok(bases) => pending.extend(bases),
10655            Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
10656        }
10657    }
10658    match introduced_owners.as_slice() {
10659        [] => ordinary,
10660        [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
10661        _ => EnclosingMemberOwnerResolution::Ambiguous,
10662    }
10663}
10664
10665fn member_using_declaration_bases(
10666    analyzer: &CppGraphSource<'_>,
10667    visibility: &VisibilityIndex<'_>,
10668    file: &ProjectFile,
10669    owner: &CodeUnit,
10670    member_name: &str,
10671) -> Result<Vec<CodeUnit>, ()> {
10672    let Some(facts) = analyzer.declaration_source_properties(owner) else {
10673        return Err(());
10674    };
10675    let scopes: Vec<_> = facts
10676        .iter()
10677        .flat_map(|fact| &fact.member_usings)
10678        .filter(|using| using.member == member_name)
10679        .map(|using| using.scope.join("::"))
10680        .collect();
10681    if scopes.is_empty() {
10682        return Ok(Vec::new());
10683    }
10684    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
10685        return Ok(Vec::new());
10686    };
10687    let mut bases = Vec::new();
10688    for raw_ancestor in hierarchy.get_ancestors(owner) {
10689        let Some(ancestor) =
10690            visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
10691        else {
10692            return Err(());
10693        };
10694        let qualified = cpp_name_for(&ancestor);
10695        if scopes
10696            .iter()
10697            .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
10698            && !bases
10699                .iter()
10700                .any(|existing| same_visible_symbol(existing, &ancestor))
10701        {
10702            bases.push(ancestor);
10703        }
10704    }
10705    Ok(bases)
10706}
10707
10708pub fn lexical_component_tiers<'a>(
10709    components: &'a [String],
10710    global: bool,
10711    lexical_scope: &'a [String],
10712) -> impl Iterator<Item = Vec<String>> + 'a {
10713    let first_prefix_len = if global { 0 } else { lexical_scope.len() };
10714    (0..=first_prefix_len).rev().map(move |prefix_len| {
10715        let mut qualified = Vec::with_capacity(prefix_len + components.len());
10716        qualified.extend_from_slice(&lexical_scope[..prefix_len]);
10717        qualified.extend_from_slice(components);
10718        qualified
10719    })
10720}
10721
10722pub fn build_visible_identifier_index(
10723    analyzer: &CppGraphSource<'_>,
10724    visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
10725    visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
10726    global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
10727) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
10728    let mut out = HashMap::default();
10729    for (file, visible) in visible_by_file {
10730        let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
10731        for unit in visible {
10732            if unit.is_field()
10733                && !visible_source_files_by_root
10734                    .get(file)
10735                    .is_some_and(|sources| sources.contains(unit.source()))
10736                && cpp_global_field_has_internal_linkage_cached(
10737                    analyzer,
10738                    global_field_internal_linkage,
10739                    unit,
10740                )
10741            {
10742                continue;
10743            }
10744            by_identifier
10745                .entry(unit.identifier().to_string())
10746                .or_default()
10747                .push(unit.clone());
10748        }
10749        for units in by_identifier.values_mut() {
10750            sort_lookup_units(units);
10751            units.dedup();
10752        }
10753        out.insert(file.clone(), by_identifier);
10754    }
10755    out
10756}
10757
10758fn sort_lookup_units(units: &mut [CodeUnit]) {
10759    units.sort_by(|left, right| {
10760        left.fq_name()
10761            .cmp(&right.fq_name())
10762            .then_with(|| left.signature().cmp(&right.signature()))
10763            .then_with(|| left.source().cmp(right.source()))
10764            .then_with(|| left.kind().cmp(&right.kind()))
10765            .then_with(|| {
10766                left.package_segment_count()
10767                    .cmp(&right.package_segment_count())
10768            })
10769            .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
10770            .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
10771    });
10772}
10773
10774fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
10775    let interner = segment_interner();
10776    for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
10777        let (left_text, left_kind) = interner.resolve(left_id);
10778        let (right_text, right_kind) = interner.resolve(right_id);
10779        let order = left_text
10780            .cmp(right_text)
10781            .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
10782        if order != CmpOrdering::Equal {
10783            return order;
10784        }
10785    }
10786    left.len().cmp(&right.len())
10787}
10788
10789const fn segment_kind_order(kind: SegmentKind) -> u8 {
10790    match kind {
10791        SegmentKind::Path => 0,
10792        SegmentKind::Package => 1,
10793        SegmentKind::Type => 2,
10794        SegmentKind::Companion => 3,
10795        SegmentKind::Nested => 4,
10796        SegmentKind::Member => 5,
10797        SegmentKind::Unknown => 6,
10798    }
10799}
10800
10801fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
10802    *units = brokk_bifrost_core::analyzer::usages::inverted_edges::dedup_preserving_order(
10803        std::mem::take(units),
10804    );
10805}
10806
10807pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
10808    // Same domain as `candidate_units` above: `reference` is a plain
10809    // `::`-joined qualified-id with operator tokens kept intact by the shared
10810    // splitter's operator merge.
10811    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10812        brokk_bifrost_core::analyzer::Language::Cpp,
10813        reference,
10814    );
10815    cpp_reference_fqn_candidates_from_parts(&parts, kind)
10816}
10817
10818fn cpp_reference_fqn_candidates_from_parts(parts: &[String], kind: TargetKind) -> Vec<String> {
10819    if parts.is_empty() {
10820        return Vec::new();
10821    }
10822
10823    let mut candidates = Vec::new();
10824    for package_len in 0..parts.len() {
10825        let package = parts[..package_len].join("::");
10826        let rest = &parts[package_len..];
10827        if rest.is_empty() {
10828            continue;
10829        }
10830        match kind {
10831            TargetKind::Type | TargetKind::Constructor => {
10832                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
10833                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10834            }
10835            TargetKind::FreeFunction
10836            | TargetKind::Method
10837            | TargetKind::GlobalField
10838            | TargetKind::MemberField
10839            | TargetKind::Macro => {
10840                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10841                if rest.len() > 1 {
10842                    let owner = rest[..rest.len() - 1].join("$");
10843                    let short = format!("{}.{}", owner, rest[rest.len() - 1]);
10844                    push_cpp_fqn_candidate(&mut candidates, &package, &short);
10845                }
10846            }
10847        }
10848    }
10849    candidates
10850}
10851
10852fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
10853    let fqn = if package.is_empty() {
10854        short.to_string()
10855    } else {
10856        format!("{package}.{short}")
10857    };
10858    if !out.contains(&fqn) {
10859        out.push(fqn);
10860    }
10861}
10862
10863pub fn infer_cpp_initializer_type(
10864    analyzer: &CppGraphSource<'_>,
10865    visibility: &VisibilityIndex<'_>,
10866    file: &ProjectFile,
10867    source: &str,
10868    node: Node<'_>,
10869) -> Option<CodeUnit> {
10870    infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
10871        .and_then(|binding| binding.unit)
10872}
10873
10874pub fn infer_cpp_initializer_binding(
10875    analyzer: &CppGraphSource<'_>,
10876    visibility: &VisibilityIndex<'_>,
10877    file: &ProjectFile,
10878    source: &str,
10879    node: Node<'_>,
10880    receiver_resolver: Option<&ReceiverResolver<'_>>,
10881) -> Option<CppScanBinding> {
10882    match node.kind() {
10883        "new_expression" => {
10884            let type_node = cpp_new_expression_type_node(node)?;
10885            let type_text = node_text(type_node, source);
10886            let name = normalize_cpp_type_name(type_text);
10887            Some(CppScanBinding::from_type_name(
10888                name.clone(),
10889                visibility.resolve_type(file, &name),
10890                1,
10891            ))
10892        }
10893        "call_expression" => node.child_by_field_name("function").and_then(|function| {
10894            // `a().b()` and `p->b()` invoke a member on a receiver *value*. The
10895            // callee's source text is an expression, not a name, and every name
10896            // lookup below normalizes a reference by truncating at the first
10897            // `(`: `first().second` would read as `first`, so the chained call
10898            // would take the type of `first()` instead of the type of
10899            // `first().second()` (#2178). Only the member path can answer for
10900            // this shape, so route to it from the callee's node kind.
10901            if function.kind() == "field_expression" {
10902                let arity = visibility.call_arity_evidence(file, node, source).exact()?;
10903                return resolve_field_method_call_return_binding(
10904                    analyzer,
10905                    visibility,
10906                    file,
10907                    source,
10908                    function,
10909                    arity,
10910                    receiver_resolver,
10911                );
10912            }
10913            let function_text = node_text(function, source);
10914            let direct_type_binding = visibility
10915                .resolve_type(file, function_text)
10916                .map(|unit| CppScanBinding::from_unit(unit, 0));
10917            if function.kind() == "template_function" && direct_type_binding.is_some() {
10918                let lexical_namespace = enclosing_namespace_context(node, source);
10919                let arity = visibility.call_arity_evidence(file, node, source).exact();
10920                if let Some(arity) = arity
10921                    && let Some(binding) = visibility.resolve_call_return_binding(
10922                        analyzer,
10923                        file,
10924                        function_text,
10925                        arity,
10926                        lexical_namespace.as_deref(),
10927                        direct_type_binding
10928                            .as_ref()
10929                            .and_then(|binding| binding.unit.as_ref()),
10930                    )
10931                {
10932                    return Some(binding);
10933                }
10934                let (has_callable, callable_binding) = visibility
10935                    .resolve_call_return_binding_without_arity(
10936                        analyzer,
10937                        file,
10938                        function_text,
10939                        lexical_namespace.as_deref(),
10940                        direct_type_binding
10941                            .as_ref()
10942                            .and_then(|binding| binding.unit.as_ref()),
10943                    );
10944                if let Some(binding) = callable_binding {
10945                    return Some(binding);
10946                }
10947                if has_callable {
10948                    return None;
10949                }
10950                return direct_type_binding;
10951            }
10952            // Only the return-typed branches need the argument count. An
10953            // unknown arity leaves them out, exactly as in the template arm
10954            // above, and still constructs the direct type: `File(getPath())`
10955            // names `File` whether or not `getPath()`'s expansion is provable.
10956            let arity = visibility.call_arity_evidence(file, node, source).exact();
10957            if let Some(arity) = arity {
10958                let direct_type_binding_for_call = direct_type_binding.clone();
10959                if let Some(binding) = resolve_static_method_call_return_binding(
10960                    analyzer, visibility, file, source, function, arity,
10961                )
10962                .or_else(|| {
10963                    // An applicable free function supplies the receiver value
10964                    // before an unrelated visible type with the same terminal
10965                    // name. The direct type still excludes its own constructor
10966                    // declaration below and remains the construction fallback.
10967                    visibility.resolve_call_return_binding(
10968                        analyzer,
10969                        file,
10970                        function_text,
10971                        arity,
10972                        enclosing_namespace_context(node, source).as_deref(),
10973                        direct_type_binding_for_call
10974                            .as_ref()
10975                            .and_then(|binding| binding.unit.as_ref()),
10976                    )
10977                }) {
10978                    return Some(binding);
10979                }
10980            }
10981            direct_type_binding
10982        }),
10983        _ => None,
10984    }
10985}
10986
10987/// Return the type specifier carried by a C++ `new_expression`.
10988///
10989/// The grammar names this child with the `type` field. If recovery omits that
10990/// field, recover only from type-specifier nodes retained in the parse tree;
10991/// skip placement arguments, the array declarator, and initializer arguments.
10992pub fn cpp_new_expression_type_node(expression: Node<'_>) -> Option<Node<'_>> {
10993    debug_assert_eq!(expression.kind(), "new_expression");
10994    if let Some(type_node) = expression.child_by_field_name("type") {
10995        return Some(type_node);
10996    }
10997
10998    let mut pending = Vec::new();
10999    let mut cursor = expression.walk();
11000    pending.extend(
11001        expression
11002            .named_children(&mut cursor)
11003            .filter(|child| {
11004                !matches!(
11005                    child.kind(),
11006                    "argument_list" | "initializer_list" | "new_declarator"
11007                )
11008            })
11009            .collect::<Vec<_>>()
11010            .into_iter()
11011            .rev(),
11012    );
11013    while let Some(node) = pending.pop() {
11014        if is_cpp_type_specifier_node(node) {
11015            return Some(node);
11016        }
11017        if node.kind() == "ERROR" {
11018            let mut cursor = node.walk();
11019            pending.extend(
11020                node.named_children(&mut cursor)
11021                    .collect::<Vec<_>>()
11022                    .into_iter()
11023                    .rev(),
11024            );
11025        }
11026    }
11027    None
11028}
11029
11030fn is_cpp_type_specifier_node(node: Node<'_>) -> bool {
11031    matches!(
11032        node.kind(),
11033        "class_specifier"
11034            | "decltype"
11035            | "dependent_type"
11036            | "enum_specifier"
11037            | "placeholder_type_specifier"
11038            | "primitive_type"
11039            | "qualified_identifier"
11040            | "sized_type_specifier"
11041            | "struct_specifier"
11042            | "template_type"
11043            | "type_identifier"
11044            | "union_specifier"
11045    )
11046}
11047
11048fn resolve_static_method_call_return_binding(
11049    analyzer: &CppGraphSource<'_>,
11050    visibility: &VisibilityIndex<'_>,
11051    file: &ProjectFile,
11052    source: &str,
11053    function: Node<'_>,
11054    arity: usize,
11055) -> Option<CppScanBinding> {
11056    if function.kind() != "qualified_identifier" {
11057        return None;
11058    }
11059    let qualified = normalize_cpp_reference_text(node_text(function, source));
11060    // A C++ qualified-id is `::`-joined with no embedded delimiters in any
11061    // single component (the shared splitter's operator-token merge keeps
11062    // `operator+`-style names intact), so re-tokenizing with the shared
11063    // structured splitter and peeling the terminal segment reproduces
11064    // `rsplit_once("::")`'s (owner, member) split exactly — same shape as
11065    // `cpp_out_of_line_function_owner`'s `qualified` split above.
11066    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11067        brokk_bifrost_core::analyzer::Language::Cpp,
11068        &qualified,
11069    );
11070    let (owner_text, member_name) = match parts.split_last() {
11071        Some((member, owner_parts)) if !owner_parts.is_empty() => {
11072            (owner_parts.join("::"), member.clone())
11073        }
11074        _ => {
11075            let scope = function.child_by_field_name("scope")?;
11076            let name = function.child_by_field_name("name")?;
11077            (
11078                node_text(scope, source).to_string(),
11079                node_text(name, source).to_string(),
11080            )
11081        }
11082    };
11083    let owner = visibility.resolve_type(file, &owner_text)?;
11084    let candidates =
11085        callable_members_accepting_arity(analyzer, visibility, file, &owner, &member_name, arity);
11086    unanimous_return_binding(analyzer, visibility, file, &candidates)
11087}
11088
11089fn resolve_field_method_call_return_binding(
11090    analyzer: &CppGraphSource<'_>,
11091    visibility: &VisibilityIndex<'_>,
11092    file: &ProjectFile,
11093    source: &str,
11094    function: Node<'_>,
11095    arity: usize,
11096    receiver_resolver: Option<&ReceiverResolver<'_>>,
11097) -> Option<CppScanBinding> {
11098    debug_assert_eq!(
11099        function.kind(),
11100        "field_expression",
11101        "the member-call return binding answers only for a field-expression callee"
11102    );
11103    let receiver_resolver = receiver_resolver?;
11104    let field = function.child_by_field_name("field")?;
11105    let member_name = node_text(function_terminal_node(field), source);
11106    let receiver = function
11107        .child_by_field_name("argument")
11108        .or_else(|| function.named_child(0))?;
11109    let owners = receiver_resolver(receiver, source);
11110    let mut candidates = Vec::new();
11111    for owner in owners {
11112        let declaring_owner =
11113            match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
11114                EnclosingMemberOwnerResolution::Owner(owner) => owner,
11115                EnclosingMemberOwnerResolution::Missing => continue,
11116                EnclosingMemberOwnerResolution::Ambiguous => return None,
11117            };
11118        candidates.extend(callable_members_accepting_arity(
11119            analyzer,
11120            visibility,
11121            file,
11122            &declaring_owner,
11123            member_name,
11124            arity,
11125        ));
11126    }
11127    unanimous_return_binding(analyzer, visibility, file, &candidates)
11128}
11129
11130fn callable_members_accepting_arity(
11131    analyzer: &CppGraphSource<'_>,
11132    visibility: &VisibilityIndex<'_>,
11133    file: &ProjectFile,
11134    owner: &CodeUnit,
11135    member_name: &str,
11136    arity: usize,
11137) -> Vec<CodeUnit> {
11138    visibility
11139        .visible_members_for_owner_name(file, owner, member_name)
11140        .into_iter()
11141        .filter(|unit| {
11142            unit.is_function()
11143                && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(arity))
11144        })
11145        .cloned()
11146        .collect()
11147}
11148
11149fn unanimous_return_binding(
11150    analyzer: &CppGraphSource<'_>,
11151    visibility: &VisibilityIndex<'_>,
11152    file: &ProjectFile,
11153    candidates: &[CodeUnit],
11154) -> Option<CppScanBinding> {
11155    let mut resolved_return: Option<CppScanBinding> = None;
11156    for function in candidates {
11157        let metadata = analyzer.signature_metadata(function);
11158        let return_types = if metadata.is_empty() {
11159            vec![cpp_function_return_type_text(analyzer, function)?]
11160        } else {
11161            metadata
11162                .iter()
11163                .map(|metadata| metadata.return_type_text().map(str::to_string))
11164                .collect::<Option<Vec<_>>>()?
11165        };
11166        for return_text in return_types {
11167            let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
11168            let name = normalize_cpp_type_name(&return_text);
11169            let binding = CppScanBinding::from_type_name(
11170                name.clone(),
11171                visibility
11172                    .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
11173                indirection,
11174            );
11175            if let Some(existing) = resolved_return.as_ref()
11176                && (existing.indirection != binding.indirection
11177                    || match (&existing.unit, &binding.unit) {
11178                        (Some(left), Some(right)) => !same_visible_symbol(left, right),
11179                        (None, None) => existing.type_name != binding.type_name,
11180                        (Some(_), None) | (None, Some(_)) => true,
11181                    })
11182            {
11183                return None;
11184            }
11185            resolved_return = Some(binding);
11186        }
11187    }
11188    resolved_return
11189}
11190
11191fn aliases_from_canonical_source(
11192    cpp: &dyn CppSource,
11193    token: QueryToken<'_>,
11194    file: &ProjectFile,
11195) -> Option<Vec<CppAlias>> {
11196    let source = cpp.declaration_source_facts(token, file)?;
11197    Some(
11198        source
11199            .facts
11200            .declarations
11201            .iter()
11202            .filter_map(|fact| {
11203                let alias = fact.file_scope_alias.as_ref()?;
11204                Some(CppAlias {
11205                    name: alias.name.clone(),
11206                    target: alias.target.clone(),
11207                    namespace: alias.namespace.clone(),
11208                })
11209            })
11210            .collect(),
11211    )
11212}
11213
11214pub(crate) fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
11215    let mut current = node.parent();
11216    while let Some(parent) = current {
11217        match parent.kind() {
11218            "translation_unit"
11219            | "namespace_definition"
11220            | "declaration_list"
11221            | "linkage_specification" => current = parent.parent(),
11222            "template_declaration" => current = parent.parent(),
11223            _ => return false,
11224        }
11225    }
11226    true
11227}
11228
11229pub fn collect_include_closure(
11230    analyzer: &CppGraphSource<'_>,
11231    include_targets: &IncludeTargetIndex,
11232    file: &ProjectFile,
11233    out: &mut HashSet<ProjectFile>,
11234    cancellation: Option<&CancellationToken>,
11235) {
11236    let mut stack = vec![file.clone()];
11237    while let Some(file) = stack.pop() {
11238        if cancellation.is_some_and(CancellationToken::is_cancelled) {
11239            break;
11240        }
11241        if !out.insert(file.clone()) {
11242            continue;
11243        }
11244        for include in analyzer.canonical_include_paths(&file).unwrap_or_default() {
11245            for target in resolve_include_targets_with_index(&file, &include, include_targets) {
11246                stack.push(target);
11247            }
11248        }
11249    }
11250}
11251
11252fn parse_macro_parameter_list<T>(
11253    replacement: &str,
11254    read: impl FnOnce(Node<'_>, &str) -> T,
11255) -> Option<T> {
11256    let parsed = parse_sentinel(
11257        "void __bifrost_macro_parameters(",
11258        SentinelInput::replacement(replacement),
11259        ");",
11260    );
11261    let (source, tree) = (parsed.source, parsed.tree);
11262    let root = tree.root_node();
11263    if root.has_error() {
11264        return None;
11265    }
11266    let declaration = single_named_child_matching(root, |child| !child.is_extra())?;
11267    let declarator = declaration.child_by_field_name("declarator")?;
11268    let parameters = declarator.child_by_field_name("parameters")?;
11269    Some(read(parameters, &source))
11270}
11271
11272fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
11273    parse_macro_parameter_list(replacement, crate::declarations::cpp_callable_arity)
11274}
11275
11276/// Interpret opaque macro replacement parameters with the primary producer's
11277/// canonical type spelling, preserving cv/ref qualifiers for overload hiding.
11278pub fn cpp_macro_parameter_types(replacement: &str) -> Option<Vec<String>> {
11279    parse_macro_parameter_list(
11280        replacement,
11281        crate::declarations::cpp_callable_parameter_types,
11282    )
11283}
11284
11285pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> Option<CallableArity> {
11286    brokk_bifrost_core::analyzer::model::first_callable_arity(analyzer.signature_metadata(unit))
11287}
11288
11289pub fn cpp_callable_parameter_types(
11290    analyzer: &CppGraphSource<'_>,
11291    unit: &CodeUnit,
11292) -> Option<Vec<String>> {
11293    brokk_bifrost_core::analyzer::model::first_callable_parameter_types(
11294        analyzer.signature_metadata(unit),
11295    )
11296}
11297
11298fn merge_compatible_callable_arities(
11299    left: CallableArity,
11300    right: CallableArity,
11301) -> Option<CallableArity> {
11302    let total = left.total();
11303    let left_repeated = left.accepts(total.saturating_add(1));
11304    let right_repeated = right.accepts(right.total().saturating_add(1));
11305    if total != right.total() || left_repeated != right_repeated {
11306        return None;
11307    }
11308    let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
11309    Some(CallableArity::new(required, total, left_repeated))
11310}
11311
11312fn collect_macro_events(
11313    cpp: &dyn CppSource,
11314    token: QueryToken<'_>,
11315    file: &ProjectFile,
11316) -> Vec<MacroEvent> {
11317    let Some(prepared) = cpp.prepared_syntax(token, file) else {
11318        return Vec::new();
11319    };
11320    let source = prepared.source();
11321    let mut events = Vec::new();
11322    let root = prepared.tree().root_node();
11323    let mut stack = vec![root];
11324    while let Some(node) = stack.pop() {
11325        match node.kind() {
11326            "preproc_def" | "preproc_function_def" => {
11327                let Some(name) = node.child_by_field_name("name") else {
11328                    continue;
11329                };
11330                let name = node_text(name, source).to_string();
11331                events.push(MacroEvent::Define {
11332                    name,
11333                    binding: MacroBinding {
11334                        source: file.clone(),
11335                        declaration_byte: node.start_byte(),
11336                        definition: VisibilityIndex::decode_macro_definition(node, source),
11337                        exact: true,
11338                    },
11339                    byte: node.start_byte(),
11340                    conditionals: owning_preprocessor_conditionals(root, node, source),
11341                });
11342                continue;
11343            }
11344            "preproc_include" => {
11345                let Some(path) = node.child_by_field_name("path") else {
11346                    events.push(MacroEvent::Include {
11347                        targets: Vec::new(),
11348                        byte: node.start_byte(),
11349                        conditionals: owning_preprocessor_conditionals(root, node, source),
11350                    });
11351                    continue;
11352                };
11353                let include = crate::imports::include_path_literal_value(path, source);
11354                let targets = include.map_or_else(Vec::new, |include| {
11355                    resolve_include_targets_with_index(file, include, cpp.include_target_index())
11356                });
11357                // An include no indexed file can answer -- its final path component names
11358                // no analyzable workspace file -- crosses into sources this index does not
11359                // hold, so it cannot hide indexed macro state and must not poison every
11360                // later local macro binding. The include's spelling does not decide that:
11361                // a quoted include falls back to the same search path an angle include
11362                // uses, and esphome's `core/log.h` reaches every component with
11363                // `#include "WString.h"`, an Arduino header absent from the tree (#3057).
11364                // A computed include, and a spelling some indexed file name does match
11365                // without resolving to one target, may still hide indexed macro state and
11366                // therefore fail closed.
11367                if targets.is_empty()
11368                    && include.is_some_and(|include| {
11369                        !cpp.include_target_index().names_indexed_file(include)
11370                    })
11371                {
11372                    continue;
11373                }
11374                events.push(MacroEvent::Include {
11375                    targets,
11376                    byte: node.start_byte(),
11377                    conditionals: owning_preprocessor_conditionals(root, node, source),
11378                });
11379                continue;
11380            }
11381            "preproc_call" => {
11382                let Some(directive) = node.child_by_field_name("directive") else {
11383                    continue;
11384                };
11385                if node_text(directive, source) != "#undef" {
11386                    continue;
11387                }
11388                let name = node
11389                    .child_by_field_name("argument")
11390                    .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
11391                if let Some(name) = name {
11392                    events.push(MacroEvent::Undef {
11393                        name,
11394                        byte: node.start_byte(),
11395                        conditionals: owning_preprocessor_conditionals(root, node, source),
11396                    });
11397                } else {
11398                    events.push(MacroEvent::Invalidate {
11399                        byte: node.start_byte(),
11400                    });
11401                }
11402                continue;
11403            }
11404            _ => {}
11405        }
11406        push_named_children_reversed(node, &mut stack);
11407    }
11408    events.sort_by_key(MacroEvent::byte);
11409    events
11410}
11411
11412fn find_include_activation<'guards>(
11413    cpp: &dyn CppSource,
11414    token: QueryToken<'_>,
11415    file: &ProjectFile,
11416    prepared: &PreparedSyntaxTree,
11417    donor_source: &ProjectFile,
11418    effective_guards: &dyn Fn() -> Option<&'guards HashSet<PreprocessorGuard>>,
11419) -> Option<usize> {
11420    let include_targets = cpp.include_target_index();
11421    let mut direct_includes = Vec::new();
11422    let mut nodes = vec![prepared.tree().root_node()];
11423    // An include activates for the whole file, so only an unconditional
11424    // directive counts here.
11425    while let Some(node) = nodes.pop() {
11426        if node.kind() == "preproc_include" {
11427            let Some(required_guards) =
11428                include_directive_guard_requirements(node, prepared.source(), effective_guards)
11429            else {
11430                continue;
11431            };
11432            if required_guards.all.is_empty() && required_guards.partial.is_empty() {
11433                for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
11434                    if let Some(target) = unique_include_target(resolve_include_targets_with_index(
11435                        file,
11436                        include,
11437                        include_targets,
11438                    )) {
11439                        direct_includes.push((node.end_byte(), target));
11440                    }
11441                }
11442            }
11443            continue;
11444        }
11445        push_named_children_reversed(node, &mut nodes);
11446    }
11447    direct_includes.sort_by_key(|(activation, _)| *activation);
11448    let mut known_missing = HashSet::default();
11449    direct_includes
11450        .into_iter()
11451        .find(|(_, direct)| {
11452            unconditional_include_reaches(
11453                cpp,
11454                token,
11455                direct,
11456                donor_source,
11457                file,
11458                &mut known_missing,
11459                effective_guards,
11460            )
11461        })
11462        .map(|(activation, _)| activation)
11463}
11464
11465fn find_conditional_include_projection_index<'guards>(
11466    cpp: &dyn CppSource,
11467    token: QueryToken<'_>,
11468    file: &ProjectFile,
11469    prepared: &PreparedSyntaxTree,
11470    effective_guards: &dyn Fn() -> Option<&'guards HashSet<PreprocessorGuard>>,
11471    on_state: &dyn Fn(),
11472) -> ConditionalIncludeProjectionIndex {
11473    let include_targets = cpp.include_target_index();
11474    let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
11475        HashMap::default();
11476    let mut pending = Vec::new();
11477    let mut nodes = vec![prepared.tree().root_node()];
11478    while let Some(node) = nodes.pop() {
11479        if node.kind() == "preproc_include" {
11480            let Some(required_guards) =
11481                include_directive_guard_requirements(node, prepared.source(), effective_guards)
11482            else {
11483                continue;
11484            };
11485            for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
11486                let Some(target) = unique_include_target(resolve_include_targets_with_index(
11487                    file,
11488                    include,
11489                    include_targets,
11490                )) else {
11491                    continue;
11492                };
11493                pending.push((target, node.end_byte(), required_guards.clone()));
11494            }
11495            continue;
11496        }
11497        push_named_children_reversed(node, &mut nodes);
11498    }
11499
11500    // Admission checks both the full guard set and the subset contributed by
11501    // incomplete conditional families. A path subsumes another only when both
11502    // sets are subsets: equal full guards can have different proof obligations
11503    // when one route uses a complete #if/#else and another a lone #if.
11504    // Keep the minimal pairs to avoid enumerating dense guard lattices (#2365).
11505    let mut expanded: HashMap<(ProjectFile, usize), Vec<PreprocessorGuardEnvironment>> =
11506        HashMap::default();
11507    while let Some((current_file, activation_byte, path)) = pending.pop() {
11508        let guard_sets = expanded
11509            .entry((current_file.clone(), activation_byte))
11510            .or_default();
11511        if guard_sets.iter().any(|existing| existing.is_subset(&path)) {
11512            continue;
11513        }
11514        let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
11515            .drain(..)
11516            .partition(|existing| path.is_subset(existing));
11517        *guard_sets = kept;
11518        guard_sets.push(path.clone());
11519        if !evicted.is_empty()
11520            && let Some(projections) = projections_by_source.get_mut(&current_file)
11521        {
11522            projections.retain(|projection| {
11523                projection.activation_byte != activation_byte
11524                    || !evicted.iter().any(|existing| {
11525                        existing.all == projection.required_guards
11526                            && existing.partial == projection.partial_guards
11527                    })
11528            });
11529        }
11530        on_state();
11531
11532        // A fresh minimal set has no equal in the store: equality would have
11533        // been caught by the subset check above.
11534        projections_by_source
11535            .entry(current_file.clone())
11536            .or_default()
11537            .push(ConditionalIncludeProjection {
11538                activation_byte,
11539                required_guards: path.all.clone(),
11540                partial_guards: path.partial.clone(),
11541            });
11542
11543        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
11544            continue;
11545        };
11546        let mut nodes = vec![current_prepared.tree().root_node()];
11547        while let Some(node) = nodes.pop() {
11548            if node.kind() == "preproc_include" {
11549                let Some(include_guards) = include_directive_guard_requirements(
11550                    node,
11551                    current_prepared.source(),
11552                    effective_guards,
11553                ) else {
11554                    continue;
11555                };
11556                let Some(reached) = path.merged(&include_guards) else {
11557                    continue;
11558                };
11559                for (include, _) in
11560                    include_path_from_node(node, current_prepared.source()).into_iter()
11561                {
11562                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
11563                        &current_file,
11564                        include,
11565                        include_targets,
11566                    )) else {
11567                        continue;
11568                    };
11569                    pending.push((target, activation_byte, reached.clone()));
11570                }
11571                continue;
11572            }
11573            push_named_children_reversed(node, &mut nodes);
11574        }
11575    }
11576
11577    projections_by_source
11578        .into_iter()
11579        .map(|(source, mut projections)| {
11580            projections.sort_by_key(|projection| projection.activation_byte);
11581            (source, Arc::from(projections))
11582        })
11583        .collect()
11584}
11585
11586/// Decide one conditional include target without materializing every source
11587/// reached by every guard combination. A path `admission` already rejects
11588/// cannot become admissible after adding nested include guards -- both rules
11589/// only ever add requirements -- so discard it before expanding the next
11590/// header.
11591#[allow(clippy::too_many_arguments)]
11592fn find_conditional_include_projection_for_source<'guards>(
11593    cpp: &dyn CppSource,
11594    token: QueryToken<'_>,
11595    file: &ProjectFile,
11596    prepared: &PreparedSyntaxTree,
11597    donor_source: &ProjectFile,
11598    admission: IncludePathAdmission,
11599    reference_guards: Option<&HashSet<PreprocessorGuard>>,
11600    reference_byte: usize,
11601    effective_guards: &dyn Fn() -> Option<&'guards HashSet<PreprocessorGuard>>,
11602    on_state: &dyn Fn(),
11603) -> bool {
11604    let Some(reference_guards) = reference_guards else {
11605        return false;
11606    };
11607    let include_targets = cpp.include_target_index();
11608    let mut pending = Vec::new();
11609    let mut nodes = vec![prepared.tree().root_node()];
11610    while let Some(node) = nodes.pop() {
11611        if node.kind() == "preproc_include" {
11612            let Some(required_guards) =
11613                include_directive_guard_requirements(node, prepared.source(), effective_guards)
11614            else {
11615                continue;
11616            };
11617            if node.end_byte() > reference_byte
11618                || !admission.admits(
11619                    &required_guards.all,
11620                    &required_guards.partial,
11621                    Some(reference_guards),
11622                )
11623            {
11624                continue;
11625            }
11626            for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
11627                let Some(target) = unique_include_target(resolve_include_targets_with_index(
11628                    file,
11629                    include,
11630                    include_targets,
11631                )) else {
11632                    continue;
11633                };
11634                if &target == donor_source {
11635                    return true;
11636                }
11637                pending.push((target, required_guards.clone()));
11638            }
11639            continue;
11640        }
11641        push_named_children_reversed(node, &mut nodes);
11642    }
11643
11644    let mut expanded: HashMap<ProjectFile, Vec<HashSet<PreprocessorGuard>>> = HashMap::default();
11645    while let Some((current_file, path)) = pending.pop() {
11646        let guard_sets = expanded.entry(current_file.clone()).or_default();
11647        if guard_sets.contains(&path.all) {
11648            continue;
11649        }
11650        guard_sets.push(path.all.clone());
11651        on_state();
11652
11653        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
11654            continue;
11655        };
11656        let mut nodes = vec![current_prepared.tree().root_node()];
11657        while let Some(node) = nodes.pop() {
11658            if node.kind() == "preproc_include" {
11659                let Some(include_guards) = include_directive_guard_requirements(
11660                    node,
11661                    current_prepared.source(),
11662                    effective_guards,
11663                ) else {
11664                    continue;
11665                };
11666                let Some(reached) = path.merged(&include_guards) else {
11667                    continue;
11668                };
11669                if !admission.admits(&reached.all, &reached.partial, Some(reference_guards)) {
11670                    continue;
11671                }
11672                for (include, _) in
11673                    include_path_from_node(node, current_prepared.source()).into_iter()
11674                {
11675                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
11676                        &current_file,
11677                        include,
11678                        include_targets,
11679                    )) else {
11680                        continue;
11681                    };
11682                    if &target == donor_source {
11683                        return true;
11684                    }
11685                    pending.push((target, reached.clone()));
11686                }
11687                continue;
11688            }
11689            push_named_children_reversed(node, &mut nodes);
11690        }
11691    }
11692    false
11693}
11694
11695/// Whether `translation_unit`'s unconditional `#include` closure reaches
11696/// `header`, directly or through any chain of headers.
11697///
11698/// The include-closure question asked on its own, for
11699/// [`crate::identity::cpp_header_body_files_are_related`]. The walk resolves
11700/// each include the way visibility does -- to a unique target or to nothing --
11701/// so a duplicated basename relates nothing, and it is memoized per file pair
11702/// and language-proof state on the analyzer.
11703///
11704/// The reference position is `translation_unit` itself: the question is
11705/// whether that unit compiles the header, so that unit's own dialect and
11706/// preprocessor context govern the walk.
11707pub fn cpp_include_closure_reaches(
11708    cpp: &dyn CppSource,
11709    token: QueryToken<'_>,
11710    translation_unit: &ProjectFile,
11711    header: &ProjectFile,
11712) -> bool {
11713    let language_guard_stable = OnceCell::new();
11714    let effective = OnceCell::new();
11715    let proven = compile_proven_guard_facts(cpp, translation_unit);
11716    unconditional_include_reaches(
11717        cpp,
11718        token,
11719        translation_unit,
11720        header,
11721        translation_unit,
11722        &mut HashSet::default(),
11723        &|| {
11724            effective
11725                .get_or_init(|| {
11726                    effective_reference_guards(
11727                        cpp,
11728                        translation_unit,
11729                        Some(&HashSet::default()),
11730                        &proven,
11731                        &|| {
11732                            *language_guard_stable.get_or_init(|| {
11733                                include_language_guard_is_stable_with(
11734                                    cpp,
11735                                    token,
11736                                    translation_unit,
11737                                    &mut |file: &ProjectFile| {
11738                                        collect_macro_events(cpp, token, file).into_boxed_slice()
11739                                    },
11740                                    &mut |_: &ProjectFile, prepared: &PreparedSyntaxTree| {
11741                                        collect_structured_include_facts(prepared)
11742                                    },
11743                                )
11744                            })
11745                        },
11746                    )
11747                })
11748                .as_ref()
11749        },
11750    )
11751}
11752
11753fn unconditional_include_reaches<'guards>(
11754    cpp: &dyn CppSource,
11755    token: QueryToken<'_>,
11756    first: &ProjectFile,
11757    donor_source: &ProjectFile,
11758    reference_file: &ProjectFile,
11759    known_missing: &mut HashSet<ProjectFile>,
11760    effective_guards: &dyn Fn() -> Option<&'guards HashSet<PreprocessorGuard>>,
11761) -> bool {
11762    if first == donor_source {
11763        return true;
11764    }
11765    if known_missing.contains(first) {
11766        return false;
11767    }
11768    if let Some(reaches) =
11769        cpp.cached_unconditional_include_reachability(first, donor_source, reference_file)
11770    {
11771        return reaches;
11772    }
11773    let mut visited = HashSet::default();
11774    let mut files = vec![first.clone()];
11775    let include_targets = cpp.include_target_index();
11776    while let Some(file) = files.pop() {
11777        if file == *donor_source {
11778            cpp.cache_unconditional_include_reachability(first, donor_source, reference_file, true);
11779            return true;
11780        }
11781        if known_missing.contains(&file) || !visited.insert(file.clone()) {
11782            continue;
11783        }
11784        let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11785            continue;
11786        };
11787        // One cursor for the whole-file walk; see `named_children_iter` (#3097).
11788        let mut cursor = prepared.tree().walk();
11789        let mut nodes = vec![prepared.tree().root_node()];
11790        while let Some(node) = nodes.pop() {
11791            if node.kind() == "preproc_include" {
11792                let Some(required_guards) =
11793                    include_directive_guard_requirements(node, prepared.source(), effective_guards)
11794                else {
11795                    continue;
11796                };
11797                if required_guards.all.is_empty() && required_guards.partial.is_empty() {
11798                    for (include, _) in include_path_from_node(node, prepared.source()).into_iter()
11799                    {
11800                        if let Some(target) = unique_include_target(
11801                            resolve_include_targets_with_index(&file, include, include_targets),
11802                        ) {
11803                            files.push(target);
11804                        }
11805                    }
11806                }
11807                continue;
11808            }
11809            let first_pushed = nodes.len();
11810            nodes.extend(node.named_children(&mut cursor));
11811            nodes[first_pushed..].reverse();
11812        }
11813    }
11814    known_missing.extend(visited);
11815    cpp.cache_unconditional_include_reachability(first, donor_source, reference_file, false);
11816    false
11817}
11818
11819/// The guards that hold at a reference in `file`: its own source guards,
11820/// the defines the build proves (#2011), and the translation unit's stable
11821/// `__cplusplus` state. A reference whose own environment is unknown stays
11822/// unknown -- the facts extend an environment, they never invent one. Every
11823/// proof and contradiction check against a reference uses this set, so a C
11824/// translation unit never takes an `#ifdef __cplusplus` arm (#7072).
11825fn effective_reference_guards(
11826    cpp: &dyn CppSource,
11827    file: &ProjectFile,
11828    reference_guards: Option<&HashSet<PreprocessorGuard>>,
11829    proven: &HashSet<PreprocessorGuard>,
11830    language_guard_is_stable: &dyn Fn() -> bool,
11831) -> Option<HashSet<PreprocessorGuard>> {
11832    let mut effective = reference_guards.cloned()?;
11833    effective.extend(proven.iter().cloned());
11834    if language_guard_is_stable() {
11835        effective.insert(if reference_uses_c_semantics(cpp, file) {
11836            PreprocessorGuard::Undefined("__cplusplus".to_string())
11837        } else {
11838            PreprocessorGuard::Defined("__cplusplus".to_string())
11839        });
11840    }
11841    Some(effective)
11842}
11843
11844fn include_language_guard_is_stable_with<MacroEvents, IncludeFacts>(
11845    cpp: &dyn CppSource,
11846    token: QueryToken<'_>,
11847    file: &ProjectFile,
11848    mut macro_events: MacroEvents,
11849    mut include_facts: IncludeFacts,
11850) -> bool
11851where
11852    MacroEvents: FnMut(&ProjectFile) -> Box<[MacroEvent]>,
11853    IncludeFacts: FnMut(&ProjectFile, &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]>,
11854{
11855    // A header has no intrinsic dialect. An opaque incoming include can hide
11856    // its compiler context from reverse include attribution. Only a
11857    // translation unit can supply the source-only language fact.
11858    if !crate::imports::is_cpp_translation_unit(file) {
11859        return false;
11860    }
11861    // When compiler data exists, keep its existing authoritative guard proof.
11862    // Source-only dialect facts cannot reconstruct -D/-U ordering.
11863    if !cpp.compile_contexts_for(file).is_empty()
11864        || cpp
11865            .reaching_translation_units(file)
11866            .iter()
11867            .any(|tu| !cpp.compile_contexts_for(tu).is_empty())
11868    {
11869        return false;
11870    }
11871    let mut pending = vec![file.clone()];
11872    let mut visited = HashSet::default();
11873    while let Some(file) = pending.pop() {
11874        if !visited.insert(file.clone()) {
11875            continue;
11876        }
11877        let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11878            return false;
11879        };
11880        if macro_events(&file).iter().any(|event| match event {
11881            MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
11882                name == "__cplusplus"
11883            }
11884            MacroEvent::Invalidate { .. } => true,
11885            MacroEvent::Include { targets, .. } => targets.is_empty(),
11886        }) {
11887            return false;
11888        }
11889        // Unlike indexed macro-binding replay, intrinsic-macro proof also
11890        // needs every include source: an opaque header may change it.
11891        for fact in include_facts(&file, prepared.as_ref()).iter() {
11892            let targets =
11893                resolve_include_targets_with_index(&file, &fact.path, cpp.include_target_index());
11894            if targets.is_empty() {
11895                return false;
11896            }
11897            pending.extend(targets);
11898        }
11899    }
11900    true
11901}
11902
11903fn declaration_guard_requirements(
11904    analyzer: &CppGraphSource<'_>,
11905    cpp: &dyn CppSource,
11906    candidate: &CodeUnit,
11907) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
11908    if cpp
11909        .prepared_syntax(analyzer.token, candidate.source())
11910        .is_none()
11911    {
11912        return Vec::new();
11913    }
11914    analyzer
11915        .declaration_source_occurrences(candidate)
11916        .into_iter()
11917        .flatten()
11918        .filter_map(|occurrence| {
11919            let guards = cpp_guard_set_to_runtime(occurrence.fact.guard_requirements.as_ref()?)?;
11920            Some((occurrence.range.start_byte, guards))
11921        })
11922        .collect()
11923}
11924
11925/// One declaration range's preprocessor facts, as a visible-at-a-reference
11926/// question reads them.
11927struct DeclarationGuardSite {
11928    /// The byte at which the range introduces the name.
11929    byte: usize,
11930    /// The preprocessor requirements the range's declaration stands under.
11931    guards: HashSet<PreprocessorGuard>,
11932    /// Whether the range is one branch of completed `#if`/`#else` families
11933    /// that all close before the reference.
11934    completed_family_before_reference: bool,
11935}
11936
11937/// The per-range facts behind [`declaration_guard_requirements`], plus the
11938/// reference-relative fact the completed-family rule asks for.
11939fn declaration_guard_sites(
11940    analyzer: &CppGraphSource<'_>,
11941    cpp: &dyn CppSource,
11942    candidate: &CodeUnit,
11943    reference_byte: usize,
11944) -> Vec<DeclarationGuardSite> {
11945    let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
11946        return Vec::new();
11947    };
11948    let root = prepared.tree().root_node();
11949    analyzer
11950        .declaration_source_occurrences(candidate)
11951        .into_iter()
11952        .flatten()
11953        .filter_map(|occurrence| {
11954            let guards = cpp_guard_set_to_runtime(occurrence.fact.guard_requirements.as_ref()?)?;
11955            Some(DeclarationGuardSite {
11956                byte: occurrence.range.start_byte,
11957                guards,
11958                completed_family_before_reference: {
11959                    root.descendant_for_byte_range(
11960                        occurrence.range.start_byte,
11961                        occurrence.range.end_byte,
11962                    )
11963                    .is_some_and(|node| {
11964                        declaration_follows_completed_family_before_reference(
11965                            node,
11966                            prepared.source(),
11967                            reference_byte,
11968                        )
11969                    })
11970                },
11971            })
11972        })
11973        .collect()
11974}
11975
11976/// Whether every conditional enclosing `node` is a completed
11977/// `#if`/`#elif`/`#else` family that closes before `reference_byte`.
11978///
11979/// A completed family is an exhaustive case analysis: every configuration
11980/// takes exactly one of its branches. A reference sited below the whole family
11981/// is compiled in a configuration that either takes the declaration's branch,
11982/// in which case the declaration is what the name resolves to there, or takes
11983/// another branch, in which case the declaration is simply not part of that
11984/// configuration. Nothing in the reference's own position settles the family,
11985/// so the declaration is one resolution the source presents at the reference
11986/// rather than a contradiction of it (#3297).
11987///
11988/// A lone `#if` is not a case analysis: nothing in the source says any
11989/// configuration defines its macro, so a declaration under one stays
11990/// unprovable and the implication rule keeps it out. Neither does a family
11991/// whose end is not behind the reference: a reference inside the other branch
11992/// contradicts the declaration, and a reference inside the declaration's own
11993/// branch is already covered by implication.
11994fn declaration_follows_completed_family_before_reference(
11995    node: Node<'_>,
11996    source: &str,
11997    reference_byte: usize,
11998) -> bool {
11999    callable_guard_completion_byte(node, source).is_some_and(|end| end > 0 && end <= reference_byte)
12000}
12001
12002/// Producer-owned completion boundary for all enclosing conditional families.
12003/// Zero is the neutral boundary for a declaration outside those families.
12004pub(crate) fn callable_guard_completion_byte(node: Node<'_>, source: &str) -> Option<usize> {
12005    let mut cache = PreprocessorContextCache::default();
12006    callable_guard_completion_byte_with_cache(node, source, &mut cache)
12007}
12008
12009pub(crate) fn callable_guard_completion_byte_with_cache(
12010    node: Node<'_>,
12011    source: &str,
12012    cache: &mut PreprocessorContextCache,
12013) -> Option<usize> {
12014    let mut completion_byte = 0;
12015    let mut ancestor = node.parent();
12016    while let Some(conditional) = ancestor {
12017        if matches!(
12018            conditional.kind(),
12019            "preproc_if" | "preproc_ifdef" | "preproc_elif"
12020        ) && !is_file_covering_include_guard(conditional, source)
12021            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
12022            && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
12023        {
12024            let family = preprocessor_conditional_family_root(conditional);
12025            if !preprocessor_conditional_family_has_terminal_else(family, cache) {
12026                return None;
12027            }
12028            completion_byte = completion_byte.max(family.end_byte());
12029        }
12030        ancestor = conditional.parent();
12031    }
12032    Some(completion_byte)
12033}
12034
12035fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
12036    analyzer
12037        .ranges(candidate)
12038        .into_iter()
12039        .map(|range| range.start_byte)
12040        .min()
12041}
12042
12043/// Build facts shared by visibility queries and standalone include identity.
12044fn compile_proven_guard_facts(
12045    cpp: &dyn CppSource,
12046    file: &ProjectFile,
12047) -> HashSet<PreprocessorGuard> {
12048    if let Some(facts) = context_guard_facts(cpp.compile_contexts_for(file), file) {
12049        return facts;
12050    }
12051    let mut translation_units = cpp.reaching_translation_units(file).into_iter();
12052    let Some(mut facts) = translation_units.next().and_then(|translation_unit| {
12053        context_guard_facts(
12054            cpp.compile_contexts_for(&translation_unit),
12055            &translation_unit,
12056        )
12057    }) else {
12058        return HashSet::default();
12059    };
12060    for translation_unit in translation_units {
12061        let Some(reached) = context_guard_facts(
12062            cpp.compile_contexts_for(&translation_unit),
12063            &translation_unit,
12064        ) else {
12065            facts.clear();
12066            break;
12067        };
12068        facts.retain(|fact| reached.contains(fact));
12069        if facts.is_empty() {
12070            break;
12071        }
12072    }
12073    facts
12074}
12075
12076/// Facts every compiler configuration proves. No entry differs from coverage
12077/// with an empty intersection (#2011). The compiler's language selection is
12078/// authoritative, including when it overrides the source extension.
12079fn context_guard_facts(
12080    contexts: &[CppCompileContext],
12081    file: &ProjectFile,
12082) -> Option<HashSet<PreprocessorGuard>> {
12083    let (first, rest) = contexts.split_first()?;
12084    let mut facts = first
12085        .defined_macros
12086        .iter()
12087        .filter(|name| {
12088            rest.iter()
12089                .all(|context| context.defined_macros.contains(*name))
12090        })
12091        .cloned()
12092        .map(PreprocessorGuard::Defined)
12093        .collect::<HashSet<_>>();
12094    let language = first.tu_language(file.rel_path());
12095    // An explicit -D/-U may override the builtin independently of the dialect.
12096    // Surviving defines above still prove their names; an -U alone proves no
12097    // source-level absence, matching the other command-line macro facts.
12098    if contexts
12099        .iter()
12100        .all(|context| !context.cplusplus_macro_configured)
12101        && rest
12102            .iter()
12103            .all(|context| context.tu_language(file.rel_path()) == language)
12104    {
12105        facts.insert(match language {
12106            CompiledLanguage::C => PreprocessorGuard::Undefined("__cplusplus".to_string()),
12107            CompiledLanguage::Cpp => PreprocessorGuard::Defined("__cplusplus".to_string()),
12108        });
12109    }
12110    Some(facts)
12111}
12112
12113pub fn guard_requirements_hold_at_reference(
12114    required: &HashSet<PreprocessorGuard>,
12115    reference: Option<&HashSet<PreprocessorGuard>>,
12116) -> bool {
12117    reference.is_some_and(|active| {
12118        required
12119            .iter()
12120            .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
12121    })
12122}
12123
12124fn preprocessor_guard_holds_at_reference(
12125    required: &PreprocessorGuard,
12126    active: &HashSet<PreprocessorGuard>,
12127) -> bool {
12128    if active.contains(required) {
12129        return true;
12130    }
12131    let active_expression =
12132        BooleanGuardExpression::all(active.iter().map(PreprocessorGuard::as_boolean_expression));
12133    active_expression.implies(&required.as_boolean_expression())
12134}
12135
12136/// Candidate discovery rule: two guard sets are compatible when neither
12137/// contradicts the other. Compatibility never establishes proven visibility;
12138/// that requires `guard_requirements_hold_at_reference`.
12139fn guards_compatible_at_reference(
12140    declaration: &HashSet<PreprocessorGuard>,
12141    reference: Option<&HashSet<PreprocessorGuard>>,
12142) -> bool {
12143    reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
12144}
12145
12146pub(crate) fn preprocessor_conditional_family_node(node: Node<'_>) -> Option<Node<'_>> {
12147    let mut ancestor = Some(node);
12148    while let Some(current) = ancestor {
12149        if is_cpp_preprocessor_conditional(current)
12150            && preprocessor_conditional_contains_descendant(current, node)
12151        {
12152            return Some(preprocessor_conditional_family_root(current));
12153        }
12154        ancestor = current.parent();
12155    }
12156    None
12157}
12158
12159pub(crate) fn preprocessor_conditional_family_for_declaration_with_cache<'tree>(
12160    node: Node<'tree>,
12161    cache: &mut PreprocessorContextCache,
12162) -> Option<Node<'tree>> {
12163    let mut ancestor = node.parent();
12164    while let Some(current) = ancestor {
12165        if is_cpp_preprocessor_conditional(current)
12166            && preprocessor_conditional_contains_descendant_with_cache(current, node, cache)
12167        {
12168            let family = preprocessor_conditional_family_root(current);
12169            if preprocessor_conditional_family_has_terminal_else(family, cache) {
12170                return Some(family);
12171            }
12172        }
12173        ancestor = current.parent();
12174    }
12175    None
12176}
12177
12178fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
12179    while let Some(parent) = conditional.parent() {
12180        let is_alternative = parent
12181            .child_by_field_name("alternative")
12182            .is_some_and(|alternative| {
12183                alternative.start_byte() == conditional.start_byte()
12184                    && alternative.end_byte() == conditional.end_byte()
12185            });
12186        if !is_alternative {
12187            break;
12188        }
12189        conditional = parent;
12190    }
12191    conditional
12192}
12193
12194fn preprocessor_conditional_family_has_terminal_else(
12195    mut conditional: Node<'_>,
12196    cache: &mut PreprocessorContextCache,
12197) -> bool {
12198    loop {
12199        let Some(alternative) = conditional.child_by_field_name("alternative") else {
12200            return cache
12201                .displaced_branch(conditional)
12202                .is_some_and(|directive| directive.is_else);
12203        };
12204        match alternative.kind() {
12205            "preproc_else" => return true,
12206            "preproc_elif" => conditional = alternative,
12207            _ => return false,
12208        }
12209    }
12210}
12211
12212/// The undecidable preprocessor guards an `#include` directive stands under,
12213/// or `None` when its conditional context keeps the directive out of a
12214/// translation unit of the reference's language: a false constant, a
12215/// condition this analyzer cannot read, or a `__cplusplus` branch that
12216/// language never takes.
12217///
12218/// This is [`callable_declaration_guard_requirements`]'s rule for the include
12219/// edge, and the language test is what keeps
12220/// [`IncludePathAdmission::Compatible`] honest: an empty active guard set
12221/// never implies `defined(__cplusplus)`, but it never contradicts it either,
12222/// so without this a `.c` file would reach the C++ arm of every
12223/// `#ifdef __cplusplus` / `#else` include pair.
12224fn include_directive_guard_requirements<'guards>(
12225    node: Node<'_>,
12226    source: &str,
12227    effective_guards: &dyn Fn() -> Option<&'guards HashSet<PreprocessorGuard>>,
12228) -> Option<PreprocessorGuardEnvironment> {
12229    let mut required = preprocessor_guard_environment_by_family(node, source)?;
12230    if !required.all.is_empty() {
12231        let effective = effective_guards();
12232        if !guards_compatible_at_reference(&required.all, effective) {
12233            return None;
12234        }
12235        for guards in [&mut required.all, &mut required.partial] {
12236            guards.retain(|guard| {
12237                !effective
12238                    .is_some_and(|active| preprocessor_guard_holds_at_reference(guard, active))
12239            });
12240        }
12241    }
12242    Some(required)
12243}
12244
12245pub fn preprocessor_guard_environment(
12246    node: Node<'_>,
12247    source: &str,
12248) -> Option<HashSet<PreprocessorGuard>> {
12249    let mut cache = PreprocessorContextCache::default();
12250    preprocessor_guard_environment_with_cache(node, source, &mut cache)
12251}
12252
12253pub(crate) fn preprocessor_guard_environment_with_cache(
12254    node: Node<'_>,
12255    source: &str,
12256    cache: &mut PreprocessorContextCache,
12257) -> Option<HashSet<PreprocessorGuard>> {
12258    preprocessor_guard_environment_by_family_with_cache(node, source, cache)
12259        .map(|environment| environment.all)
12260}
12261
12262/// The preprocessor guards `node` stands under, split by whether the
12263/// conditional that contributes each one belongs to a family that covers every
12264/// configuration.
12265///
12266/// A complete `#if`/`#else` or `#if`/`#elif`/`#else` family is a case analysis
12267/// every configuration takes exactly one branch of, which is why the resolver
12268/// already reads a declaration through one (`exhaustive_guard_family_activation`,
12269/// `complementary_same_fqn_type_declarations`). A lone `#if` is not: nothing in
12270/// the source says any configuration defines that macro, so only the build can
12271/// decide it, and it stays in `partial`.
12272#[derive(Clone)]
12273struct PreprocessorGuardEnvironment {
12274    all: HashSet<PreprocessorGuard>,
12275    partial: HashSet<PreprocessorGuard>,
12276}
12277
12278impl PreprocessorGuardEnvironment {
12279    fn is_subset(&self, other: &Self) -> bool {
12280        self.all.is_subset(&other.all) && self.partial.is_subset(&other.partial)
12281    }
12282
12283    /// The environment of a path standing under both `self` and `other`, or
12284    /// `None` when the two contradict. A guard a lone `#if` contributes on
12285    /// either side stays undecided for the whole path.
12286    fn merged(&self, other: &Self) -> Option<Self> {
12287        Some(Self {
12288            all: merge_preprocessor_guards(&self.all, &other.all)?,
12289            partial: self.partial.union(&other.partial).cloned().collect(),
12290        })
12291    }
12292}
12293
12294fn preprocessor_guard_environment_by_family(
12295    node: Node<'_>,
12296    source: &str,
12297) -> Option<PreprocessorGuardEnvironment> {
12298    let mut cache = PreprocessorContextCache::default();
12299    preprocessor_guard_environment_by_family_with_cache(node, source, &mut cache)
12300}
12301
12302fn preprocessor_guard_environment_by_family_with_cache(
12303    node: Node<'_>,
12304    source: &str,
12305    cache: &mut PreprocessorContextCache,
12306) -> Option<PreprocessorGuardEnvironment> {
12307    let mut all = HashSet::default();
12308    let mut partial = HashSet::default();
12309    let mut ancestor = node.parent();
12310    while let Some(conditional) = ancestor {
12311        if matches!(
12312            conditional.kind(),
12313            "preproc_if" | "preproc_ifdef" | "preproc_elif"
12314        ) && !is_file_covering_include_guard(conditional, source)
12315            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
12316            && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
12317        {
12318            let guard = preprocessor_guard_for_descendant(conditional, node, source, cache)?;
12319            match guard {
12320                PreprocessorGuard::Constant(true) => {
12321                    ancestor = conditional.parent();
12322                    continue;
12323                }
12324                PreprocessorGuard::Constant(false) => return None,
12325                _ => {}
12326            }
12327            if all.contains(&guard.negated()) {
12328                return None;
12329            }
12330            if !preprocessor_conditional_family_has_terminal_else(
12331                preprocessor_conditional_family_root(conditional),
12332                cache,
12333            ) {
12334                partial.insert(guard.clone());
12335            }
12336            all.insert(guard);
12337        }
12338        ancestor = conditional.parent();
12339    }
12340    if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
12341        match guard {
12342            PreprocessorGuard::Constant(true) => {}
12343            PreprocessorGuard::Constant(false) => return None,
12344            _ => {
12345                if all.contains(&guard.negated()) {
12346                    return None;
12347                }
12348                // The family of a conditional the parser split across a
12349                // statement boundary is not readable from the tree, so it
12350                // counts as undecided.
12351                partial.insert(guard.clone());
12352                all.insert(guard);
12353            }
12354        }
12355    }
12356    Some(PreprocessorGuardEnvironment { all, partial })
12357}
12358
12359pub(crate) fn callable_preprocessor_guard_requirements_with_cache(
12360    node: Node<'_>,
12361    source: &str,
12362    cache: &mut PreprocessorContextCache,
12363) -> Option<HashSet<PreprocessorGuard>> {
12364    let mut guards = HashSet::default();
12365    for guard in callable_preprocessor_ancestor_guards_with_cache(node, source, cache)? {
12366        match guard {
12367            PreprocessorGuard::Constant(true) => {}
12368            PreprocessorGuard::Constant(false) => return None,
12369            _ => {
12370                if guards.contains(&guard.negated()) {
12371                    return None;
12372                }
12373                guards.insert(guard);
12374            }
12375        }
12376    }
12377    Some(guards)
12378}
12379
12380fn callable_preprocessor_ancestor_guards(
12381    node: Node<'_>,
12382    source: &str,
12383) -> Option<Vec<PreprocessorGuard>> {
12384    let mut cache = PreprocessorContextCache::default();
12385    callable_preprocessor_ancestor_guards_with_cache(node, source, &mut cache)
12386}
12387
12388fn callable_preprocessor_ancestor_guards_with_cache(
12389    node: Node<'_>,
12390    source: &str,
12391    cache: &mut PreprocessorContextCache,
12392) -> Option<Vec<PreprocessorGuard>> {
12393    let mut guards = Vec::new();
12394    let mut ancestor = node.parent();
12395    while let Some(conditional) = ancestor {
12396        if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
12397            && !is_file_covering_include_guard(conditional, source)
12398            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
12399            && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
12400        {
12401            guards.push(preprocessor_guard_for_descendant(
12402                conditional,
12403                node,
12404                source,
12405                cache,
12406            )?);
12407        }
12408        ancestor = conditional.parent();
12409    }
12410    Some(guards)
12411}
12412
12413fn fragmented_statement_preprocessor_guard(
12414    descendant: Node<'_>,
12415    source: &str,
12416) -> Option<PreprocessorGuard> {
12417    // A conditional that starts before `} else if (...) {` crosses the
12418    // enclosing statement's grammar boundary. tree-sitter leaves its opener
12419    // as a `preproc_if` with a missing terminator in the consequence and
12420    // reparses the real `#endif` as a `preproc_call` in the alternative. Pair
12421    // those structured nodes before restoring the guard to intervening uses.
12422    let mut ancestor = descendant.parent();
12423    while let Some(statement) = ancestor {
12424        if statement.kind() == "if_statement"
12425            && let (Some(consequence), Some(alternative)) = (
12426                statement.child_by_field_name("consequence"),
12427                statement.child_by_field_name("alternative"),
12428            )
12429            && alternative.start_byte() <= descendant.start_byte()
12430            && descendant.end_byte() <= alternative.end_byte()
12431        {
12432            let mut cursor = consequence.walk();
12433            let openers = consequence
12434                .named_children(&mut cursor)
12435                .filter(|child| {
12436                    matches!(child.kind(), "preproc_if" | "preproc_ifdef")
12437                        && child
12438                            .child(child.child_count().saturating_sub(1))
12439                            .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
12440                })
12441                .collect::<Vec<_>>();
12442            if openers.len() != 1 {
12443                ancestor = statement.parent();
12444                continue;
12445            }
12446
12447            let mut terminators = Vec::new();
12448            let mut stack = vec![alternative];
12449            while let Some(node) = stack.pop() {
12450                if node.kind() == "preproc_call"
12451                    && node.start_byte() >= descendant.end_byte()
12452                    && node
12453                        .child_by_field_name("directive")
12454                        .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
12455                {
12456                    terminators.push(node);
12457                    continue;
12458                }
12459                push_named_children_reversed(node, &mut stack);
12460            }
12461            if terminators.len() == 1 {
12462                return simple_preprocessor_guard(openers[0], source);
12463            }
12464        }
12465        ancestor = statement.parent();
12466    }
12467    None
12468}
12469
12470pub(crate) fn preprocessor_guard_for_descendant(
12471    conditional: Node<'_>,
12472    descendant: Node<'_>,
12473    source: &str,
12474    cache: &mut PreprocessorContextCache,
12475) -> Option<PreprocessorGuard> {
12476    let guard = simple_preprocessor_guard(conditional, source)?;
12477    match preprocessor_branch_of_descendant(conditional, descendant, cache) {
12478        PreprocessorBranch::Consequence => Some(guard),
12479        // Tree-sitter nests an `#elif` chain in each `alternative` field. A
12480        // descendant in any later branch must first exclude the parent branch,
12481        // then collect the nested `preproc_elif` guard from its own ancestor.
12482        PreprocessorBranch::Structured(alternative)
12483            if matches!(alternative.kind(), "preproc_else" | "preproc_elif") =>
12484        {
12485            Some(guard.negated())
12486        }
12487        PreprocessorBranch::DisplacedElse => Some(guard.negated()),
12488        PreprocessorBranch::Structured(_) | PreprocessorBranch::Unreadable => None,
12489    }
12490}
12491
12492/// The branch of `conditional` that holds `descendant`.
12493enum PreprocessorBranch<'tree> {
12494    Consequence,
12495    /// Inside the structured `alternative` field.
12496    Structured(Node<'tree>),
12497    /// After an `#else` the parser displaced into an `ERROR` of the
12498    /// consequence ([`cpp_displaced_preprocessor_branch`]).
12499    DisplacedElse,
12500    /// After a displaced `#elif`, whose condition has no structured node.
12501    Unreadable,
12502}
12503
12504fn preprocessor_branch_of_descendant<'tree>(
12505    conditional: Node<'tree>,
12506    descendant: Node<'_>,
12507    cache: &mut PreprocessorContextCache,
12508) -> PreprocessorBranch<'tree> {
12509    if let Some(alternative) =
12510        conditional
12511            .child_by_field_name("alternative")
12512            .filter(|alternative| {
12513                alternative.start_byte() <= descendant.start_byte()
12514                    && descendant.end_byte() <= alternative.end_byte()
12515            })
12516    {
12517        return PreprocessorBranch::Structured(alternative);
12518    }
12519    match cache.displaced_branch(conditional) {
12520        Some(directive) if directive.end_byte <= descendant.start_byte() => {
12521            if directive.is_else {
12522                PreprocessorBranch::DisplacedElse
12523            } else {
12524                PreprocessorBranch::Unreadable
12525            }
12526        }
12527        _ => PreprocessorBranch::Consequence,
12528    }
12529}
12530
12531pub(crate) fn preprocessor_conditional_contains_descendant(
12532    conditional: Node<'_>,
12533    descendant: Node<'_>,
12534) -> bool {
12535    cpp_displaced_preprocessor_boundary(conditional)
12536        .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
12537}
12538
12539fn preprocessor_conditional_contains_descendant_with_cache(
12540    conditional: Node<'_>,
12541    descendant: Node<'_>,
12542    cache: &mut PreprocessorContextCache,
12543) -> bool {
12544    cache
12545        .displaced_boundary_end(conditional)
12546        .is_none_or(|end_byte| descendant.end_byte() <= end_byte)
12547}
12548
12549pub fn merge_preprocessor_guards(
12550    left: &HashSet<PreprocessorGuard>,
12551    right: &HashSet<PreprocessorGuard>,
12552) -> Option<HashSet<PreprocessorGuard>> {
12553    let mut merged = left.clone();
12554    for guard in right {
12555        let boolean_negation = guard.as_boolean_expression().negated();
12556        if merged.contains(&guard.negated())
12557            || merged
12558                .iter()
12559                .map(PreprocessorGuard::as_boolean_expression)
12560                .any(|existing| existing == boolean_negation)
12561        {
12562            return None;
12563        }
12564        merged.insert(guard.clone());
12565    }
12566    Some(merged)
12567}
12568
12569fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
12570    if conditional.kind() == "preproc_ifdef" {
12571        let name = conditional.child_by_field_name("name")?;
12572        let name = node_text(name, source).to_string();
12573        return match conditional.child(0)?.kind() {
12574            "#ifdef" => Some(PreprocessorGuard::Defined(name)),
12575            "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
12576            _ => None,
12577        };
12578    }
12579    let condition = conditional.child_by_field_name("condition")?;
12580    simple_preprocessor_expression_guard(condition, source).or_else(|| {
12581        Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
12582            node_text(condition, source),
12583        )))
12584    })
12585}
12586
12587fn simple_preprocessor_expression_guard(
12588    expression: Node<'_>,
12589    source: &str,
12590) -> Option<PreprocessorGuard> {
12591    match expression.kind() {
12592        "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
12593            node_text(expression, source).to_string(),
12594        ))),
12595        "number_literal" => match node_text(expression, source).trim() {
12596            "0" => Some(PreprocessorGuard::Constant(false)),
12597            "1" => Some(PreprocessorGuard::Constant(true)),
12598            _ => None,
12599        },
12600        "preproc_defined" => {
12601            let identifier = (0..expression.named_child_count())
12602                .filter_map(|index| expression.named_child(index))
12603                .find(|child| child.kind() == "identifier")?;
12604            Some(PreprocessorGuard::Defined(
12605                node_text(identifier, source).to_string(),
12606            ))
12607        }
12608        "unary_expression"
12609            if expression
12610                .child_by_field_name("operator")
12611                .is_some_and(|operator| operator.kind() == "!") =>
12612        {
12613            simple_preprocessor_expression_guard(
12614                expression.child_by_field_name("argument")?,
12615                source,
12616            )
12617            .map(|guard| guard.negated())
12618        }
12619        "parenthesized_expression" => (0..expression.named_child_count())
12620            .filter_map(|index| expression.named_child(index))
12621            .next()
12622            .and_then(|child| simple_preprocessor_expression_guard(child, source)),
12623        "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
12624            expression, source,
12625        ))),
12626        _ => None,
12627    }
12628}
12629
12630fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
12631    match expression.kind() {
12632        "number_literal" => match node_text(expression, source).trim() {
12633            "0" => BooleanGuardExpression::Constant(false),
12634            "1" => BooleanGuardExpression::Constant(true),
12635            _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12636                expression, source,
12637            ))),
12638        },
12639        "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
12640        "preproc_defined" => {
12641            let identifier = (0..expression.named_child_count())
12642                .filter_map(|index| expression.named_child(index))
12643                .find(|child| child.kind() == "identifier");
12644            identifier.map_or_else(
12645                || {
12646                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12647                        expression, source,
12648                    )))
12649                },
12650                |identifier| {
12651                    BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
12652                },
12653            )
12654        }
12655        "unary_expression"
12656            if expression
12657                .child_by_field_name("operator")
12658                .is_some_and(|operator| operator.kind() == "!") =>
12659        {
12660            expression.child_by_field_name("argument").map_or_else(
12661                || {
12662                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12663                        expression, source,
12664                    )))
12665                },
12666                |argument| boolean_preprocessor_expression(argument, source).negated(),
12667            )
12668        }
12669        "parenthesized_expression" => (0..expression.named_child_count())
12670            .filter_map(|index| expression.named_child(index))
12671            .next()
12672            .map_or_else(
12673                || {
12674                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12675                        expression, source,
12676                    )))
12677                },
12678                |child| boolean_preprocessor_expression(child, source),
12679            ),
12680        "binary_expression" => {
12681            let operands = || {
12682                Some((
12683                    boolean_preprocessor_expression(
12684                        expression.child_by_field_name("left")?,
12685                        source,
12686                    ),
12687                    boolean_preprocessor_expression(
12688                        expression.child_by_field_name("right")?,
12689                        source,
12690                    ),
12691                ))
12692            };
12693            match expression
12694                .child_by_field_name("operator")
12695                .map(|operator| operator.kind())
12696            {
12697                Some("&&") => operands().map_or_else(
12698                    || {
12699                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12700                            expression, source,
12701                        )))
12702                    },
12703                    |(left, right)| BooleanGuardExpression::all([left, right]),
12704                ),
12705                Some("||") => operands().map_or_else(
12706                    || {
12707                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12708                            expression, source,
12709                        )))
12710                    },
12711                    |(left, right)| BooleanGuardExpression::any([left, right]),
12712                ),
12713                _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12714                    expression, source,
12715                ))),
12716            }
12717        }
12718        _ => {
12719            BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
12720        }
12721    }
12722}
12723
12724fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
12725    if targets.len() == 1 {
12726        targets.pop()
12727    } else {
12728        None
12729    }
12730}
12731
12732fn callable_declaration_nodes<'tree>(
12733    analyzer: &CppGraphSource<'_>,
12734    prepared: &'tree PreparedSyntaxTree,
12735    candidate: &CodeUnit,
12736) -> Vec<Node<'tree>> {
12737    let root = prepared.tree().root_node();
12738    analyzer
12739        .ranges(candidate)
12740        .into_iter()
12741        .filter_map(|range| {
12742            let mut declaration =
12743                root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
12744            // A declaration an attribute-like macro invocation swallowed lives
12745            // inside the `ERROR` the parser left, not inside a `declaration`
12746            // node, so that envelope is where the climb stops (#2552).
12747            //
12748            // The climb can also run out of ancestors. A collapse that takes a
12749            // whole file with it -- whisper.cpp's `include/whisper.h` from its
12750            // first `WHISPER_DEPRECATED(` to the end (#3094) -- leaves no
12751            // envelope around most of what a region reparse recovers there, and
12752            // the recovered declaration's own `;` can fall outside the envelope
12753            // it does leave, so the range spans the container rather than one
12754            // node. Either way the declaration stands at the scope the climb
12755            // ended in, which is what the callers ask these nodes about.
12756            while !matches!(
12757                declaration.kind(),
12758                "declaration" | "field_declaration" | "function_definition"
12759            ) && !crate::declarations::is_macro_wrapped_declaration_envelope(
12760                declaration,
12761                prepared.source(),
12762            ) {
12763                let Some(parent) = declaration.parent() else {
12764                    break;
12765                };
12766                declaration = parent;
12767            }
12768            Some(declaration)
12769        })
12770        .collect()
12771}
12772
12773fn real_function_definition_ancestor<'tree>(
12774    node: Node<'tree>,
12775    source: &str,
12776) -> Option<Node<'tree>> {
12777    let mut ancestor = node.parent();
12778    while let Some(node) = ancestor {
12779        if node.kind() == "function_definition"
12780            && !is_recovered_declaration_scope_container(node, source)
12781        {
12782            return Some(node);
12783        }
12784        ancestor = node.parent();
12785    }
12786    None
12787}
12788
12789fn callable_declaration_activation_in_file(
12790    analyzer: &CppGraphSource<'_>,
12791    candidate: &CodeUnit,
12792    reference: &CallableReferenceContext<'_>,
12793) -> Option<usize> {
12794    analyzer
12795        .declaration_source_occurrences(candidate)?
12796        .into_iter()
12797        .filter_map(|occurrence| {
12798            let activation = occurrence.fact.callable_activation?;
12799            let guards = cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
12800            callable_guard_requirements_are_visible(
12801                &guards,
12802                occurrence.fact.callable_guard_completion_byte,
12803                reference,
12804            )
12805            .then_some(activation)
12806        })
12807        .min()
12808}
12809
12810fn callable_guard_requirements_are_visible(
12811    required: &HashSet<PreprocessorGuard>,
12812    completion_byte: Option<usize>,
12813    reference: &CallableReferenceContext<'_>,
12814) -> bool {
12815    if required.is_empty() || guard_requirements_hold_at_reference(required, reference.guards()) {
12816        return true;
12817    }
12818    reference.position.as_ref().is_some_and(|position| {
12819        completion_byte.is_some_and(|end| end > 0 && end <= position.byte)
12820            && guards_compatible_at_reference(required, reference.guards())
12821    })
12822}
12823
12824type ReferenceGuardAugmenter<'a> =
12825    dyn Fn(Option<&HashSet<PreprocessorGuard>>) -> Option<HashSet<PreprocessorGuard>> + 'a;
12826
12827/// The reference side of a callable visibility question.
12828///
12829/// Whole-file arity activation has no exact reference position and therefore
12830/// no source guard environment, but retains file-level language and build facts.
12831/// Position-based questions augment their source environment with the same
12832/// effective context as declaration and include proofs.
12833struct CallableReferenceContext<'a> {
12834    effective_guards: &'a ReferenceGuardAugmenter<'a>,
12835    file: &'a ProjectFile,
12836    position: Option<CallableReferencePosition<'a>>,
12837    file_guards: OnceCell<Option<HashSet<PreprocessorGuard>>>,
12838}
12839
12840/// One reference position plus its preprocessor guard environment. The
12841/// environment is computed on demand because most declarations carry no
12842/// non-trivial guard.
12843struct CallableReferencePosition<'a> {
12844    prepared: &'a PreparedSyntaxTree,
12845    byte: usize,
12846    guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
12847}
12848
12849impl CallableReferenceContext<'_> {
12850    fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
12851        let Some(position) = self.position.as_ref() else {
12852            return self
12853                .file_guards
12854                .get_or_init(|| (self.effective_guards)(Some(&HashSet::default())))
12855                .as_ref();
12856        };
12857        position
12858            .guards
12859            .get_or_init(|| {
12860                let raw = position
12861                    .prepared
12862                    .tree()
12863                    .root_node()
12864                    .descendant_for_byte_range(position.byte, position.byte.saturating_add(1))
12865                    .and_then(|node| {
12866                        preprocessor_guard_environment(node, position.prepared.source())
12867                    });
12868                (self.effective_guards)(raw.as_ref())
12869            })
12870            .as_ref()
12871    }
12872}
12873
12874/// The undecidable preprocessor guards `node` stands under, or `None` when its
12875/// conditional context excludes it from `reference`'s translation unit outright:
12876/// a false constant, a condition this analyzer cannot read, or a `__cplusplus`
12877/// branch the reference's language never takes.
12878///
12879/// Collecting one guard per enclosing conditional makes the whole walk a
12880/// conjunction of the declaration's requirements. Which comparison those
12881/// requirements then face depends on where the declaration lives: a same-file
12882/// declaration shares the reference's configuration, so the reference's active
12883/// guards must imply them, while a foreign file resolves its own conditionals
12884/// and only has to stay compatible with the reference.
12885fn callable_declaration_guard_requirements(
12886    node: Node<'_>,
12887    source: &str,
12888    reference: &CallableReferenceContext<'_>,
12889) -> Option<HashSet<PreprocessorGuard>> {
12890    let mut required = HashSet::default();
12891    for guard in callable_preprocessor_ancestor_guards(node, source)? {
12892        match guard {
12893            PreprocessorGuard::Constant(true) => {}
12894            PreprocessorGuard::Constant(false) => return None,
12895            guard => {
12896                required.insert(guard);
12897            }
12898        }
12899    }
12900    guards_compatible_at_reference(&required, reference.guards()).then_some(required)
12901}
12902
12903/// Whether a declaration in the reference's own file is co-active with the
12904/// reference: one translation unit resolves every conditional the same way, so
12905/// the reference's active guards must imply the declaration's requirements.
12906///
12907/// The exception is a declaration in one branch of a completed `#if`/`#else`
12908/// family the reference follows: the reference's own position settles nothing
12909/// about that family, so a compatible branch is one resolution the source
12910/// presents at the reference (#3297), exactly as
12911/// [`VisibilityIndex::external_type_candidate_visible_in_context`] reads a type
12912/// declaration there.
12913fn callable_preprocessor_context_is_visible_for_reference(
12914    node: Node<'_>,
12915    source: &str,
12916    reference: &CallableReferenceContext<'_>,
12917) -> bool {
12918    let Some(required) = callable_declaration_guard_requirements(node, source, reference) else {
12919        return false;
12920    };
12921    if required.is_empty() || guard_requirements_hold_at_reference(&required, reference.guards()) {
12922        return true;
12923    }
12924    reference.position.as_ref().is_some_and(|position| {
12925        guards_compatible_at_reference(&required, reference.guards())
12926            && declaration_follows_completed_family_before_reference(node, source, position.byte)
12927    })
12928}
12929
12930fn flattened_macro_namespace_declaration_matches(
12931    analyzer: &CppGraphSource<'_>,
12932    _cpp: &dyn CppSource,
12933    reference_file: &ProjectFile,
12934    visible_declaration: &CodeUnit,
12935    qualified_candidate: &CodeUnit,
12936    reference_byte: usize,
12937) -> bool {
12938    // Namespace-opening macros can leave tree-sitter unable to retain the
12939    // namespace owner after a later recovery point. In that shape the forward
12940    // declaration is indexed at translation-unit scope, while the definition
12941    // still has its qualified owner. Require all surviving structural evidence
12942    // before treating the declaration as activation for that definition.
12943    if visible_declaration.kind() != qualified_candidate.kind()
12944        || visible_declaration.identifier() != qualified_candidate.identifier()
12945        || visible_declaration.signature() != qualified_candidate.signature()
12946        || !visible_declaration.package_name().is_empty()
12947        || qualified_candidate.package_name().is_empty()
12948    {
12949        return false;
12950    }
12951
12952    let Some(occurrences) = analyzer.declaration_source_occurrences(visible_declaration) else {
12953        return false;
12954    };
12955    occurrences.into_iter().any(|occurrence| {
12956        occurrence
12957            .displaced_namespace_closing_brace
12958            .is_some_and(|closing_brace| {
12959                visible_declaration.source() != reference_file || closing_brace < reference_byte
12960            })
12961    })
12962}
12963
12964pub(crate) fn flattened_macro_namespace_components(
12965    declaration: Node<'_>,
12966    source: &str,
12967) -> Option<Vec<String>> {
12968    flattened_macro_function_namespace_components(declaration, source)
12969        .or_else(|| flattened_macro_error_namespace_components(declaration, source))
12970}
12971
12972fn flattened_macro_function_namespace_components(
12973    declaration: Node<'_>,
12974    source: &str,
12975) -> Option<Vec<String>> {
12976    let body = declaration
12977        .parent()
12978        .filter(|parent| parent.kind() == "compound_statement")?;
12979    let function = body.parent()?;
12980    if function.child_by_field_name("body") != Some(body) {
12981        return None;
12982    }
12983    let namespace_name = recovered_macro_namespace_name(function, source)?;
12984    let mut components = enclosing_namespace_components(declaration, source)?;
12985    components.push(namespace_name);
12986    Some(components)
12987}
12988
12989/// The namespace name a namespace-opening macro token displaced into a
12990/// synthetic `function_definition`, or `None` when `function` is not that
12991/// recovery shape.
12992///
12993/// `ABSL_NAMESPACE_BEGIN` (or `FMT_BEGIN_NAMESPACE`, ...) immediately before
12994/// `namespace x {` leaves tree-sitter with a `function_definition` whose type is
12995/// the macro token, whose declarator is the namespace name behind an `ERROR`
12996/// holding the `namespace` keyword, and whose body spans the whole namespace
12997/// region. The macro-declaration grammar keeps the same envelope clean: the
12998/// token is a `macro_modifier` (itself admitted only where the parse would
12999/// otherwise contain an error) and the keyword occupies the type field. The
13000/// matching `*_NAMESPACE_END` sibling is what separates the recovery artifact
13001/// from a real function definition.
13002fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
13003    if function.kind() != "function_definition" {
13004        return None;
13005    }
13006    let body = function
13007        .child_by_field_name("body")
13008        .filter(|body| body.kind() == "compound_statement")?;
13009    let mut cursor = function.walk();
13010    let prefix = function
13011        .named_children(&mut cursor)
13012        .take_while(|child| child.start_byte() < body.start_byte())
13013        .filter(|child| child.kind() != "comment")
13014        .collect::<Vec<_>>();
13015    let begin_index = prefix.iter().rposition(|child| {
13016        flattened_macro_sentinel_name(*child, source)
13017            .is_some_and(|name| is_namespace_begin_sentinel(&name))
13018    })?;
13019    let mut identifiers = Vec::new();
13020    let mut stack = prefix[begin_index + 1..]
13021        .iter()
13022        .rev()
13023        .copied()
13024        .collect::<Vec<_>>();
13025    while let Some(current) = stack.pop() {
13026        if let Some(identifier) = direct_cpp_identifier_name(current, source) {
13027            identifiers.push(identifier);
13028            continue;
13029        }
13030        let mut cursor = current.walk();
13031        let children = current.named_children(&mut cursor).collect::<Vec<_>>();
13032        stack.extend(children.into_iter().rev());
13033    }
13034    let [keyword, namespace_name] = identifiers.as_slice() else {
13035        return None;
13036    };
13037    // The namespace keyword and the complete sentinel envelope establish the
13038    // identifier's role. Capitalization does not establish a macro binding.
13039    if keyword != "namespace" || namespace_name.is_empty() {
13040        return None;
13041    }
13042    let mut next = function.next_named_sibling();
13043    let next = loop {
13044        let candidate = next?;
13045        next = candidate.next_named_sibling();
13046        if candidate.kind() != "comment" {
13047            break candidate;
13048        }
13049    };
13050    flattened_macro_sentinel_name(next, source)
13051        .is_some_and(|name| is_namespace_end_sentinel(&name))
13052        .then(|| namespace_name.clone())
13053}
13054
13055/// A `function_definition` that exists only because tree-sitter recovered a
13056/// macro-decorated class head or a namespace-opening macro token. A declaration
13057/// in such a body keeps class or namespace scope, so a scope walk must step over
13058/// the wrapper instead of treating the declaration as block local.
13059pub(crate) fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
13060    crate::declarations::is_recovered_exported_class_container(node, source)
13061        || crate::declarations::is_recovered_fragmented_partial_specialization_container(
13062            node, source,
13063        )
13064        || recovered_macro_namespace_name(node, source).is_some()
13065}
13066
13067fn flattened_macro_error_namespace_components(
13068    declaration: Node<'_>,
13069    source: &str,
13070) -> Option<Vec<String>> {
13071    let parent = declaration
13072        .parent()
13073        .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
13074    let mut cursor = parent.walk();
13075    let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
13076    let declaration_index = siblings
13077        .iter()
13078        .position(|candidate| same_node(*candidate, declaration))?;
13079    let begin_index = (0..declaration_index).rev().find(|index| {
13080        flattened_macro_sentinel_name(siblings[*index], source)
13081            .is_some_and(|name| is_namespace_begin_sentinel(&name))
13082    })?;
13083
13084    let significant = siblings[begin_index + 1..declaration_index]
13085        .iter()
13086        .copied()
13087        .filter(|node| node.kind() != "comment")
13088        .collect::<Vec<_>>();
13089    let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
13090        return None;
13091    };
13092    if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
13093        return None;
13094    }
13095    let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
13096    if significant[2..].iter().any(|node| {
13097        flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
13098            is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
13099        })
13100    }) {
13101        return None;
13102    }
13103
13104    let mut saw_namespace_close = false;
13105    for sibling in siblings.iter().skip(declaration_index + 1).copied() {
13106        if sibling.kind() == "comment" {
13107            continue;
13108        }
13109        if !saw_namespace_close {
13110            if direct_unmatched_closing_brace(sibling) {
13111                saw_namespace_close = true;
13112                continue;
13113            }
13114            if flattened_macro_sentinel_name(sibling, source).is_some() {
13115                return None;
13116            }
13117            continue;
13118        }
13119        if !flattened_macro_sentinel_name(sibling, source)
13120            .is_some_and(|name| is_namespace_end_sentinel(&name))
13121        {
13122            return None;
13123        }
13124        let mut components = enclosing_namespace_components(declaration, source)?;
13125        components.push(namespace_name);
13126        return Some(components);
13127    }
13128    None
13129}
13130
13131fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
13132    // At translation-unit scope the trailing `X_NAMESPACE_END` token parses as
13133    // an `expression_statement` with a missing semicolon; inside a namespace
13134    // body the same token stays a bare `type_identifier`. Under the
13135    // macro-declaration grammar the token is wrapped in a `macro_modifier`
13136    // wherever it decorates a declaration head, and a stacked `END`/`BEGIN`
13137    // pair heads the next region's bogus function with the trailing `END` as
13138    // its first modifier child.
13139    let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
13140        node.named_child(0)?
13141    } else {
13142        node
13143    };
13144    let candidates = [
13145        direct_cpp_identifier_name(node, source),
13146        node.child_by_field_name("type")
13147            .and_then(|type_node| direct_cpp_identifier_name(type_node, source)),
13148        macro_modifier_identifier(node)
13149            .map(|identifier| normalize_cpp_whitespace(node_text(identifier, source))),
13150        {
13151            let mut cursor = node.walk();
13152            node.named_children(&mut cursor)
13153                .find(|child| child.kind() != "comment")
13154                .and_then(macro_modifier_identifier)
13155                .map(|identifier| normalize_cpp_whitespace(node_text(identifier, source)))
13156        },
13157    ];
13158    candidates.into_iter().flatten().find(|candidate| {
13159        cpp_export_macro_token(candidate)
13160            && (is_namespace_begin_sentinel(candidate) || is_namespace_end_sentinel(candidate))
13161    })
13162}
13163
13164/// Namespace-opening macros are spelled both ways in the wild:
13165/// `ABSL_NAMESPACE_BEGIN` (abseil, nlohmann) and `FMT_BEGIN_NAMESPACE` (fmt).
13166#[derive(Clone, Copy, PartialEq, Eq)]
13167enum NamespaceSentinel {
13168    Begin,
13169    End,
13170}
13171
13172fn namespace_sentinel(name: &str) -> Option<NamespaceSentinel> {
13173    if name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE") {
13174        Some(NamespaceSentinel::Begin)
13175    } else if name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE") {
13176        Some(NamespaceSentinel::End)
13177    } else {
13178        None
13179    }
13180}
13181
13182fn is_namespace_begin_sentinel(name: &str) -> bool {
13183    namespace_sentinel(name) == Some(NamespaceSentinel::Begin)
13184}
13185
13186fn is_namespace_end_sentinel(name: &str) -> bool {
13187    namespace_sentinel(name) == Some(NamespaceSentinel::End)
13188}
13189
13190fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
13191    if node.kind() != "ERROR" || node.named_child_count() != 1 {
13192        return None;
13193    }
13194    let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
13195    (!cpp_export_macro_token(&name)).then_some(name)
13196}
13197
13198fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
13199    if !matches!(
13200        node.kind(),
13201        "identifier" | "namespace_identifier" | "type_identifier"
13202    ) {
13203        return None;
13204    }
13205    let name = normalize_cpp_whitespace(node_text(node, source));
13206    (!name.is_empty()).then_some(name)
13207}
13208
13209fn guard_requirement_sets_match(
13210    left: &[(usize, HashSet<PreprocessorGuard>)],
13211    right: &[(usize, HashSet<PreprocessorGuard>)],
13212) -> bool {
13213    left.len() == right.len()
13214        && left.iter().all(|(_, left_guards)| {
13215            right
13216                .iter()
13217                .any(|(_, right_guards)| left_guards == right_guards)
13218        })
13219        && right.iter().all(|(_, right_guards)| {
13220            left.iter()
13221                .any(|(_, left_guards)| right_guards == left_guards)
13222        })
13223}
13224
13225pub(crate) fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
13226    node.kind() == "ERROR"
13227        && (0..node.child_count())
13228            .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
13229}
13230
13231/// The preprocessor requirements a scope-level binding (a using declaration or
13232/// directive, or a namespace alias) stands under, or `None` when its guard
13233/// environment cannot be represented.
13234pub fn binding_site_guards(node: Node<'_>, source: &str) -> Option<HashSet<PreprocessorGuard>> {
13235    if callable_preprocessor_context_is_visible(node, source) {
13236        Some(HashSet::default())
13237    } else {
13238        preprocessor_guard_environment(node, source)
13239    }
13240}
13241
13242pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
13243    let mut ancestor = node.parent();
13244    while let Some(parent) = ancestor {
13245        if is_cpp_preprocessor_conditional(parent)
13246            && !is_file_covering_include_guard(parent, source)
13247            && !is_split_cpp_language_linkage_wrapper(parent, node, source)
13248        {
13249            return false;
13250        }
13251        ancestor = parent.parent();
13252    }
13253    true
13254}
13255
13256fn is_split_cpp_language_linkage_wrapper(
13257    conditional: Node<'_>,
13258    descendant: Node<'_>,
13259    source: &str,
13260) -> bool {
13261    if conditional.child_by_field_name("alternative").is_some()
13262        || !matches!(
13263            simple_preprocessor_guard(conditional, source),
13264            Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
13265        )
13266    {
13267        return false;
13268    }
13269    let mut current = descendant.parent();
13270    let linkage = loop {
13271        let Some(node) = current else {
13272            return false;
13273        };
13274        if node == conditional {
13275            return false;
13276        }
13277        if node.kind() == "linkage_specification" {
13278            break node;
13279        }
13280        current = node.parent();
13281    };
13282    if linkage
13283        .child_by_field_name("value")
13284        .is_none_or(|value| node_text(value, source) != "\"C\"")
13285    {
13286        return false;
13287    }
13288    let Some(body) = linkage.child_by_field_name("body") else {
13289        return false;
13290    };
13291    // Walk the body with cursors: an indexed `named_child(i)` restarts from
13292    // the first child, so indexed loops over a header-sized linkage body made
13293    // each call quadratic, and this runs once per declaration inside it
13294    // (#3397).
13295    let mut cursor = body.walk();
13296    let closes_opening_branch = body
13297        .named_children(&mut cursor)
13298        .take_while(|child| child.end_byte() <= descendant.start_byte())
13299        .any(|child| {
13300            child.kind() == "preproc_call"
13301                && child
13302                    .child_by_field_name("directive")
13303                    .is_some_and(|directive| node_text(directive, source) == "#endif")
13304        });
13305    let mut cursor = body.walk();
13306    let reopens_for_closing_brace = body
13307        .named_children(&mut cursor)
13308        .skip_while(|child| child.start_byte() < descendant.end_byte())
13309        .any(|child| {
13310            matches!(
13311                simple_preprocessor_guard(child, source),
13312                Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
13313            ) && {
13314                let mut tokens = child.walk();
13315                child
13316                    .children(&mut tokens)
13317                    .any(|token| token.kind() == "#endif" && token.is_missing())
13318            }
13319        });
13320    closes_opening_branch && reopens_for_closing_brace
13321}
13322
13323/// The argument list a call-shaped node supplies: `f(args)`, `new T(args)`,
13324/// `T{args}` and the member initializer `: field(args)`, whose grammar gives its
13325/// argument list no field name.
13326pub fn call_arguments_node(node: Node<'_>) -> Option<Node<'_>> {
13327    node.child_by_field_name("arguments")
13328        .or_else(|| node.child_by_field_name("parameters"))
13329        .or_else(|| node.child_by_field_name("value"))
13330        .or_else(|| first_named_child_of_kind(node, "argument_list"))
13331        .or_else(|| first_named_child_of_kind(node, "initializer_list"))
13332}
13333
13334pub fn call_arity(node: Node<'_>) -> usize {
13335    call_arguments_node(node)
13336        .map(|args| argument_children(args).count())
13337        .unwrap_or(0)
13338}
13339
13340pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
13341    let recovered_block_arguments = recovered_block_literal_arguments(node);
13342    (0..node.child_count())
13343        .filter_map(move |index| node.child(index))
13344        .filter(|child| child.is_named() && !child.is_extra())
13345        // The `^(` opening a Clang block literal argument survives the
13346        // macro-declaration grammar's bogus function envelope as an ERROR in
13347        // the parameter list. It only opens the block: the parameter
13348        // declaration that follows it already stands for the block argument.
13349        .filter(move |child| node.kind() != "parameter_list" || !block_literal_open_error(*child))
13350        .flat_map(move |child| {
13351            if let Some((raw, left, right)) = recovered_block_arguments
13352                && child == raw
13353            {
13354                [Some(left), Some(right)]
13355            } else {
13356                [Some(child), None]
13357            }
13358        })
13359        .flatten()
13360}
13361
13362/// The `^(` token pair that opens a Clang block literal argument, retained by
13363/// the bogus function envelope's parameter list as an `ERROR`. The block's
13364/// parameter declaration follows it and counts as the one logical argument.
13365fn block_literal_open_error(node: Node<'_>) -> bool {
13366    if node.kind() != "ERROR" || node.is_missing() {
13367        return false;
13368    }
13369    let mut cursor = node.walk();
13370    let tokens = node.children(&mut cursor).collect::<Vec<_>>();
13371    matches!(tokens.as_slice(), [caret, open] if caret.kind() == "^" && open.kind() == "(")
13372}
13373
13374/// Recover the two ordinary C values that the C++ grammar folds into one
13375/// `new_expression` for `callee(new, trailing)`.
13376///
13377/// The malformed node has an exact grammar-owned shape: the anonymous `new`
13378/// token, an extra `ERROR` containing only the comma token, and the trailing
13379/// value in the `type` field.  The caller supplies the compilation-dialect
13380/// proof from [`reference_uses_c_semantics`]; a C++ source therefore never
13381/// reinterprets a real new-expression through this path.
13382pub fn recovered_c_new_expression_arguments(
13383    node: Node<'_>,
13384    uses_c_semantics: bool,
13385) -> Option<[Node<'_>; 2]> {
13386    if !uses_c_semantics || node.kind() != "new_expression" {
13387        return None;
13388    }
13389    let parent = node.parent()?;
13390    if parent.kind() != "argument_list" {
13391        return None;
13392    }
13393    let keyword = node.child(0)?;
13394    let error = node.child(1)?;
13395    let trailing = node.child(2)?;
13396    if node.child(3).is_some()
13397        || keyword.kind() != "new"
13398        || keyword.is_named()
13399        || keyword.child_count() != 0
13400        || error.kind() != "ERROR"
13401        || !error.is_extra()
13402        || error.child_count() != 1
13403        || error.child(0).is_none_or(|comma| comma.kind() != ",")
13404        || node.child_by_field_name("type") != Some(trailing)
13405        || trailing.kind() != "type_identifier"
13406    {
13407        return None;
13408    }
13409    Some([keyword, trailing])
13410}
13411
13412/// The recovered C value covering one focused source range, starting from any
13413/// node within the malformed new-expression.
13414pub fn recovered_c_new_expression_argument_at(
13415    mut node: Node<'_>,
13416    start_byte: usize,
13417    end_byte: usize,
13418    uses_c_semantics: bool,
13419) -> Option<Node<'_>> {
13420    // The recovery only exists for C sources, and the climb below reaches the
13421    // root before it can answer no. `Node::parent` re-descends from the root on
13422    // every step, so leaving the check inside the loop made every scanned node
13423    // of a C++ file pay a full quadratic ancestor walk for an answer settled by
13424    // the file's language (#3097).
13425    if !uses_c_semantics {
13426        return None;
13427    }
13428    loop {
13429        if let Some(arguments) = recovered_c_new_expression_arguments(node, uses_c_semantics) {
13430            return arguments.into_iter().find(|argument| {
13431                argument.start_byte() <= start_byte && end_byte <= argument.end_byte()
13432            });
13433        }
13434        node = node.parent()?;
13435    }
13436}
13437
13438fn recovered_c_keyword_argument_count(
13439    file: &ProjectFile,
13440    call: Node<'_>,
13441    arguments: Node<'_>,
13442    source: &str,
13443) -> usize {
13444    // A C identifier that is a C++ keyword can be displaced twice by the C++
13445    // grammar: first into a direct parameter-list `ERROR(keyword)`, then into
13446    // a direct argument-list `ERROR(',', keyword)`. Match those CST tokens in
13447    // the enclosing C function before restoring the otherwise dropped slot;
13448    // ABI15's collapsed function-head shape is handled by the fallback below.
13449    if !is_c_source_file(file) || arguments.kind() != "argument_list" {
13450        return 0;
13451    }
13452    let mut ancestor = Some(call);
13453    let function = loop {
13454        let Some(current) = ancestor else {
13455            return recovered_c_keyword_argument_count_from_collapsed_function(call, arguments)
13456                .unwrap_or(0);
13457        };
13458        if current.kind() == "function_definition" {
13459            break current;
13460        }
13461        ancestor = current.parent();
13462    };
13463    let Some(parameters) = function
13464        .child_by_field_name("declarator")
13465        .and_then(|declarator| declarator.child_by_field_name("parameters"))
13466    else {
13467        return 0;
13468    };
13469    let displaced_parameter_keywords = (0..parameters.child_count())
13470        .filter_map(|index| parameters.child(index))
13471        .filter(|error| error.kind() == "ERROR")
13472        .filter_map(|error| {
13473            let parameter = error.prev_named_sibling()?;
13474            if parameter.kind() != "parameter_declaration"
13475                || parameter.end_byte() != error.start_byte()
13476                || extract_variable_name(parameter, source).is_some()
13477            {
13478                return None;
13479            }
13480            let mut children = (0..error.child_count())
13481                .filter_map(|index| error.child(index))
13482                .filter(|child| !child.is_extra() && !child.is_missing());
13483            let keyword = children.next()?;
13484            (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
13485                .then_some(keyword)
13486        })
13487        .collect::<Vec<_>>();
13488    if displaced_parameter_keywords.is_empty() {
13489        return 0;
13490    }
13491
13492    (0..arguments.child_count())
13493        .filter_map(|index| arguments.child(index))
13494        .filter(|error| error.kind() == "ERROR" && error.is_extra())
13495        .filter(|error| {
13496            let mut children = (0..error.child_count())
13497                .filter_map(|index| error.child(index))
13498                .filter(|child| !child.is_extra() && !child.is_missing());
13499            let Some(comma) = children.next() else {
13500                return false;
13501            };
13502            let Some(keyword) = children.next() else {
13503                return false;
13504            };
13505            children.next().is_none()
13506                && comma.kind() == ","
13507                && !keyword.is_named()
13508                && keyword.child_count() == 0
13509                && displaced_parameter_keywords
13510                    .iter()
13511                    .any(|parameter| parameter.kind_id() == keyword.kind_id())
13512        })
13513        .count()
13514}
13515
13516/// ABI15 can collapse a C function head into nested `ERROR` nodes when its
13517/// parameter name is a C++ keyword. The call still has a structured body, and
13518/// the outer error retains the function type, name, and opening parenthesis;
13519/// recover the displaced parameter only from those CST relationships. Text
13520/// matching would also accept an unbound call after the collapsed body.
13521fn recovered_c_keyword_argument_count_from_collapsed_function(
13522    call: Node<'_>,
13523    arguments: Node<'_>,
13524) -> Option<usize> {
13525    let argument_keywords = children_iter(arguments)
13526        .filter(|error| error.kind() == "ERROR" && error.is_extra())
13527        .filter_map(|error| {
13528            let mut children =
13529                children_iter(error).filter(|child| !child.is_extra() && !child.is_missing());
13530            let comma = children.next()?;
13531            let keyword = children.next()?;
13532            (children.next().is_none()
13533                && comma.kind() == ","
13534                && !keyword.is_named()
13535                && keyword.child_count() == 0)
13536                .then_some(keyword)
13537        })
13538        .collect::<Vec<_>>();
13539    if argument_keywords.is_empty() {
13540        return Some(0);
13541    }
13542
13543    // The collapsed function keeps its body under the parameter ERROR, while
13544    // ordinary control-flow blocks add compound_statement layers below it.
13545    // Stop before crossing a real callable body, whose parameters are a
13546    // separate binding scope.
13547    let mut ancestor = call.parent();
13548    let body = loop {
13549        let current = ancestor?;
13550        if matches!(current.kind(), "function_definition" | "lambda_expression") {
13551            return None;
13552        }
13553        if current.kind() == "compound_statement"
13554            && current
13555                .parent()
13556                .is_some_and(|parent| parent.kind() == "ERROR")
13557        {
13558            break current;
13559        }
13560        ancestor = current.parent();
13561    };
13562    let parameter_error = body.parent().filter(|parent| parent.kind() == "ERROR")?;
13563    let function_error = parameter_error
13564        .parent()
13565        .filter(|parent| parent.kind() == "ERROR")?;
13566    let function_children = children_iter(function_error).collect::<Vec<_>>();
13567    let parameter_error_index = function_children
13568        .iter()
13569        .position(|child| same_node(*child, parameter_error))?;
13570    // ERROR nodes expose their retained field labels by child index, while
13571    // child_by_field_name does not return their type child in runtime 0.25.
13572    let has_return_type = function_children[..parameter_error_index.saturating_sub(2)]
13573        .iter()
13574        .enumerate()
13575        .any(|(index, child)| {
13576            function_error.field_name_for_child(index as u32) == Some("type")
13577                && !child.is_extra()
13578                && !child.is_missing()
13579                && matches!(
13580                    child.kind(),
13581                    "primitive_type" | "sized_type_specifier" | "type_identifier"
13582                )
13583        });
13584    if parameter_error_index < 2
13585        || !has_return_type
13586        || function_children[parameter_error_index - 1].kind() != "("
13587        || function_children[parameter_error_index - 2].kind() != "identifier"
13588    {
13589        return Some(0);
13590    }
13591
13592    let parameter_children = children_iter(parameter_error).collect::<Vec<_>>();
13593    let body_index = parameter_children
13594        .iter()
13595        .position(|child| same_node(*child, body))?;
13596    let close_parenthesis_index = parameter_children[..body_index]
13597        .iter()
13598        .position(|child| child.kind() == ")")?;
13599    let parameter_keywords = parameter_children[..close_parenthesis_index]
13600        .iter()
13601        .enumerate()
13602        .filter(|(index, child)| {
13603            !child.is_named()
13604                && !child.is_extra()
13605                && !child.is_missing()
13606                && child.child_count() == 0
13607                && argument_keywords
13608                    .iter()
13609                    .any(|keyword| keyword.kind_id() == child.kind_id())
13610                && collapsed_c_parameter_name_position(&parameter_children, *index)
13611        })
13612        .map(|(_, child)| *child)
13613        .collect::<Vec<_>>();
13614    Some(
13615        argument_keywords
13616            .iter()
13617            .filter(|keyword| {
13618                parameter_keywords
13619                    .iter()
13620                    .any(|parameter| parameter.kind_id() == keyword.kind_id())
13621            })
13622            .count(),
13623    )
13624}
13625
13626/// The ABI15 collapsed head retains a parameter's type and pointer declarator
13627/// as direct children. Require that exact segment before accepting an
13628/// anonymous keyword as its name; a keyword after an `=` or attribute must not
13629/// borrow the same recovery.
13630fn collapsed_c_parameter_name_position(children: &[Node<'_>], keyword_index: usize) -> bool {
13631    let segment_start = children[..keyword_index]
13632        .iter()
13633        .rposition(|child| matches!(child.kind(), "(" | ","))
13634        .map_or(0, |index| index + 1);
13635    let mut saw_type = false;
13636    let mut saw_pointer = false;
13637    for child in &children[segment_start..keyword_index] {
13638        if child.kind() == "*" && !child.is_extra() && !child.is_missing() {
13639            saw_pointer = true;
13640            continue;
13641        }
13642        if saw_pointer {
13643            return false;
13644        }
13645        if !child.is_named() || child.is_extra() || child.is_missing() {
13646            return false;
13647        }
13648        if child.kind() == "type_qualifier" {
13649            continue;
13650        }
13651        if matches!(
13652            child.kind(),
13653            "class_specifier"
13654                | "dependent_type"
13655                | "enum_specifier"
13656                | "identifier"
13657                | "primitive_type"
13658                | "qualified_identifier"
13659                | "scoped_identifier"
13660                | "struct_specifier"
13661                | "template_type"
13662                | "type_identifier"
13663                | "union_specifier"
13664        ) {
13665            saw_type = true;
13666            continue;
13667        }
13668        return false;
13669    }
13670    saw_type && saw_pointer
13671}
13672
13673fn recovered_block_literal_arguments<'tree>(
13674    arguments: Node<'tree>,
13675) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
13676    if arguments.kind() != "argument_list" {
13677        return None;
13678    }
13679    let mut raw_arguments = (0..arguments.child_count())
13680        .filter_map(|index| arguments.child(index))
13681        .filter(|child| child.is_named() && !child.is_extra());
13682    let raw = raw_arguments.next()?;
13683    if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
13684        return None;
13685    }
13686
13687    let left = raw.child_by_field_name("left")?;
13688    if left.is_missing() || left.start_byte() == left.end_byte() {
13689        return None;
13690    }
13691    let right = raw.child_by_field_name("right")?;
13692    if right.kind() != "compound_literal_expression"
13693        || right.is_missing()
13694        || right
13695            .child_by_field_name("type")
13696            .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
13697        || right
13698            .child_by_field_name("value")
13699            .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
13700    {
13701        return None;
13702    }
13703    let has_intervening_error = (0..raw.child_count())
13704        .filter_map(|index| raw.child(index))
13705        .any(|child| {
13706            child.kind() == "ERROR"
13707                && !child.is_missing()
13708                && child.start_byte() >= left.end_byte()
13709                && child.end_byte() <= right.start_byte()
13710        });
13711    has_intervening_error.then_some((raw, left, right))
13712}
13713
13714pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
13715    match node.kind() {
13716        "new_expression" => node
13717            .child_by_field_name("type")
13718            .or_else(|| node.named_child(0)),
13719        "compound_literal_expression" => node.child_by_field_name("type"),
13720        "call_expression" => node.child_by_field_name("function"),
13721        _ => None,
13722    }
13723}
13724
13725/// The structured type named by a C-style cast expression.
13726///
13727/// Tree-sitter wraps the actual type syntax in a `type_descriptor`.  Return its
13728/// structured `type` field so resolution retains qualified and nested syntax
13729/// without reparsing source text.
13730pub fn cast_expression_type_node(node: Node<'_>) -> Option<Node<'_>> {
13731    if node.kind() != "cast_expression" {
13732        return None;
13733    }
13734    let descriptor = node.child_by_field_name("type")?;
13735    if descriptor.kind() == "type_descriptor" {
13736        descriptor.child_by_field_name("type")
13737    } else {
13738        Some(descriptor)
13739    }
13740}
13741
13742/// The expression a field access reads its member from.
13743///
13744/// That is normally the node in the `argument` field. A preprocessor
13745/// conditional inside a call's argument list defeats the grammar: tree-sitter
13746/// ends the argument list at the `#if` line, then reads the text that follows
13747/// `#endif` as a field access on the whole call expression and leaves the real
13748/// receiver in an `ERROR` node between the `argument` child and the operator.
13749/// mbedtls writes exactly that at `library/ssl_tls.c:4999`, where
13750/// `ssl->session->encrypt_then_mac` is one conditionally compiled call argument
13751/// (#2982).
13752///
13753/// `.` and `->` read whatever ends immediately before them in either tree, so
13754/// answer from the operator's position instead of trusting the `argument`
13755/// field, and unwrap the single expression a displaced `ERROR` holds.
13756pub fn cpp_field_expression_receiver(field: Node<'_>) -> Option<Node<'_>> {
13757    debug_assert_eq!(field.kind(), "field_expression");
13758    let operator = field.child_by_field_name("operator")?;
13759    let mut cursor = field.walk();
13760    let receiver = field
13761        .named_children(&mut cursor)
13762        .filter(|child| child.end_byte() <= operator.start_byte())
13763        .last()?;
13764    if receiver.kind() != "ERROR" {
13765        return Some(receiver);
13766    }
13767    (receiver.named_child_count() == 1)
13768        .then(|| receiver.named_child(0))
13769        .flatten()
13770}
13771
13772pub fn field_initializer_constructs_target(
13773    node: Node<'_>,
13774    ctx: &ScanCtx<'_>,
13775    owner: &CodeUnit,
13776) -> bool {
13777    // A qualified name in a constructor initializer denotes a base
13778    // subobject constructor (`namespace::Base(args)`), not a member field.  The
13779    // field-initializer grammar exposes the qualified name as one structured
13780    // `qualified_identifier`; resolve its owner through the same lexical type
13781    // machinery used for ordinary C++ type references before considering the
13782    // initializer a hit.  This keeps an unrelated `namespace::Other(...)`, a
13783    // qualified non-constructor member, and an unresolved owner out of the
13784    // target constructor's inverse usage set.
13785    if first_named_child_of_kind(node, "qualified_identifier").is_some() {
13786        return qualified_base_initializer_constructs_target(node, ctx, owner);
13787    }
13788    let Some(name) = node
13789        .child_by_field_name("name")
13790        .or_else(|| first_named_child_of_kind(node, "field_identifier"))
13791        .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
13792    else {
13793        return false;
13794    };
13795    let field_name = node_text(name, ctx.source);
13796    ctx.visibility
13797        .visible_identifier_candidates(ctx.file, field_name)
13798        .filter(|unit| unit.is_field() && unit.identifier() == field_name)
13799        .any(|unit| field_declares_type(unit, ctx, owner) && field_holds_subobject(unit, ctx))
13800}
13801
13802/// Whether a member initializer for `unit` runs a constructor of the type the
13803/// member declares.
13804///
13805/// Only a member that holds the type by value does. `Owner* p` initialized as
13806/// `p(nullptr)` binds a pointer and `Owner& r` as `r(other)` binds a
13807/// reference; neither runs an `Owner` constructor, even though both declare
13808/// `Owner` as the receiver type a member access through them resolves (#3286).
13809/// A member whose declaration the index cannot decode keeps the by-value
13810/// reading, which is what the declared-type match already assumed.
13811fn field_holds_subobject(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
13812    field_initializer_holds_subobject(&ctx.analyzer, ctx.visibility, unit)
13813}
13814
13815fn qualified_base_initializer_constructs_target(
13816    node: Node<'_>,
13817    ctx: &ScanCtx<'_>,
13818    owner: &CodeUnit,
13819) -> bool {
13820    let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
13821        return false;
13822    };
13823    let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
13824        return false;
13825    };
13826    let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
13827        return false;
13828    };
13829    let resolves_target = |components: &[String]| {
13830        matches!(
13831            ctx.visibility.resolve_type_components_lexically_for_target(
13832                &ctx.analyzer,
13833                ctx.file,
13834                components,
13835                is_globally_qualified_cpp_name(qualified),
13836                &lexical_scope,
13837                owner,
13838            ),
13839            LexicalTypeResolution::Resolved { unit, .. }
13840                if same_visible_symbol(&unit, owner)
13841        )
13842    };
13843    if resolves_target(&components) {
13844        return true;
13845    }
13846
13847    // Some real-world code spells a base mem-initializer as
13848    // `Base::Base(args)`. In that structured path the final component repeats
13849    // the constructor name; resolve the preceding type path. The terminal
13850    // identity check prevents an arbitrary qualified member from taking this
13851    // route.
13852    components
13853        .last()
13854        .is_some_and(|terminal| terminal == owner.identifier())
13855        && resolves_target(&components[..components.len() - 1])
13856}
13857
13858fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
13859    ctx.visibility
13860        .field_declared_type_fact(&ctx.analyzer, unit)
13861        .is_some_and(|fact| {
13862            ctx.visibility
13863                .resolves_to_type(&ctx.analyzer, ctx.file, &fact.type_text, owner)
13864                || ctx.visibility.resolves_to_type(
13865                    &ctx.analyzer,
13866                    ctx.file,
13867                    &normalize_field_type_text(&fact.type_text),
13868                    owner,
13869                )
13870        })
13871}
13872
13873pub fn field_declared_binding(
13874    analyzer: &CppGraphSource<'_>,
13875    visibility: &VisibilityIndex<'_>,
13876    visible_from: &ProjectFile,
13877    field: &CodeUnit,
13878) -> Option<CppScanBinding> {
13879    let (normalized, resolved, indirection) =
13880        field_declared_type_binding(analyzer, visibility, visible_from, field)?;
13881    let resolved = resolved
13882        .or_else(|| anonymous_aggregate_field_owner(analyzer, visibility, visible_from, field));
13883    Some(CppScanBinding::from_type_name(
13884        normalized,
13885        resolved,
13886        indirection,
13887    ))
13888}
13889
13890/// Whether a member-initializer `field(args)` runs the declared type's
13891/// constructor: only a member that holds the type by value does. `Owner* p`
13892/// initialized as `p(nullptr)` binds a pointer and `Owner& r` as `r(other)`
13893/// binds a reference; neither runs an `Owner` constructor (#3286). A member
13894/// whose declaration the index cannot decode keeps the by-value reading, the
13895/// same assumption the usage scan's declared-type match makes.
13896pub fn field_initializer_holds_subobject(
13897    analyzer: &CppGraphSource<'_>,
13898    visibility: &VisibilityIndex<'_>,
13899    field: &CodeUnit,
13900) -> bool {
13901    visibility
13902        .field_declared_type_fact(analyzer, field)
13903        .is_none_or(|fact| !fact.binds_indirectly)
13904}
13905
13906/// Resolve the receiver type minted for a named declarator on an anonymous C
13907/// aggregate, such as `struct { int r; } c`. The declaration index preserves
13908/// the aggregate as the nested class `Owner$c`, but the field's type fact has
13909/// no spelling that can name that class. Confirm the anonymous aggregate from
13910/// its parsed declaration, then use the owner's structured child relationship
13911/// to recover the one corresponding receiver type.
13912fn anonymous_aggregate_field_owner(
13913    analyzer: &CppGraphSource<'_>,
13914    visibility: &VisibilityIndex<'_>,
13915    visible_from: &ProjectFile,
13916    field: &CodeUnit,
13917) -> Option<CodeUnit> {
13918    let owner = type_owner_of(analyzer, field)?;
13919    if !owner.is_class() {
13920        return None;
13921    }
13922    let declaration = analyzer.get_source(field, false)?;
13923    let mut parser = Parser::new();
13924    parser
13925        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13926        .ok()?;
13927    let tree = parser.parse(&declaration, None)?;
13928    let mut stack = vec![tree.root_node()];
13929    while let Some(node) = stack.pop() {
13930        if matches!(node.kind(), "declaration" | "field_declaration")
13931            && let Some(type_node) = node
13932                .child_by_field_name("type")
13933                .or_else(|| first_type_child(node))
13934            && matches!(type_node.kind(), "struct_specifier" | "union_specifier")
13935            && type_node.child_by_field_name("name").is_none()
13936            && declared_name_binding(node, type_node, field.identifier(), &declaration).is_some()
13937        {
13938            let matches = visibility
13939                .visible_members_for_owner_name(visible_from, &owner, field.identifier())
13940                .into_iter()
13941                .filter(|child| child.is_class() && child.identifier() == field.identifier())
13942                .collect::<Vec<_>>();
13943            return match matches.as_slice() {
13944                [child] => Some((*child).clone()),
13945                _ => None,
13946            };
13947        }
13948        let mut cursor = node.walk();
13949        stack.extend(node.named_children(&mut cursor));
13950    }
13951    None
13952}
13953
13954/// Resolve an anonymous C aggregate's generated owner from its declaration
13955/// range. Anonymous local structs and unions have no type name to enter into
13956/// the visibility index; declaration extraction gives them a structured class
13957/// identity keyed by the aggregate node's exact CST range instead. Matching
13958/// that range keeps nested aggregates and unrelated generated owners out of
13959/// the result without inspecting source text.
13960pub fn anonymous_aggregate_owner(
13961    analyzer: &CppGraphSource<'_>,
13962    file: &ProjectFile,
13963    node: Node<'_>,
13964) -> Option<CodeUnit> {
13965    if !matches!(node.kind(), "struct_specifier" | "union_specifier")
13966        || node.child_by_field_name("name").is_some()
13967    {
13968        return None;
13969    }
13970    let mut candidates = analyzer
13971        .declarations(file)
13972        .into_iter()
13973        .filter(|candidate| {
13974            candidate.is_class()
13975                && analyzer.ranges(candidate).into_iter().any(|range| {
13976                    range.start_byte == node.start_byte() && range.end_byte == node.end_byte()
13977                })
13978        })
13979        .collect::<Vec<_>>();
13980    candidates.sort_by_key(|candidate| candidate.fq_name());
13981    candidates.dedup();
13982    match candidates.as_slice() {
13983        [candidate] => Some(candidate.clone()),
13984        _ => None,
13985    }
13986}
13987
13988/// Resolve an unqualified-head type reference through C++ block scope.
13989///
13990/// Unqualified lookup searches the enclosing blocks and local class bodies of
13991/// a function before any namespace or member scope of the function itself, so
13992/// a local class hides every same-spelled outer type from the point where it
13993/// is declared. `None` means no enclosing block or local class declares the
13994/// head and ordinary lookup proceeds. A head that a block does declare is
13995/// never answered by an outer type: it resolves to the local class extraction
13996/// minted for that block, or stays `Missing` when no such class exists (a
13997/// local class declared but never defined, or one written where extraction
13998/// mints no identity). C block scope is a separate capability and keeps
13999/// ordinary lookup.
14000pub fn function_local_type_resolution(
14001    analyzer: &CppGraphSource<'_>,
14002    file: &ProjectFile,
14003    node: Node<'_>,
14004    components: &[String],
14005    source: &str,
14006) -> Option<LexicalTypeResolution> {
14007    let (head, rest) = components.split_first()?;
14008    if is_c_source_file(file) {
14009        return None;
14010    }
14011    let declaration = function_local_class_declaration(node, head, source)?;
14012    let Some(mut unit) = function_local_class_unit(analyzer, file, declaration, head, source)
14013    else {
14014        return Some(LexicalTypeResolution::Missing);
14015    };
14016    for component in rest {
14017        let mut nested = analyzer
14018            .declarations(file)
14019            .into_iter()
14020            .filter(|candidate| {
14021                candidate.is_class()
14022                    && candidate.identifier() == component
14023                    && brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
14024                        .is_some_and(|parent| parent == unit.fq_name())
14025            })
14026            .collect::<Vec<_>>();
14027        nested.dedup();
14028        match nested.as_slice() {
14029            [child] => unit = child.clone(),
14030            [] => return Some(LexicalTypeResolution::Missing),
14031            _ => return Some(LexicalTypeResolution::Ambiguous),
14032        }
14033    }
14034    Some(LexicalTypeResolution::Resolved {
14035        components: canonical_cpp_scope_components(&unit),
14036        candidates: vec![unit.clone()],
14037        unit,
14038    })
14039}
14040
14041/// Whether `unit` is a function-local class or a member of one. Extraction
14042/// scopes those through their callable, so a callable segment sits inside the
14043/// identity rather than at its end; no namespace-scope spelling names them.
14044pub fn is_function_local_unit(unit: &CodeUnit) -> bool {
14045    let interner = segment_interner();
14046    unit.fq().segments().split_last().is_some_and(|(_, scope)| {
14047        scope
14048            .iter()
14049            .any(|&segment| interner.resolve(segment).1 == SegmentKind::Member)
14050    })
14051}
14052
14053/// The class-like specifier that block-scope lookup of `name` reaches from
14054/// `node`, innermost scope first, stopping at the outermost function body.
14055///
14056/// A block declares a class at its specifier, so only a declaration that ends
14057/// before the reference is visible there. A local class body sees its own
14058/// injected name and its nested classes: all of them from a member function
14059/// body (the complete-class context), only earlier ones elsewhere in the body.
14060fn function_local_class_declaration<'tree>(
14061    node: Node<'tree>,
14062    name: &str,
14063    source: &str,
14064) -> Option<Node<'tree>> {
14065    // A recovery envelope that tree-sitter parsed as a function body (a
14066    // namespace sentinel, an export-macro class) is declaration scope, not a
14067    // block: the walk ends there, as it does at the translation unit.
14068    let mut path = Vec::new();
14069    let mut child = node;
14070    while let Some(ancestor) = child.parent() {
14071        if ancestor.kind() == "compound_statement"
14072            && ancestor.parent().is_some_and(|function| {
14073                function.kind() == "function_definition"
14074                    && (is_recovered_declaration_scope_container(function, source)
14075                        || crate::graph::extractor::is_malformed_wrapper_function_definition(
14076                            function,
14077                        ))
14078            })
14079        {
14080            break;
14081        }
14082        path.push((ancestor, child));
14083        child = ancestor;
14084    }
14085    let outermost_block = path
14086        .iter()
14087        .rposition(|(ancestor, _)| ancestor.kind() == "compound_statement")?;
14088    let mut in_member_body = false;
14089    for &(ancestor, _) in &path[..=outermost_block] {
14090        match ancestor.kind() {
14091            "compound_statement" => {
14092                if let Some(found) =
14093                    scope_local_class_declaration(ancestor, name, source, Some(node.start_byte()))
14094                {
14095                    return Some(found);
14096                }
14097            }
14098            "function_definition" | "lambda_expression" => in_member_body = true,
14099            "field_declaration_list" => {
14100                let class = ancestor.parent()?;
14101                if local_class_name_is(class, name, source) {
14102                    return Some(class);
14103                }
14104                let before = (!in_member_body).then_some(node.start_byte());
14105                if let Some(found) = scope_local_class_declaration(ancestor, name, source, before) {
14106                    return Some(found);
14107                }
14108                in_member_body = false;
14109            }
14110            _ => {}
14111        }
14112    }
14113    None
14114}
14115
14116/// The last class named `name` that one block or class body declares, among
14117/// those ending before `before` when it is given. A specifier counts as a
14118/// declaration when it has a body or stands alone (`struct W;`); one inside a
14119/// declaration with declarators and no body (`struct W *p;`) is a reference.
14120fn scope_local_class_declaration<'tree>(
14121    scope: Node<'tree>,
14122    name: &str,
14123    source: &str,
14124    before: Option<usize>,
14125) -> Option<Node<'tree>> {
14126    named_children_iter(scope)
14127        .filter_map(scope_level_class_specifier)
14128        .filter(|specifier| before.is_none_or(|before| specifier.end_byte() <= before))
14129        .filter(|specifier| local_class_name_is(*specifier, name, source))
14130        .last()
14131}
14132
14133fn scope_level_class_specifier(statement: Node<'_>) -> Option<Node<'_>> {
14134    let is_class_like = |node: &Node<'_>| {
14135        matches!(
14136            node.kind(),
14137            "class_specifier" | "struct_specifier" | "union_specifier"
14138        )
14139    };
14140    if is_class_like(&statement) {
14141        return Some(statement);
14142    }
14143    if !matches!(
14144        statement.kind(),
14145        "declaration" | "field_declaration" | "type_definition"
14146    ) {
14147        return None;
14148    }
14149    let specifier = statement
14150        .child_by_field_name("type")
14151        .filter(is_class_like)?;
14152    (specifier.child_by_field_name("body").is_some()
14153        || statement.child_by_field_name("declarator").is_none())
14154    .then_some(specifier)
14155}
14156
14157fn local_class_name_is(specifier: Node<'_>, name: &str, source: &str) -> bool {
14158    specifier
14159        .child_by_field_name("name")
14160        .is_some_and(|name_node| {
14161            name_node.kind() == "type_identifier" && node_text(name_node, source) == name
14162        })
14163}
14164
14165/// The unit extraction minted for the local class `declaration` names. A
14166/// forward declaration names the class its own scope defines.
14167fn function_local_class_unit(
14168    analyzer: &CppGraphSource<'_>,
14169    file: &ProjectFile,
14170    declaration: Node<'_>,
14171    name: &str,
14172    source: &str,
14173) -> Option<CodeUnit> {
14174    let definition = if declaration.child_by_field_name("body").is_some() {
14175        declaration
14176    } else {
14177        let mut scope = declaration.parent()?;
14178        if scope.kind() != "compound_statement" && scope.kind() != "field_declaration_list" {
14179            scope = scope.parent()?;
14180        }
14181        named_children_iter(scope)
14182            .filter_map(scope_level_class_specifier)
14183            .filter(|specifier| specifier.child_by_field_name("body").is_some())
14184            .find(|specifier| local_class_name_is(*specifier, name, source))?
14185    };
14186    let mut candidates = analyzer
14187        .declarations(file)
14188        .into_iter()
14189        .filter(|candidate| {
14190            candidate.is_class()
14191                && candidate.identifier() == name
14192                && is_function_local_unit(candidate)
14193                && analyzer.ranges(candidate).into_iter().any(|range| {
14194                    range.start_byte == definition.start_byte()
14195                        && range.end_byte == definition.end_byte()
14196                })
14197        })
14198        .collect::<Vec<_>>();
14199    candidates.dedup();
14200    match candidates.as_slice() {
14201        [candidate] => Some(candidate.clone()),
14202        _ => None,
14203    }
14204}
14205
14206/// The one logical type the candidates name, or why they do not name one.
14207fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
14208    let Some(first) = candidates.first() else {
14209        return Err(TypeCandidateFailure::Unresolvable);
14210    };
14211    if candidates
14212        .iter()
14213        .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
14214    {
14215        Ok((*first).clone())
14216    } else {
14217        Err(TypeCandidateFailure::Ambiguous)
14218    }
14219}
14220
14221fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
14222    logical_type_candidate(candidates).ok()
14223}
14224
14225fn unique_type_candidate_preserving_alias(
14226    analyzer: &CppGraphSource<'_>,
14227    file: &ProjectFile,
14228    candidates: &[&CodeUnit],
14229) -> Option<CodeUnit> {
14230    let first = *candidates.first()?;
14231    if declared_type_alias(analyzer, first) {
14232        return candidates
14233            .iter()
14234            .all(|candidate| {
14235                declared_type_alias(analyzer, candidate)
14236                    && candidate.kind() == first.kind()
14237                    && candidate.fq_name() == first.fq_name()
14238                    && candidate.source() == first.source()
14239            })
14240            .then(|| first.clone());
14241    }
14242    if analyzer.reference_uses_c_semantics(file)
14243        && first.is_class()
14244        && indexed_c_tag_kind(analyzer, first).is_some()
14245    {
14246        let mut full_source = None;
14247        let mut tag_kind = None;
14248        for candidate in candidates.iter().copied() {
14249            let candidate_tag_kind = indexed_c_tag_kind(analyzer, candidate)?;
14250            if tag_kind
14251                .replace(candidate_tag_kind)
14252                .is_some_and(|existing| existing != candidate_tag_kind)
14253            {
14254                return None;
14255            }
14256            if cpp_class_declaration_strength(analyzer, candidate)
14257                == CppClassDeclarationStrength::Full
14258                && full_source
14259                    .replace(candidate.source())
14260                    .is_some_and(|existing| existing != candidate.source())
14261            {
14262                return None;
14263            }
14264        }
14265    }
14266    candidates
14267        .iter()
14268        .all(|candidate| {
14269            !declared_type_alias(analyzer, candidate)
14270                && candidate.kind() == first.kind()
14271                && candidate.fq_name() == first.fq_name()
14272        })
14273        .then(|| first.clone())
14274}
14275
14276pub(crate) fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
14277    brokk_bifrost_core::analyzer::capabilities::provider_declares_type_alias(
14278        analyzer.type_alias_provider(),
14279        unit,
14280    )
14281}
14282
14283pub fn field_declared_type_binding(
14284    analyzer: &CppGraphSource<'_>,
14285    visibility: &VisibilityIndex<'_>,
14286    visible_from: &ProjectFile,
14287    field: &CodeUnit,
14288) -> Option<(String, Option<CodeUnit>, i32)> {
14289    let fact = visibility.field_declared_type_fact(analyzer, field)?;
14290    let normalized = normalize_field_type_text(&fact.type_text);
14291    let primary = visibility.resolve_unique_canonical_type_for_declaration(
14292        analyzer,
14293        visible_from,
14294        field,
14295        &normalized,
14296    );
14297    let resolved = match (primary, fact.template_arguments.as_deref()) {
14298        (Some(primary), Some(arguments)) => visibility
14299            .resolve_template_arguments(visible_from, primary, arguments)
14300            .ok(),
14301        (resolved, None) => resolved,
14302        (None, Some(_)) => None,
14303    };
14304    Some((normalized, resolved, fact.indirection))
14305}
14306
14307fn decode_field_declared_type_fact(
14308    analyzer: &CppGraphSource<'_>,
14309    field: &CodeUnit,
14310) -> Option<DeclaredFieldTypeFact> {
14311    if let Some(facts) = analyzer.declaration_source_properties(field)
14312        && let Some(first) = facts.first().and_then(|fact| fact.field_type.as_ref())
14313        && facts
14314            .iter()
14315            .all(|fact| fact.field_type.as_ref() == Some(first))
14316    {
14317        return Some(first.clone());
14318    }
14319    let Some(declaration) = analyzer.get_source(field, false) else {
14320        return decode_indexed_field_declared_type_fact(analyzer, field);
14321    };
14322    let mut parser = Parser::new();
14323    parser
14324        .set_language(&tree_sitter_cpp::LANGUAGE.into())
14325        .ok()?;
14326    // A field's indexed source is stored without its surrounding class body.
14327    // Give tree-sitter that grammatical context before checking recovery-only
14328    // field shapes such as `PyObject_HEAD Imaging image;`.
14329    let contextual = parse_sentinel(
14330        "struct __bifrost_field_context { ",
14331        SentinelInput::region(&declaration),
14332        " };",
14333    );
14334    let (contextual_declaration, contextual_tree) = (contextual.source, contextual.tree);
14335    let mut stack = vec![contextual_tree.root_node()];
14336    while let Some(node) = stack.pop() {
14337        if let Some(recovered) = recovered_pyobject_head_field(node, &contextual_declaration)
14338            && node_text(recovered.name, &contextual_declaration) == field.identifier()
14339        {
14340            return Some(DeclaredFieldTypeFact {
14341                type_text: node_text(recovered.type_node, &contextual_declaration).to_string(),
14342                indirection: recovered.pointer_depth(),
14343                binds_indirectly: recovered.pointer_depth() > 0,
14344                template_arguments: None,
14345            });
14346        }
14347        if let Some(recovered) =
14348            recovered_function_like_field_declarator(node, &contextual_declaration)
14349            && node_text(recovered.name, &contextual_declaration) == field.identifier()
14350        {
14351            let type_node = node
14352                .child_by_field_name("type")
14353                .or_else(|| first_type_child(node))?;
14354            return Some(DeclaredFieldTypeFact {
14355                type_text: node_text(type_node, &contextual_declaration).to_string(),
14356                indirection: recovered.pointer_depth(),
14357                // A function-like declarator names a callable member, never a
14358                // subobject of the declared return type.
14359                binds_indirectly: true,
14360                template_arguments: cpp_template_reference_arguments(
14361                    type_node,
14362                    &contextual_declaration,
14363                ),
14364            });
14365        }
14366        if let Some(fact) =
14367            decode_declared_field_type_node(node, field.identifier(), &contextual_declaration)
14368        {
14369            return Some(fact);
14370        }
14371        let mut cursor = node.walk();
14372        stack.extend(node.named_children(&mut cursor));
14373    }
14374    let tree = parser.parse(&declaration, None)?;
14375    let mut stack = vec![tree.root_node()];
14376    while let Some(node) = stack.pop() {
14377        if let Some(fact) = decode_declared_field_type_node(node, field.identifier(), &declaration)
14378        {
14379            return Some(fact);
14380        }
14381        let mut cursor = node.walk();
14382        stack.extend(node.named_children(&mut cursor));
14383    }
14384    None
14385}
14386
14387/// Decode a field whose generated owner prevents the ordinary source lookup
14388/// from selecting a standalone declaration. The indexed ranges still point
14389/// into the physical syntax tree, so recover the enclosing declaration from
14390/// that structure and apply the same declarator decoder to it.
14391fn decode_indexed_field_declared_type_fact(
14392    analyzer: &CppGraphSource<'_>,
14393    field: &CodeUnit,
14394) -> Option<DeclaredFieldTypeFact> {
14395    let cpp = analyzer.cpp?;
14396    let prepared = cpp.prepared_syntax(analyzer.token, field.source())?;
14397    let source = prepared.source();
14398    let root = prepared.tree().root_node();
14399    for range in analyzer.ranges(field) {
14400        let end = range.start_byte.saturating_add(1).min(source.len());
14401        let mut current = root.descendant_for_byte_range(range.start_byte, end);
14402        while let Some(node) = current {
14403            if matches!(node.kind(), "declaration" | "field_declaration")
14404                && let Some(fact) =
14405                    decode_declared_field_type_node(node, field.identifier(), source)
14406            {
14407                return Some(fact);
14408            }
14409            current = node.parent();
14410        }
14411    }
14412    None
14413}
14414
14415fn decode_declared_field_type_node(
14416    node: Node<'_>,
14417    field_name: &str,
14418    source: &str,
14419) -> Option<DeclaredFieldTypeFact> {
14420    if !matches!(node.kind(), "declaration" | "field_declaration") {
14421        return None;
14422    }
14423    let type_node = node
14424        .child_by_field_name("type")
14425        .or_else(|| first_type_child(node))?;
14426    cpp_declared_field_type_fact(node, type_node, field_name, source)
14427}
14428
14429pub(crate) fn cpp_declared_field_type_fact(
14430    node: Node<'_>,
14431    type_node: Node<'_>,
14432    field_name: &str,
14433    source: &str,
14434) -> Option<DeclaredFieldTypeFact> {
14435    let binding = declared_name_binding(node, type_node, field_name, source)?;
14436    let declared_type = if matches!(
14437        type_node.kind(),
14438        "class_specifier" | "struct_specifier" | "union_specifier"
14439    ) {
14440        type_node.child_by_field_name("name")
14441    } else {
14442        Some(type_node)
14443    };
14444    Some(DeclaredFieldTypeFact {
14445        type_text: declared_type.map_or_else(
14446            || field_name.to_string(),
14447            |declared_type| node_text(declared_type, source).to_string(),
14448        ),
14449        indirection: binding.pointer_depth,
14450        binds_indirectly: binding.indirect,
14451        template_arguments: declared_type
14452            .and_then(|declared_type| cpp_template_reference_arguments(declared_type, source)),
14453    })
14454}
14455
14456/// True when an alias declarator names a function type.
14457///
14458/// The declarator chain is walked through the `declarator` field, so the
14459/// parameter list -- a sibling field -- is never entered and a parameter's own
14460/// function declarator cannot be mistaken for the alias's.
14461pub(crate) fn declarator_names_function_type(declarator: Node<'_>) -> bool {
14462    let mut current = Some(declarator);
14463    while let Some(node) = current {
14464        match node.kind() {
14465            "function_declarator" | "abstract_function_declarator" => return true,
14466            "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
14467                current = node.named_child(0);
14468            }
14469            _ => current = node.child_by_field_name("declarator"),
14470        }
14471    }
14472    false
14473}
14474
14475fn decode_structured_alias_target(
14476    analyzer: &CppGraphSource<'_>,
14477    unit: &CodeUnit,
14478) -> Option<StructuredAliasTarget> {
14479    let facts = analyzer.declaration_source_properties(unit)?;
14480    let first = facts.first()?.alias_target.as_ref()?;
14481    facts
14482        .iter()
14483        .all(|fact| fact.alias_target.as_ref() == Some(first))
14484        .then(|| first.clone())
14485}
14486
14487pub(crate) fn structured_alias_type_target(
14488    mut type_node: Node<'_>,
14489    source: &str,
14490) -> Option<StructuredAliasTarget> {
14491    while type_node.kind() == "type_descriptor" {
14492        type_node = type_node.child_by_field_name("type")?;
14493    }
14494    if type_node.kind() == "primitive_type" {
14495        return Some(StructuredAliasTarget::Builtin);
14496    }
14497    if matches!(
14498        type_node.kind(),
14499        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
14500    ) {
14501        type_node = type_node.child_by_field_name("name")?;
14502    }
14503    let global = type_node.child_by_field_name("scope").is_none()
14504        && type_node.child(0).is_some_and(|child| child.kind() == "::");
14505    let mut components = Vec::new();
14506    append_structured_type_components(type_node, source, &mut components)?;
14507    let arguments = cpp_template_reference_arguments(type_node, source);
14508    (!components.is_empty()).then_some(StructuredAliasTarget::Named {
14509        components,
14510        global,
14511        arguments,
14512    })
14513}
14514
14515fn append_structured_type_components(
14516    node: Node<'_>,
14517    source: &str,
14518    out: &mut Vec<String>,
14519) -> Option<()> {
14520    match node.kind() {
14521        "identifier" | "namespace_identifier" | "type_identifier" => {
14522            out.push(node_text(node, source).to_string());
14523            Some(())
14524        }
14525        "template_type" | "template_function" => {
14526            append_structured_type_components(node.child_by_field_name("name")?, source, out)
14527        }
14528        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
14529            let (scope, name) = qualified_name_parts(node)?;
14530            if let Some(scope) = scope {
14531                append_structured_type_components(scope, source, out)?;
14532            }
14533            append_structured_type_components(name, source, out)
14534        }
14535        _ => None,
14536    }
14537}
14538
14539/// How the declarator that names a declared entity binds the declaration's
14540/// type.
14541pub(crate) struct DeclaredNameBinding {
14542    /// Pointer declarators between the type and the name.
14543    pub pointer_depth: i32,
14544    /// The name binds the declared type through a pointer or a reference, so
14545    /// the declared entity holds no subobject of that type. An array of the
14546    /// type does hold subobjects, so it is not indirect.
14547    pub indirect: bool,
14548}
14549
14550pub(crate) fn declared_name_binding(
14551    declaration: Node<'_>,
14552    type_node: Node<'_>,
14553    field_name: &str,
14554    source: &str,
14555) -> Option<DeclaredNameBinding> {
14556    let mut stack = Vec::new();
14557    let mut cursor = declaration.walk();
14558    stack.extend(
14559        declaration
14560            .named_children(&mut cursor)
14561            .filter(|child| !same_node(*child, type_node)),
14562    );
14563    while let Some(node) = stack.pop() {
14564        if matches!(node.kind(), "identifier" | "field_identifier")
14565            && node_text(node, source) == field_name
14566        {
14567            let mut binding = DeclaredNameBinding {
14568                pointer_depth: 0,
14569                indirect: false,
14570            };
14571            let mut current = node.parent();
14572            while let Some(parent) = current {
14573                if same_node(parent, declaration) {
14574                    return Some(binding);
14575                }
14576                match parent.kind() {
14577                    "pointer_declarator" => {
14578                        binding.pointer_depth += 1;
14579                        binding.indirect = true;
14580                    }
14581                    "reference_declarator" => binding.indirect = true,
14582                    _ => {}
14583                }
14584                current = parent.parent();
14585            }
14586            return None;
14587        }
14588        let mut cursor = node.walk();
14589        stack.extend(node.named_children(&mut cursor));
14590    }
14591    None
14592}
14593
14594fn normalize_field_type_text(type_text: &str) -> String {
14595    const FIELD_SPECIFIERS: [&str; 8] = [
14596        "extern ",
14597        "static ",
14598        "mutable ",
14599        "constexpr ",
14600        "constinit ",
14601        "inline ",
14602        "volatile ",
14603        "const ",
14604    ];
14605
14606    let mut normalized = normalize_type_text(type_text);
14607    loop {
14608        let Some(stripped) = FIELD_SPECIFIERS
14609            .iter()
14610            .find_map(|specifier| normalized.strip_prefix(specifier))
14611        else {
14612            return normalized;
14613        };
14614        normalized = normalize_type_text(stripped);
14615    }
14616}
14617
14618pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
14619    let Some(type_node) = node.child_by_field_name("type") else {
14620        return false;
14621    };
14622    ctx.visibility.resolves_to_type(
14623        &ctx.analyzer,
14624        ctx.file,
14625        node_text(type_node, ctx.source),
14626        owner,
14627    )
14628}
14629
14630pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
14631    !ctx.analyzer
14632        .declarations(ctx.file)
14633        .into_iter()
14634        .filter(|unit| unit.is_function())
14635        .any(|unit| {
14636            ctx.analyzer.ranges(&unit).iter().any(|range| {
14637                node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
14638            })
14639        })
14640}
14641
14642/// How a `T var ...;` declaration initializes its object.
14643pub enum DeclarationConstructorInitializer<'tree> {
14644    /// Direct initialization, `T var(args)` or `T var{args}`: the argument list
14645    /// the declaration hands the constructor.
14646    Arguments(Node<'tree>),
14647    /// Copy initialization from one expression, `T var = expr`, which supplies a
14648    /// single constructor argument without spelling an argument list.
14649    Expression(Node<'tree>),
14650    /// `T var;`, which names no constructor argument at all.
14651    Empty,
14652}
14653
14654pub fn declaration_constructor_initializer(
14655    node: Node<'_>,
14656) -> DeclarationConstructorInitializer<'_> {
14657    let mut cursor = node.walk();
14658    for child in node.named_children(&mut cursor) {
14659        if child.kind() == "init_declarator" {
14660            let Some(value) = child
14661                .child_by_field_name("value")
14662                .or_else(|| first_named_child_of_kind(child, "initializer_list"))
14663                .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
14664            else {
14665                return DeclarationConstructorInitializer::Empty;
14666            };
14667            return match value.kind() {
14668                "argument_list" | "initializer_list" => {
14669                    DeclarationConstructorInitializer::Arguments(value)
14670                }
14671                "compound_literal_expression" => call_arguments_node(value)
14672                    .map_or(DeclarationConstructorInitializer::Empty, |arguments| {
14673                        DeclarationConstructorInitializer::Arguments(arguments)
14674                    }),
14675                _ => DeclarationConstructorInitializer::Expression(value),
14676            };
14677        }
14678        if let Some(declarator) = declaration_declarator(node, child) {
14679            return declarator_parameters(declarator)
14680                .map_or(DeclarationConstructorInitializer::Empty, |parameters| {
14681                    DeclarationConstructorInitializer::Arguments(parameters)
14682                });
14683        }
14684    }
14685    DeclarationConstructorInitializer::Empty
14686}
14687
14688pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
14689    match declaration_constructor_initializer(node) {
14690        DeclarationConstructorInitializer::Arguments(arguments) => {
14691            argument_children(arguments).count()
14692        }
14693        DeclarationConstructorInitializer::Expression(_) => 1,
14694        DeclarationConstructorInitializer::Empty => 0,
14695    }
14696}
14697
14698/// The parameter list of the innermost declarator, which is where a
14699/// `T var(args)` declaration parsed as a function declarator keeps the
14700/// constructor arguments.
14701fn declarator_parameters(node: Node<'_>) -> Option<Node<'_>> {
14702    let mut current = node;
14703    loop {
14704        if let Some(parameters) = current.child_by_field_name("parameters") {
14705            return Some(parameters);
14706        }
14707        current = current.child_by_field_name("declarator")?;
14708    }
14709}
14710
14711fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
14712    brokk_bifrost_core::analyzer::tree_walk::first_named_descendant_of_kind_inclusive(root, kind)
14713}
14714
14715fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
14716    if node.kind() == "identifier" {
14717        return true;
14718    }
14719    if node.kind() == "parenthesized_expression" {
14720        return false;
14721    }
14722    if node.kind() == "call_expression" {
14723        return node
14724            .child_by_field_name("function")
14725            .is_some_and(|function| function.kind() == "identifier");
14726    }
14727    let mut stack = vec![node];
14728    while let Some(descendant) = stack.pop() {
14729        if descendant != node && descendant.kind() == "parenthesized_expression" {
14730            continue;
14731        }
14732        if descendant.kind() == "identifier" {
14733            return true;
14734        }
14735        if descendant.kind() == "call_expression" {
14736            if descendant
14737                .child_by_field_name("function")
14738                .is_some_and(|function| function.kind() == "identifier")
14739            {
14740                return true;
14741            }
14742            continue;
14743        }
14744        push_named_children_reversed(descendant, &mut stack);
14745    }
14746    false
14747}
14748
14749fn macro_expansion_shape_is_safe(
14750    node: Node<'_>,
14751    source: &str,
14752    parameters: &[String],
14753    environment: &MacroEnvironment,
14754) -> bool {
14755    if matches!(node.kind(), "identifier" | "parenthesized_expression") {
14756        return true;
14757    }
14758    if node.kind() == "call_expression" {
14759        let Some(function) = node.child_by_field_name("function") else {
14760            return true;
14761        };
14762        if function.kind() != "identifier" {
14763            return true;
14764        }
14765        let function_name = node_text(function, source);
14766        if parameters
14767            .iter()
14768            .any(|parameter| parameter == function_name)
14769        {
14770            return false;
14771        }
14772        if !environment.may_bind(function_name) {
14773            return true;
14774        }
14775        let Some(arguments) = node.child_by_field_name("arguments") else {
14776            return false;
14777        };
14778        return argument_children(arguments).all(|argument| {
14779            if argument.kind() == "identifier"
14780                && parameters
14781                    .iter()
14782                    .any(|parameter| parameter == node_text(argument, source))
14783            {
14784                return false;
14785            }
14786            macro_expansion_shape_is_safe(argument, source, parameters, environment)
14787        });
14788    }
14789    let mut stack = vec![node];
14790    while let Some(descendant) = stack.pop() {
14791        if descendant != node {
14792            if descendant.kind() == "parenthesized_expression" {
14793                continue;
14794            }
14795            if descendant.kind() == "call_expression" {
14796                let expands = descendant
14797                    .child_by_field_name("function")
14798                    .filter(|function| function.kind() == "identifier")
14799                    .is_some_and(|function| environment.may_bind(node_text(function, source)));
14800                if expands {
14801                    return false;
14802                }
14803                continue;
14804            }
14805        }
14806        if descendant.kind() == "identifier" {
14807            let identifier = node_text(descendant, source);
14808            if parameters.iter().any(|parameter| parameter == identifier)
14809                || environment.may_bind(identifier)
14810            {
14811                return false;
14812            }
14813        }
14814        push_named_children_reversed(descendant, &mut stack);
14815    }
14816    true
14817}
14818
14819fn collect_structured_include_facts(prepared: &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]> {
14820    let source = prepared.source();
14821    let mut facts = Vec::new();
14822    let mut nodes = vec![prepared.tree().root_node()];
14823    while let Some(node) = nodes.pop() {
14824        if node.kind() == "preproc_include" {
14825            let Some(path) = node
14826                .child_by_field_name("path")
14827                .and_then(|path| crate::imports::include_path_literal_value(path, source))
14828                .map(str::to_owned)
14829            else {
14830                continue;
14831            };
14832            facts.push(StructuredIncludeFact {
14833                start_byte: node.start_byte(),
14834                end_byte: node.end_byte(),
14835                path,
14836            });
14837            continue;
14838        }
14839        push_named_children_reversed(node, &mut nodes);
14840    }
14841    Arc::from(facts.into_boxed_slice())
14842}
14843
14844fn has_unresolved_include_visible_before_in_prepared(
14845    file: &ProjectFile,
14846    prepared: &PreparedSyntaxTree,
14847    include_targets: &IncludeTargetIndex,
14848    facts: &[StructuredIncludeFact],
14849    before_byte: usize,
14850    effective_guards: &ReferenceGuardAugmenter<'_>,
14851) -> bool {
14852    let guards = OnceCell::new();
14853    let reference = CallableReferenceContext {
14854        effective_guards,
14855        file_guards: OnceCell::new(),
14856        file,
14857        position: Some(CallableReferencePosition {
14858            prepared,
14859            byte: before_byte,
14860            guards: &guards,
14861        }),
14862    };
14863    let root = prepared.tree().root_node();
14864    facts
14865        .iter()
14866        .filter(|fact| fact.end_byte <= before_byte)
14867        .any(|fact| {
14868            let node = root
14869                .descendant_for_byte_range(fact.start_byte, fact.end_byte)
14870                .expect("structured include fact range must be in prepared tree");
14871            assert_eq!(
14872                node.kind(),
14873                "preproc_include",
14874                "structured include fact range must identify its include node"
14875            );
14876            callable_preprocessor_context_is_visible_for_reference(
14877                node,
14878                prepared.source(),
14879                &reference,
14880            ) && resolve_include_targets_with_index(file, &fact.path, include_targets).is_empty()
14881        })
14882}
14883
14884fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
14885    let descendant = node;
14886    while let Some(parent) = node.parent() {
14887        if is_cpp_preprocessor_conditional(parent)
14888            && !is_file_covering_include_guard(parent, source)
14889            && preprocessor_conditional_contains_descendant(parent, descendant)
14890        {
14891            return true;
14892        }
14893        node = parent;
14894    }
14895    false
14896}
14897
14898/// The [`OwningPreprocessorConditionals`] of a macro event at `event`.
14899///
14900/// The descendant this walks up from is the one
14901/// [`VisibilityIndex::macro_event_condition_value`] starts from -- the
14902/// innermost node at the event's first byte, not the event node itself --
14903/// because containment compares that descendant's end against the recovered
14904/// conditional boundary, and the two nodes end in different places. An event
14905/// that [`has_preprocessor_conditional_ancestor`] rejects owns nothing: that
14906/// predicate is what has always decided whether an event is conditional at
14907/// all, and answering it first also skips the walk for the ordinary
14908/// unconditional event.
14909fn owning_preprocessor_conditionals(
14910    root: Node<'_>,
14911    event: Node<'_>,
14912    source: &str,
14913) -> OwningPreprocessorConditionals {
14914    if !has_preprocessor_conditional_ancestor(event, source) {
14915        return OwningPreprocessorConditionals::default();
14916    }
14917    let start = event.start_byte();
14918    let descendant = root
14919        .descendant_for_byte_range(start, start.saturating_add(1).min(source.len()))
14920        .expect("a byte inside the parsed tree names a descendant");
14921    let mut owners = Vec::new();
14922    let mut current = descendant.parent();
14923    while let Some(conditional) = current {
14924        if is_cpp_preprocessor_conditional(conditional)
14925            && !is_file_covering_include_guard(conditional, source)
14926            && preprocessor_conditional_contains_descendant(conditional, descendant)
14927        {
14928            owners.push(conditional.start_byte());
14929        }
14930        current = conditional.parent();
14931    }
14932    owners.into_boxed_slice()
14933}
14934
14935fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
14936    node.parent()
14937        .filter(|parent| parent.kind() == "translation_unit")
14938        .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
14939        && is_canonical_include_guard(node, source)
14940}
14941
14942fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
14943    if node.kind() != "preproc_ifdef"
14944        || node
14945            .child(0)
14946            .is_none_or(|directive| directive.kind() != "#ifndef")
14947        || node.child_by_field_name("alternative").is_some()
14948    {
14949        return false;
14950    }
14951    let Some(guard_name) = node.child_by_field_name("name") else {
14952        return false;
14953    };
14954    let mut cursor = node.walk();
14955    node.named_children(&mut cursor)
14956        .find(|child| *child != guard_name && child.kind() != "comment")
14957        .filter(|child| child.kind() == "preproc_def")
14958        .and_then(|definition| definition.child_by_field_name("name"))
14959        .is_some_and(|defined_name| {
14960            node_text(defined_name, source) == node_text(guard_name, source)
14961        })
14962}
14963
14964fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
14965    let mut guard = None;
14966    for child in named_children_iter(root) {
14967        if child.kind() == "comment" || is_pragma_once(child, source) {
14968            continue;
14969        }
14970        if guard.is_none() && is_canonical_include_guard(child, source) {
14971            guard = Some(child);
14972        } else {
14973            return None;
14974        }
14975    }
14976    guard
14977        .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
14978        .map(|name| node_text(name, source).to_string())
14979}
14980
14981fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
14982    if (0..root.named_child_count())
14983        .filter_map(|index| root.named_child(index))
14984        .any(|child| is_pragma_once(child, source))
14985    {
14986        return MacroIncludeProtection::PragmaOnce;
14987    }
14988    top_level_canonical_include_guard_name(root, source)
14989        .map(MacroIncludeProtection::MacroGuard)
14990        .unwrap_or(MacroIncludeProtection::None)
14991}
14992
14993fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
14994    node.kind() == "preproc_call"
14995        && node
14996            .child_by_field_name("directive")
14997            .is_some_and(|directive| node_text(directive, source) == "#pragma")
14998        && node
14999            .child_by_field_name("argument")
15000            .is_some_and(|argument| node_text(argument, source).trim() == "once")
15001}
15002
15003fn parse_preproc_identifier(argument: &str) -> Option<String> {
15004    let parsed = parse_sentinel(
15005        "void __bifrost_undef() { ",
15006        SentinelInput::replacement(argument),
15007        "; }",
15008    );
15009    let (sentinel, tree) = (parsed.source, parsed.tree);
15010    if tree.root_node().has_error() {
15011        return None;
15012    }
15013    let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
15014    // A line comment before the synthetic terminator is a named extra, not
15015    // another expression in the directive argument.
15016    let identifier = single_named_child_matching(statement, |child| !child.is_extra())?;
15017    (identifier.kind() == "identifier").then(|| node_text(identifier, &sentinel).to_string())
15018}
15019
15020pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
15021    match node.kind() {
15022        "identifier" | "field_identifier" => {
15023            let name = node_text(node, source).trim();
15024            (!name.is_empty()).then(|| name.to_string())
15025        }
15026        "abstract_array_declarator"
15027        | "abstract_function_declarator"
15028        | "abstract_parenthesized_declarator"
15029        | "abstract_pointer_declarator"
15030        | "abstract_reference_declarator" => None,
15031        "function_declarator" => node
15032            .child_by_field_name("declarator")
15033            .or_else(|| node.child_by_field_name("name"))
15034            .and_then(|child| extract_variable_name(child, source)),
15035        _ => node
15036            .child_by_field_name("declarator")
15037            .or_else(|| node.child_by_field_name("name"))
15038            .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
15039            .and_then(|child| extract_variable_name(child, source)),
15040    }
15041}
15042
15043/// Whether `file` is proven to use plain-C source semantics.
15044///
15045/// `Language::Cpp` intentionally serves both C and C++. Headers do not carry a
15046/// compilation dialect on their own, so only an exact `.c` source extension is
15047/// sufficient to reinterpret C++-grammar keyword nodes such as `this` as C
15048/// identifiers.
15049///
15050/// The exact-lowercase-`.c` rule itself lives in [`LanguageDialect::for_path`],
15051/// which extraction reads too (a `.c` file is extracted with C tag scope), so
15052/// the doctrine has exactly one definition.
15053pub fn is_c_source_file(file: &ProjectFile) -> bool {
15054    LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
15055}
15056
15057/// Whether tree-sitter parsed the operand of C `sizeof(T)` as an expression
15058/// identifier even though `T` may denote a typedef.
15059///
15060/// The grammar cannot distinguish `sizeof(value)` from `sizeof(Type)` without
15061/// semantic information. Keep this helper structural and narrow; callers must
15062/// still prove a visible type and reject an active ordinary-namespace shadow.
15063pub fn is_c_sizeof_expression_type_candidate(file: &ProjectFile, node: Node<'_>) -> bool {
15064    if !is_c_source_file(file) || node.kind() != "identifier" {
15065        return false;
15066    }
15067    let mut operand = node;
15068    while let Some(parent) = operand.parent().filter(|parent| {
15069        parent.kind() == "parenthesized_expression"
15070            && parent.named_child_count() == 1
15071            && parent.named_child(0) == Some(operand)
15072    }) {
15073        operand = parent;
15074    }
15075    operand.parent().is_some_and(|parent| {
15076        parent.kind() == "sizeof_expression" && parent.child_by_field_name("value") == Some(operand)
15077    })
15078}
15079
15080/// Return the type and member leaves of a C `offsetof` member designator.
15081///
15082/// `offsetof_expression` is a dedicated tree-sitter node, so its two operands
15083/// must be interpreted through their named fields.  In particular, do not
15084/// infer the aggregate from the enclosing lexical scope: an `offsetof` can
15085/// name a member of an unrelated aggregate, including a field promoted from
15086/// an anonymous union.  A missing or unsupported operand is deliberately
15087/// rejected so callers can keep the reference unresolved.
15088pub fn c_offsetof_member_parts(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
15089    if !is_c_offsetof_member_node(node) {
15090        return None;
15091    }
15092    let expression = node.parent()?;
15093    if expression.has_error() {
15094        return None;
15095    }
15096    let closing = expression.child(expression.child_count().saturating_sub(1))?;
15097    if closing.kind() != ")" || closing.is_missing() {
15098        return None;
15099    }
15100    let type_descriptor = expression.child_by_field_name("type")?;
15101    if type_descriptor.kind() != "type_descriptor"
15102        || type_descriptor.is_missing()
15103        || type_descriptor.has_error()
15104    {
15105        return None;
15106    }
15107    let type_specifier = type_descriptor.child_by_field_name("type")?;
15108    if type_specifier.is_missing() || type_specifier.has_error() {
15109        return None;
15110    }
15111    let type_reference = match type_specifier.kind() {
15112        "class_specifier" | "struct_specifier" | "union_specifier" => {
15113            type_specifier.child_by_field_name("name")?
15114        }
15115        _ => type_specifier,
15116    };
15117    (!type_reference.is_missing() && !type_reference.has_error()).then_some((type_reference, node))
15118}
15119
15120/// Whether `node` is the member leaf of an `offsetof_expression`, including a
15121/// malformed type operand.  Callers use this guard to prevent the ordinary
15122/// field-name heuristics from guessing an owner after structured resolution
15123/// has failed.
15124pub fn is_c_offsetof_member_node(node: Node<'_>) -> bool {
15125    node.kind() == "field_identifier"
15126        && node.parent().is_some_and(|parent| {
15127            parent.kind() == "offsetof_expression"
15128                && parent.child_by_field_name("member") == Some(node)
15129        })
15130}
15131
15132/// Admit an ABI 15 template-function shape to type resolution only through
15133/// the structured argument list of a type descriptor. This is syntax evidence;
15134/// callers must still resolve a visible type binding before proving identity.
15135pub fn cpp_template_function_has_type_owner(node: Node<'_>) -> bool {
15136    if node.kind() != "template_function" {
15137        return false;
15138    }
15139    let Some(arguments) = node
15140        .parent()
15141        .filter(|parent| parent.kind() == "template_argument_list")
15142    else {
15143        return false;
15144    };
15145    let Some(owner) = arguments.parent().filter(|parent| {
15146        parent.kind() == "template_type"
15147            && parent.child_by_field_name("arguments") == Some(arguments)
15148    }) else {
15149        return false;
15150    };
15151
15152    let mut current = owner;
15153    while let Some(parent) = current.parent() {
15154        if parent.kind() == "type_descriptor" && parent.child_by_field_name("type") == Some(current)
15155        {
15156            return true;
15157        }
15158        if parent.kind() == "qualified_identifier"
15159            && parent.child_by_field_name("name") == Some(current)
15160        {
15161            current = parent;
15162            continue;
15163        }
15164        return false;
15165    }
15166    false
15167}
15168
15169/// Whether `node` is a template argument name that tree-sitter spelled with
15170/// type syntax.
15171///
15172/// The grammar cannot tell a type argument from a non-type (value) argument, so
15173/// it gives both the same shape:
15174/// `template_argument_list -> type_descriptor -> type_identifier`. In
15175/// `std::array<W, N>` the type `W` and the constant `N` parse identically, and
15176/// so do `std::span<const uint8_t, ED448_LEN>`'s length and a nested type
15177/// member used as a real type argument.
15178///
15179/// This helper reports only the syntactic position. A caller must still prove
15180/// which namespace explains the spelling: forward navigation asks the type
15181/// namespace first and reads the leaf as a value only when no type explains it,
15182/// and the inverse field scan admits the leaf only when no visible type does
15183/// (#2556).
15184pub fn is_type_shaped_template_argument_name(node: Node<'_>) -> bool {
15185    if node.kind() != "type_identifier" {
15186        return false;
15187    }
15188    let Some(descriptor) = node
15189        .parent()
15190        .filter(|parent| parent.kind() == "type_descriptor")
15191    else {
15192        return false;
15193    };
15194    if descriptor.child_by_field_name("type") != Some(node) {
15195        return false;
15196    }
15197    let Some(arguments) = descriptor
15198        .parent()
15199        .filter(|parent| parent.kind() == "template_argument_list")
15200    else {
15201        return false;
15202    };
15203    arguments.parent().is_some_and(|owner| {
15204        matches!(
15205            owner.kind(),
15206            "template_type" | "template_function" | "template_method"
15207        ) && owner.child_by_field_name("arguments") == Some(arguments)
15208    })
15209}
15210
15211/// Whether a reference written in `file` reads C++ source with C semantics.
15212///
15213/// [`is_c_source_file`] answers the half a path settles on its own. The other
15214/// half is a header, which has no dialect of its own: it is read as C exactly
15215/// when every workspace translation unit that provably compiles it compiles it
15216/// as C ([`CppSource::header_uses_c_semantics`], issue #1970).
15217///
15218/// This is the gate for anything that is really about the compilation
15219/// language of the code being read -- which reading of an included header's
15220/// declarations is in scope, whether `this` is an ordinary identifier. It is
15221/// NOT the gate for a question that is genuinely about a `.c` file on disk;
15222/// those keep calling [`is_c_source_file`].
15223pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
15224    is_c_source_file(file) || cpp.header_uses_c_semantics(file)
15225}
15226
15227pub fn is_declarator_node(node: Node<'_>) -> bool {
15228    matches!(
15229        node.kind(),
15230        "identifier"
15231            | "field_identifier"
15232            | "qualified_identifier"
15233            | "scoped_identifier"
15234            | "pointer_declarator"
15235            | "reference_declarator"
15236            | "array_declarator"
15237            | "parenthesized_declarator"
15238            | "function_declarator"
15239    )
15240}
15241
15242/// Returns the declarator represented by one direct child of a declaration.
15243///
15244/// Tree-sitter exposes the first declarator through the `declarator` field,
15245/// but subsequent comma-separated declarators are unfielded direct children:
15246/// `int first, second;` therefore has an `identifier` child for `first` and a
15247/// second, otherwise identical, `identifier` child for `second`. Consumers
15248/// must inspect both shapes or a later declarator can be mistaken for a use of
15249/// an unrelated indexed symbol.
15250pub fn declaration_declarator<'tree>(
15251    declaration: Node<'tree>,
15252    child: Node<'tree>,
15253) -> Option<Node<'tree>> {
15254    if !matches!(
15255        declaration.kind(),
15256        "declaration"
15257            | "field_declaration"
15258            | "parameter_declaration"
15259            | "optional_parameter_declaration"
15260            | "function_definition"
15261            | "type_definition"
15262            | "alias_declaration"
15263            | "template_instantiation"
15264    ) {
15265        return None;
15266    }
15267    if declaration
15268        .child_by_field_name("type")
15269        .is_some_and(|type_node| same_node(type_node, child))
15270    {
15271        return None;
15272    }
15273    if child.kind() == "init_declarator" {
15274        return child.child_by_field_name("declarator");
15275    }
15276    let field = field_name_in_parent(declaration, child);
15277    if (is_declarator_node(child) && matches!(field, Some("declarator") | None))
15278        || (declaration.kind() == "type_definition"
15279            && child.kind() == "type_identifier"
15280            && matches!(field, Some("declarator") | None))
15281    {
15282        Some(child)
15283    } else {
15284        None
15285    }
15286}
15287
15288/// Read the name, declarator, and optional type of a typed local binding.
15289pub fn cpp_binding_parts<'tree>(
15290    node: Node<'tree>,
15291    source: &str,
15292) -> Option<(String, Node<'tree>, Option<Node<'tree>>)> {
15293    let declarator = node.child_by_field_name("declarator")?;
15294    let name = extract_variable_name(declarator, source)?;
15295    let type_node = node
15296        .child_by_field_name("type")
15297        .or_else(|| first_type_child(node));
15298    Some((name, declarator, type_node))
15299}
15300
15301/// One run of a container's children whose parsed namespaces differ from
15302/// their lexical namespaces, with the complete lexical namespace path.
15303#[derive(Clone, Debug, PartialEq, Eq)]
15304pub struct RecoveredNamespaceRegion {
15305    /// Start byte of the first child in the run.
15306    pub start: usize,
15307    /// End byte of the last child in the run.
15308    pub end: usize,
15309    /// The complete enclosing namespace path of the run, outermost first.
15310    /// This can be empty when a parsed namespace extends past its real close.
15311    pub components: Vec<String>,
15312    /// The `namespace Name { ... }` construct that opens each of `components`,
15313    /// parallel to it, from the `namespace` keyword through the close the index
15314    /// proved for its `{`. A proven surviving namespace definition uses its
15315    /// body's direct close; a recovered head, or a namespace body exposed by
15316    /// malformed member recovery, uses the file-global brace stack. A level
15317    /// whose close the index could not prove -- the file never closes it, or
15318    /// the file's brace token stream lost an opening token (#3328) -- carries
15319    /// the head alone. This is the range the declaration walk records for the
15320    /// Module, so a namespace whose head collapsed into an `ERROR` reports
15321    /// where it is written rather than where its first member is (#3309).
15322    pub component_ranges: Vec<Range>,
15323    /// The name token each of `components` is written as, parallel to it. The
15324    /// declaration walk interns this subspan as the Module's name occurrence:
15325    /// the node the walk is visiting when it declares the level belongs to an
15326    /// unrelated construct inside the region, and deriving the name from it
15327    /// paired the level's range with that construct's name token (#4292).
15328    pub component_name_ranges: Vec<Range>,
15329    /// The close the index proved for each level's `{`, parallel to
15330    /// `components`. A node after that close is outside the level (#3633).
15331    /// `None` when the file never closes the level or the walk proved no
15332    /// pairing in the file (#3328); the level then applies to the whole region
15333    /// as a best-effort scope.
15334    pub component_closes: Vec<Option<Range>>,
15335}
15336
15337/// The namespaces C++ parse recovery drops from a file's tree.
15338///
15339/// When tree-sitter cannot parse a construct inside a namespace body it closes
15340/// an inner scope with a MISSING brace, or skips an opening brace into an
15341/// ERROR node. Every real `}` after that then closes one scope too early: a
15342/// class body's `}` closes the namespace, the namespace's own `}` closes its
15343/// parent, and the outermost real closes land in a trailing ERROR node. The
15344/// declarations between a stolen close and the real one keep their byte
15345/// positions but lose their `namespace_definition` ancestors (Catch2's
15346/// `catch_matchers_templated.hpp`, issue #1537).
15347///
15348/// A file-global stack over real AST brace tokens restores both lost and
15349/// overextended namespaces. Missing braces do nothing; error-free subtrees are
15350/// balanced and can be skipped. For each child of a damaged container, compare
15351/// the namespaces still open on that stack with the parsed ancestor path. A
15352/// difference yields a region carrying the complete lexical path, including
15353/// an empty path when recovery swallowed file-scope declarations. Consecutive
15354/// children sharing that path form one [`RecoveredNamespaceRegion`]. The same
15355/// pass retains matching brace positions for declaration partitioning (#3087).
15356/// A file without parse errors needs neither correction nor a brace index.
15357///
15358/// The stack restores the source's nesting only while the tree's brace tokens
15359/// are the source's braces. Severe recovery can break that even when the real
15360/// brace counts balance: in simdjson's `compile_time_json-inl.h`, ABI 15 keeps
15361/// the reflection case labels as `character` nodes but loses the outer
15362/// namespace's closing token. The global stack then cascades the function and
15363/// inner namespace closes into the wrong owners. A surviving
15364/// `namespace_definition` can supply stronger provenance when its body has a
15365/// genuine declaration boundary: its direct real `}` belongs to that
15366/// namespace, while a missing direct close proves that the namespace has no
15367/// close to claim. A body whose direct boundary is itself recovered from a
15368/// class/member error stays with the global fallback. The proven boundaries
15369/// override the global stack only when the file has no unpaired real close. An
15370/// unpaired close proves the stream lost an open at a position the stack cannot
15371/// recover, so all pairing in that file remains invalid:
15372/// [`Self::matching_close_brace`] then answers nothing and each restored level
15373/// keeps the head that writes it. The namespace paths remain, as the
15374/// best-effort structure recovery they are.
15375///
15376/// Every consumer reads a node's namespace path through one authority rule,
15377/// shared by [`Self::enclosing_namespace_levels`] and
15378/// [`Self::restore_enclosing_namespaces`], so declaration collection and both
15379/// lookup directions agree:
15380///
15381/// - Outside every region, the node's parsed `namespace_definition` chain is
15382///   the whole answer.
15383/// - Inside a region, the region's restored levels come first and only the
15384///   parsed ancestors that start inside the region follow them. The region
15385///   overrides a parsed ancestor that starts before it, which is how a
15386///   namespace the tree kept open past its real close gives up the
15387///   declarations after that close (#3087).
15388/// - A restored level stops applying at the close the stack paired with its
15389///   `{`, and so does every level inside it. The walk compares a child with
15390///   the stack before it descends into that child, so a run can reach past a
15391///   close inside its last child. In simdjson's `tests/dom/basictests.cpp` the
15392///   collapsed `namespace type_tests {` on line 1467 closes on line 1811, and
15393///   recovery put that `}` inside a `preproc_else` that runs to line 2505: the
15394///   region that restores `type_tests` covers the whole `preproc_else`, and
15395///   without this check every namespace written after the close inherited
15396///   `type_tests` (#3633). A level whose close the stack did not prove applies
15397///   to the whole region, the best-effort answer #3328 leaves.
15398/// - A region whose restored path is empty keeps exactly the parsed ancestors
15399///   whose own `{` and `}` are both real tokens, wherever they start, and drops
15400///   the others. This holds whether or not the file's pairing is proven.
15401#[derive(Clone, Debug, Default)]
15402pub struct OrphanedNamespaceScopeIndex {
15403    regions: Vec<RecoveredNamespaceRegion>,
15404    /// Open-brace start byte to the close the file-global stack paired with it.
15405    /// Empty when the walk proved the brace token stream incomplete.
15406    brace_closes: HashMap<usize, Range>,
15407}
15408
15409impl OrphanedNamespaceScopeIndex {
15410    pub fn build(root: Node<'_>, source: &str) -> Self {
15411        if !root.has_error() {
15412            return Self::default();
15413        }
15414        /// One namespace level open on the brace stack: the `namespace Name {`
15415        /// head it is written as, and the `{`'s start byte, which is the
15416        /// `brace_closes` key its close lands under. The close is not known
15417        /// while the level is open, so the final ranges are assembled once the
15418        /// walk has paired every brace.
15419        #[derive(Clone, Copy)]
15420        struct OpenHead {
15421            head: Range,
15422            open: usize,
15423        }
15424        /// A [`RecoveredNamespaceRegion`] whose levels still name their open
15425        /// braces rather than their closes.
15426        struct PendingRegion {
15427            start: usize,
15428            end: usize,
15429            components: Vec<String>,
15430            heads: Vec<OpenHead>,
15431            name_ranges: Vec<Range>,
15432        }
15433        struct Frame<'tree> {
15434            node: Node<'tree>,
15435            children: Vec<Node<'tree>>,
15436            next: usize,
15437            parsed_scope: Vec<String>,
15438            run: Option<PendingRegion>,
15439        }
15440        fn frame<'tree>(
15441            node: Node<'tree>,
15442            mut parsed_scope: Vec<String>,
15443            source: &str,
15444        ) -> Frame<'tree> {
15445            if node.kind() == "namespace_definition"
15446                && let Some(name) = node.child_by_field_name("name")
15447            {
15448                let mut components = Vec::new();
15449                if append_cpp_name_components(name, source, &mut components).is_some() {
15450                    parsed_scope.extend(components);
15451                }
15452            }
15453            let mut cursor = node.walk();
15454            Frame {
15455                node,
15456                children: node.children(&mut cursor).collect(),
15457                next: 0,
15458                parsed_scope,
15459                run: None,
15460            }
15461        }
15462        let mut regions = Vec::new();
15463        let mut brace_closes = HashMap::default();
15464        // A surviving namespace_definition may own a direct brace boundary of
15465        // its declaration_list. Keep that evidence separate from the file-
15466        // global stack: recovery can lose an outer namespace close while
15467        // retaining balanced braces from nested constructs, so token order
15468        // alone can assign a real close to the wrong namespace. The helper
15469        // rejects body boundaries exposed by malformed class/member recovery.
15470        let mut namespace_brace_proofs = Vec::new();
15471        // Real closes that arrived with nothing open. Each one proves the tree
15472        // is missing a scope-opening token the source writes, which is what
15473        // makes every pairing in the file unusable as an extent (#3328). The
15474        // complete set is kept, not a count, so a walk that has to be diagnosed
15475        // shows every position the stream went out of sync.
15476        let mut unpaired_closes: Vec<Range> = Vec::new();
15477        // The brace stack belongs to the file, not to a parser frame. A real
15478        // close inside a damaged child can close its parent's namespace. The
15479        // following children must see that removal immediately (#3087).
15480        let mut open = Vec::new();
15481        let mut lexical_scope = Vec::new();
15482        // One head per entry of `lexical_scope`: `namespace a::b {` opens two
15483        // levels and both are written by that one head, exactly as the
15484        // ordinary namespace visit records one range for every level of the
15485        // C++17 shorthand (#1878).
15486        let mut lexical_heads: Vec<OpenHead> = Vec::new();
15487        // One name token per entry of `lexical_scope`: unlike the shared head,
15488        // each level of a `namespace a::b {` shorthand has its own token.
15489        let mut lexical_name_ranges: Vec<Range> = Vec::new();
15490        let mut frames = vec![frame(root, Vec::new(), source)];
15491        // A frame's node is the previous frame's direct child, so the frame
15492        // below answers what Node::parent would without re-descending from the
15493        // root; per-node parent or sibling climbs are quadratic over a wide
15494        // translation unit (#3141).
15495        while !frames.is_empty() {
15496            let parent_node = frames.len().checked_sub(2).map(|index| frames[index].node);
15497            let current = frames.last_mut().expect("frames is non-empty");
15498            if current.next == current.children.len() {
15499                regions.extend(frames.pop().expect("the frame just borrowed").run);
15500                continue;
15501            }
15502            let child = current.children[current.next];
15503            current.next += 1;
15504            match child.kind() {
15505                "{" if !child.is_missing() => {
15506                    regions.extend(current.run.take());
15507                    if let Some(namespace) = parent_node.filter(|parent| {
15508                        parent.kind() == "namespace_definition"
15509                            && parent.child_by_field_name("body") == Some(current.node)
15510                            && child.start_byte() == current.node.start_byte()
15511                    }) && let Some(proof) = namespace_body_brace_proof(current.node)
15512                    {
15513                        namespace_brace_proofs.push((child.start_byte(), namespace, proof));
15514                    }
15515                    // The head is the construct that writes the namespace: the
15516                    // `namespace_definition` node when one survived, and the
15517                    // bare `namespace` keyword when recovery collapsed the head
15518                    // into flat `ERROR` children (#3084).
15519                    let mut named = parent_node.and_then(|parent| {
15520                        namespace_body_name_components(parent, current.node, source)
15521                    });
15522                    if named.is_none() {
15523                        named = recovered_namespace_open_components(
15524                            &current.children[..current.next - 1],
15525                            source,
15526                        );
15527                    }
15528                    open.push((child.start_byte(), lexical_scope.len()));
15529                    if let Some((written_at, components)) = named {
15530                        let head = OpenHead {
15531                            head: Range {
15532                                start_byte: written_at.start_byte(),
15533                                end_byte: child.end_byte(),
15534                                start_line: written_at.start_position().row + 1,
15535                                end_line: child.end_position().row + 1,
15536                            },
15537                            open: child.start_byte(),
15538                        };
15539                        lexical_heads.extend(std::iter::repeat_n(head, components.len()));
15540                        lexical_name_ranges
15541                            .extend(components.iter().map(|(_, name_range)| *name_range));
15542                        lexical_scope
15543                            .extend(components.into_iter().map(|(component, _)| component));
15544                    }
15545                    continue;
15546                }
15547                "}" if !child.is_missing() => {
15548                    regions.extend(current.run.take());
15549                    let close = Range {
15550                        start_byte: child.start_byte(),
15551                        end_byte: child.end_byte(),
15552                        start_line: child.start_position().row + 1,
15553                        end_line: child.end_position().row + 1,
15554                    };
15555                    match open.pop() {
15556                        Some((start, namespace_len)) => {
15557                            debug_assert!(
15558                                start < close.start_byte,
15559                                "a close pairs with an open before it: {start} then {close:?}"
15560                            );
15561                            lexical_scope.truncate(namespace_len);
15562                            lexical_heads.truncate(namespace_len);
15563                            lexical_name_ranges.truncate(namespace_len);
15564                            brace_closes.insert(start, close);
15565                        }
15566                        None => unpaired_closes.push(close),
15567                    }
15568                    continue;
15569                }
15570                _ => {}
15571            }
15572            // The namespace head and body precede their opening brace; their
15573            // own parsed namespace is not yet on the lexical stack. Compare
15574            // the items inside the body, not this header bookkeeping.
15575            if current.node.kind() != "namespace_definition"
15576                && lexical_scope != current.parsed_scope
15577            {
15578                match &mut current.run {
15579                    Some(run) if run.components == lexical_scope => run.end = child.end_byte(),
15580                    run => {
15581                        regions.extend(run.take());
15582                        *run = Some(PendingRegion {
15583                            start: child.start_byte(),
15584                            end: child.end_byte(),
15585                            components: lexical_scope.clone(),
15586                            heads: lexical_heads.clone(),
15587                            name_ranges: lexical_name_ranges.clone(),
15588                        });
15589                    }
15590                }
15591            } else {
15592                regions.extend(current.run.take());
15593            }
15594            // Error-free subtrees are balanced. Keeping them intact also
15595            // leaves their valid nested namespaces to the ordinary ancestry
15596            // walk in restore_enclosing_namespaces. A clean
15597            // namespace-sentinel wrapper (the macro-declaration grammar reads
15598            // it as a function_definition whose type is the literal namespace
15599            // keyword) is the one exception: its body is a statement-context
15600            // compound whose members need the recovered scope.
15601            let descend = child.has_error()
15602                || (child.kind() == "function_definition"
15603                    && child.child_by_field_name("body").is_some_and(|body| {
15604                        namespace_body_name_components(child, body, source).is_some()
15605                    }));
15606            if descend {
15607                let parsed_scope = current.parsed_scope.clone();
15608                frames.push(frame(child, parsed_scope, source));
15609            }
15610        }
15611        // A close with nothing open is only possible when a `{` the source
15612        // writes is not a `{` token of the tree. The stack then paired some
15613        // earlier open with a close that belongs to an inner scope, and
15614        // nothing in the token stream says which pairs absorbed the loss: the
15615        // lost token sits at an unknown position before the close that
15616        // exposed it. Keep no pairing rather than an unprovable extent. An
15617        // open the file never closes is the opposite case and stays: it makes
15618        // no claim, so the levels it writes keep their heads either way.
15619        if !unpaired_closes.is_empty() {
15620            brace_closes.clear();
15621        } else {
15622            // Use AST ownership when the global walk has no unpaired close. A missing
15623            // direct close removes a misleading stack pairing; a real direct
15624            // close replaces any cascade from a damaged descendant. Apply this
15625            // only to the namespace chain containing a proven missing boundary;
15626            // unrelated or ambiguous namespaces retain the global fallback.
15627            let missing_namespaces = namespace_brace_proofs
15628                .iter()
15629                .filter_map(|(_, namespace, proof)| {
15630                    matches!(proof, NamespaceBodyBraceProof::Missing).then_some(namespace.id())
15631                })
15632                .collect::<HashSet<_>>();
15633            for (open, namespace, proof) in namespace_brace_proofs {
15634                if !std::iter::successors(Some(namespace), |node| node.parent())
15635                    .any(|ancestor| missing_namespaces.contains(&ancestor.id()))
15636                {
15637                    continue;
15638                }
15639                match proof {
15640                    NamespaceBodyBraceProof::Real(close) => {
15641                        brace_closes.insert(open, close);
15642                    }
15643                    NamespaceBodyBraceProof::Missing => {
15644                        brace_closes.remove(&open);
15645                    }
15646                }
15647            }
15648        }
15649        // Every surviving brace is paired now, so each level's head can grow to
15650        // the construct it writes. A level whose `{` never closes keeps its
15651        // head: the file ends inside the namespace and there is no real close
15652        // to reach for.
15653        let regions = regions
15654            .into_iter()
15655            .map(|region| {
15656                debug_assert_eq!(
15657                    region.components.len(),
15658                    region.heads.len(),
15659                    "every recovered namespace level carries the head that writes it: {:?}",
15660                    region.components
15661                );
15662                debug_assert_eq!(
15663                    region.components.len(),
15664                    region.name_ranges.len(),
15665                    "every recovered namespace level carries the name token that writes it: {:?}",
15666                    region.components
15667                );
15668                let component_closes: Vec<_> = region
15669                    .heads
15670                    .iter()
15671                    .map(|level| brace_closes.get(&level.open).copied())
15672                    .collect();
15673                let component_ranges = region
15674                    .heads
15675                    .iter()
15676                    .zip(&component_closes)
15677                    .map(|(level, close)| match close {
15678                        Some(close) => Range {
15679                            start_byte: level.head.start_byte,
15680                            end_byte: close.end_byte,
15681                            start_line: level.head.start_line,
15682                            end_line: close.end_line,
15683                        },
15684                        None => level.head,
15685                    })
15686                    .collect();
15687                RecoveredNamespaceRegion {
15688                    start: region.start,
15689                    end: region.end,
15690                    components: region.components,
15691                    component_ranges,
15692                    component_name_ranges: region.name_ranges,
15693                    component_closes,
15694                }
15695            })
15696            .collect();
15697        Self {
15698            regions,
15699            brace_closes,
15700        }
15701    }
15702
15703    /// The real AST brace matching an opening brace in a damaged subtree.
15704    /// Missing tokens and braces inside comments or literals do not participate.
15705    ///
15706    /// `None` when this open has no close in the file, and for every open in a
15707    /// file whose brace token stream lost an opening token: a caller cannot
15708    /// treat a pairing the walk could not prove as an enclosing extent (#3328).
15709    pub fn matching_close_brace(&self, open: usize) -> Option<Range> {
15710        self.brace_closes.get(&open).copied()
15711    }
15712
15713    pub fn is_empty(&self) -> bool {
15714        self.regions.is_empty()
15715    }
15716
15717    /// The bytes this index holds, for the analyzer cache's weight.
15718    pub fn approximate_size(&self) -> usize {
15719        self.regions.iter().fold(
15720            self.brace_closes.len() * std::mem::size_of::<(usize, Range)>(),
15721            |total, region| {
15722                total
15723                    .saturating_add(std::mem::size_of::<RecoveredNamespaceRegion>())
15724                    .saturating_add(region.components.iter().map(String::len).sum::<usize>())
15725                    .saturating_add(region.component_ranges.len() * std::mem::size_of::<Range>())
15726                    .saturating_add(
15727                        region.component_name_ranges.len() * std::mem::size_of::<Range>(),
15728                    )
15729                    .saturating_add(
15730                        region.component_closes.len() * std::mem::size_of::<Option<Range>>(),
15731                    )
15732            },
15733        )
15734    }
15735
15736    /// The innermost recovered region containing `byte`.
15737    pub fn region_at(&self, byte: usize) -> Option<&RecoveredNamespaceRegion> {
15738        self.regions
15739            .iter()
15740            .filter(|region| region.start <= byte && byte < region.end)
15741            .min_by_key(|region| region.end - region.start)
15742    }
15743
15744    /// The enclosing namespaces of `node`, outermost first, restoring the ones
15745    /// parse recovery dropped from its ancestor chain. The one answer both
15746    /// lookup directions and declaration collection use (issue #1537).
15747    pub fn enclosing_namespace_components(&self, node: Node<'_>, source: &str) -> Vec<String> {
15748        self.enclosing_namespace_levels(node, source)
15749            .into_iter()
15750            .map(|(component, ..)| component)
15751            .collect()
15752    }
15753
15754    /// [`Self::enclosing_namespace_components`] with the construct that writes
15755    /// each level and the level's own name token, for the declaration walk
15756    /// that has to record both ranges.
15757    ///
15758    /// A level the brace stack restored carries the ranges this index recorded
15759    /// for its head and name; a surviving `namespace_definition` ancestor
15760    /// carries its own node range and name token, which is what the ordinary
15761    /// namespace visit records.
15762    pub fn enclosing_namespace_levels(
15763        &self,
15764        node: Node<'_>,
15765        source: &str,
15766    ) -> Vec<(String, Range, Range)> {
15767        let mut parsed = Vec::new();
15768        let mut current = node.parent();
15769        while let Some(parent) = current {
15770            if parent.kind() == "namespace_definition"
15771                && let Some(name) = parent.child_by_field_name("name")
15772                && let Some(components) = cpp_name_component_ranges(name, source)
15773            {
15774                let range = node_range(parent);
15775                parsed.push((
15776                    parent.start_byte(),
15777                    components
15778                        .into_iter()
15779                        .map(|(component, name_range)| (component, range, name_range))
15780                        .collect(),
15781                    namespace_definition_has_real_braces(parent),
15782                ));
15783            }
15784            current = parent.parent();
15785        }
15786        parsed.reverse();
15787        self.splice_recovered_path(parsed, node.start_byte(), |region, active_components| {
15788            region
15789                .components
15790                .iter()
15791                .take(active_components)
15792                .cloned()
15793                .zip(
15794                    region
15795                        .component_ranges
15796                        .iter()
15797                        .take(active_components)
15798                        .copied(),
15799                )
15800                .zip(
15801                    region
15802                        .component_name_ranges
15803                        .iter()
15804                        .take(active_components)
15805                        .copied(),
15806                )
15807                .map(|((component, range), name_range)| (component, range, name_range))
15808                .collect()
15809        })
15810    }
15811
15812    /// [`Self::enclosing_namespace_components`] for a caller that has already
15813    /// climbed the ancestor chain: `parsed` lists the node's named
15814    /// `namespace_definition` ancestors outermost first, each with its start
15815    /// byte and whether its own opening and closing braces survived parsing.
15816    /// The answer follows the authority rule on [`OrphanedNamespaceScopeIndex`].
15817    pub fn restore_enclosing_namespaces(
15818        &self,
15819        parsed: Vec<(usize, Vec<String>, bool)>,
15820        node_start: usize,
15821    ) -> Vec<String> {
15822        self.splice_recovered_path(parsed, node_start, |region, active_components| {
15823            region
15824                .components
15825                .iter()
15826                .take(active_components)
15827                .cloned()
15828                .collect()
15829        })
15830    }
15831
15832    /// Splice a node's parsed namespace ancestors onto the path a recovered
15833    /// region restores, by the authority rule on [`OrphanedNamespaceScopeIndex`].
15834    /// `recovered` receives the region and the number of its outermost levels
15835    /// still open at the node, and reads those levels in whatever shape the
15836    /// caller needs.
15837    fn splice_recovered_path<T>(
15838        &self,
15839        parsed: Vec<(usize, Vec<T>, bool)>,
15840        node_start: usize,
15841        recovered: impl FnOnce(&RecoveredNamespaceRegion, usize) -> Vec<T>,
15842    ) -> Vec<T> {
15843        let Some(region) = self.region_at(node_start) else {
15844            return parsed
15845                .into_iter()
15846                .flat_map(|(_, levels, _)| levels)
15847                .collect();
15848        };
15849        debug_assert_eq!(
15850            region.components.len(),
15851            region.component_closes.len(),
15852            "every recovered namespace component carries its close proof: {region:?}"
15853        );
15854        debug_assert_eq!(
15855            region.components.len(),
15856            region.component_name_ranges.len(),
15857            "every recovered namespace component carries its name token: {region:?}"
15858        );
15859        if region.components.is_empty() {
15860            return parsed
15861                .into_iter()
15862                .filter(|(_, _, complete)| *complete)
15863                .flat_map(|(_, levels, _)| levels)
15864                .collect();
15865        }
15866        let active_components = region
15867            .component_closes
15868            .iter()
15869            .take_while(|close| {
15870                close
15871                    .as_ref()
15872                    .is_none_or(|close| node_start < close.end_byte)
15873            })
15874            .count();
15875        recovered(region, active_components)
15876            .into_iter()
15877            .chain(
15878                parsed
15879                    .into_iter()
15880                    .filter(|(start, _, _)| *start >= region.start)
15881                    .flat_map(|(_, levels, _)| levels),
15882            )
15883            .collect()
15884    }
15885}
15886
15887/// Whether a parsed namespace owns real AST tokens for both of its boundary
15888/// braces. Recovery may damage its body while leaving this boundary usable as
15889/// lexical ownership evidence.
15890pub(super) fn namespace_definition_has_real_braces(namespace: Node<'_>) -> bool {
15891    debug_assert_eq!(namespace.kind(), "namespace_definition");
15892    let Some(body) = namespace.child_by_field_name("body") else {
15893        return false;
15894    };
15895    let mut opening = false;
15896    let mut closing = false;
15897    let mut cursor = body.walk();
15898    for child in body.children(&mut cursor) {
15899        match child.kind() {
15900            "{" if !child.is_missing() => opening = true,
15901            "}" if !child.is_missing() => closing = true,
15902            _ => {}
15903        }
15904    }
15905    opening && closing
15906}
15907
15908/// The `namespace` keyword and name components of the namespace a stray `{`
15909/// opens, or `None` when the brace does not follow a namespace head.
15910///
15911/// When a namespace body holds a construct tree-sitter cannot parse, recovery
15912/// can collapse the whole `namespace Name { ... }` into one `ERROR` instead of
15913/// a `namespace_definition`: the head survives as the node's own `namespace`
15914/// keyword, name and `{` tokens, in that order, and everything the namespace
15915/// declares becomes a flat sibling of them (Catch2's `catch_decomposer.hpp`,
15916/// issue #3084). Read the head from those siblings so the brace stack names
15917/// the scope the brace opens. An anonymous namespace has no representable
15918/// name and keeps an opaque scope.
15919///
15920/// `preceding` is the open brace's preceding siblings, nearest last. The
15921/// caller already holds the container's child list; `Node::prev_sibling`
15922/// would re-descend from the root for each step (#3141).
15923fn recovered_namespace_open_components<'tree>(
15924    preceding: &[Node<'tree>],
15925    source: &str,
15926) -> Option<(Node<'tree>, Vec<(String, Range)>)> {
15927    let mut head = Vec::new();
15928    for &sibling in preceding.iter().rev() {
15929        if sibling.kind() != "comment" {
15930            head.push(sibling);
15931            if head.len() == 2 {
15932                break;
15933            }
15934        }
15935    }
15936    let [name, keyword] = head[..] else {
15937        return None;
15938    };
15939    if keyword.kind() != "namespace" {
15940        return None;
15941    }
15942    let components = cpp_name_component_ranges(name, source)?;
15943    (!components.is_empty()).then_some((keyword, components))
15944}
15945
15946/// The written head and name-token ranges of the namespace opened by `body`.
15947/// Ordinary namespace bodies and the two damaged literal heads share the
15948/// existing brace-stack lifetime; a recovered compound body's end is NOT a
15949/// namespace close proof (#5868).
15950fn namespace_body_name_components<'tree>(
15951    parent: Node<'tree>,
15952    body: Node<'tree>,
15953    source: &str,
15954) -> Option<(Node<'tree>, Vec<(String, Range)>)> {
15955    if parent.child_by_field_name("body") != Some(body) {
15956        return None;
15957    }
15958    let (written_at, name) = match (parent.kind(), body.kind()) {
15959        ("namespace_definition", "declaration_list") => {
15960            (parent, parent.child_by_field_name("name")?)
15961        }
15962        ("function_definition", "compound_statement") => {
15963            // The two wrapper shapes are mutually exclusive on child kinds: a
15964            // recovered head has an ERROR where the clean macro-modifier head
15965            // has the name. Recovery inside the body still flags has_error on
15966            // the clean shape, so the shape checks, not has_error, must pick
15967            // the reader.
15968            recovered_literal_namespace_head(parent, body, source)
15969                .or_else(|| macro_modifier_namespace_head(parent, body, source))?
15970        }
15971        _ => return None,
15972    };
15973    let components = cpp_name_component_ranges(name, source)?;
15974    (!components.is_empty()).then_some((written_at, components))
15975}
15976
15977/// The written head and name of the literal namespace a damaged wrapper opens,
15978/// when recovery read the `namespace` keyword as a function declarator.
15979fn recovered_literal_namespace_head<'tree>(
15980    parent: Node<'tree>,
15981    body: Node<'tree>,
15982    source: &str,
15983) -> Option<(Node<'tree>, Node<'tree>)> {
15984    // An opaque prefix can make the parser read the literal namespace
15985    // keyword as a function declarator. Account for every direct token:
15986    // no parameters, attributes, extra names or punctuation may compete.
15987    let mut cursor = parent.walk();
15988    let children = parent
15989        .children(&mut cursor)
15990        .filter(|child| child.kind() != "comment")
15991        .collect::<Vec<_>>();
15992    let [prefix, keyword, error, actual_body] = children.as_slice() else {
15993        return None;
15994    };
15995    if *actual_body != body
15996        || parent.child_by_field_name("type") != Some(*prefix)
15997        || parent.child_by_field_name("declarator") != Some(*keyword)
15998        || prefix.kind() != "type_identifier"
15999        || prefix.is_missing()
16000        || prefix.has_error()
16001        || prefix.child_count() != 0
16002        || keyword.kind() != "identifier"
16003        || keyword.is_missing()
16004        || keyword.has_error()
16005        || keyword.child_count() != 0
16006        || node_text(*keyword, source) != "namespace"
16007        || error.kind() != "ERROR"
16008        || error.is_missing()
16009        || body.is_missing()
16010    {
16011        return None;
16012    }
16013    let mut cursor = error.walk();
16014    let names = error
16015        .children(&mut cursor)
16016        .filter(|child| child.kind() != "comment")
16017        .collect::<Vec<_>>();
16018    let [name] = names.as_slice() else {
16019        return None;
16020    };
16021    if !matches!(name.kind(), "identifier" | "namespace_identifier")
16022        || name.is_missing()
16023        || name.has_error()
16024        || name.child_count() != 0
16025    {
16026        return None;
16027    }
16028    let open = body.child(0)?;
16029    if open.kind() != "{"
16030        || open.is_missing()
16031        || open.has_error()
16032        || open.start_byte() != body.start_byte()
16033    {
16034        return None;
16035    }
16036    // Keep the literal keyword's provenance, never the unrelated prefix
16037    // or the prematurely closed function envelope (#3309, #4292).
16038    Some((*keyword, *name))
16039}
16040
16041/// The written head and name of the literal namespace a macro-modifier
16042/// wrapper opens, when the macro-declaration grammar read the same construct
16043/// without recovery.
16044fn macro_modifier_namespace_head<'tree>(
16045    parent: Node<'tree>,
16046    body: Node<'tree>,
16047    source: &str,
16048) -> Option<(Node<'tree>, Node<'tree>)> {
16049    // The macro-declaration grammar reads the same construct without
16050    // recovery: the opaque prefix is a `macro_modifier`, the literal
16051    // `namespace` keyword occupies the type field, and the namespace
16052    // name is the declarator. The keyword cannot be a type, so its
16053    // presence in the type field is the recovery evidence the older
16054    // grammar's `ERROR` carried; account for every direct token the
16055    // same way.
16056    let mut cursor = parent.walk();
16057    let children = parent
16058        .children(&mut cursor)
16059        .filter(|child| child.kind() != "comment")
16060        .collect::<Vec<_>>();
16061    let [prefix, keyword, name, actual_body] = children.as_slice() else {
16062        return None;
16063    };
16064    if *actual_body != body
16065        || parent.child_by_field_name("type") != Some(*keyword)
16066        || parent.child_by_field_name("declarator") != Some(*name)
16067        || prefix.kind() != "macro_modifier"
16068        || prefix.is_missing()
16069        || prefix.has_error()
16070        || macro_modifier_identifier(*prefix).is_none()
16071        || keyword.kind() != "type_identifier"
16072        || keyword.is_missing()
16073        || keyword.has_error()
16074        || keyword.child_count() != 0
16075        || node_text(*keyword, source) != "namespace"
16076        || !matches!(name.kind(), "identifier" | "namespace_identifier")
16077        || name.is_missing()
16078        || name.has_error()
16079        || name.child_count() != 0
16080        || body.is_missing()
16081    {
16082        return None;
16083    }
16084    let open = body.child(0)?;
16085    if open.kind() != "{"
16086        || open.is_missing()
16087        || open.has_error()
16088        || open.start_byte() != body.start_byte()
16089    {
16090        return None;
16091    }
16092    // Keep the literal keyword's provenance, never the unrelated prefix
16093    // or the prematurely closed function envelope (#3309, #4292).
16094    Some((*keyword, *name))
16095}
16096
16097#[derive(Clone, Copy)]
16098enum NamespaceBodyBraceProof {
16099    Real(Range),
16100    Missing,
16101}
16102
16103/// Prove a namespace body's boundary from its direct AST children. Require the
16104/// body's boundary as well as direct ownership, so a stray brace inside a
16105/// recovered body cannot become namespace ownership evidence. Unknown is
16106/// distinct from a proven missing token: unknown leaves the global pairing in
16107/// place, while a direct MISSING `}` removes a misleading pairing.
16108fn namespace_body_brace_proof(body: Node<'_>) -> Option<NamespaceBodyBraceProof> {
16109    if body.kind() != "declaration_list" {
16110        return None;
16111    }
16112    let mut cursor = body.walk();
16113    let children = body.children(&mut cursor).collect::<Vec<_>>();
16114    let close_index = children
16115        .iter()
16116        .rposition(|child| child.kind() == "}" && child.end_byte() == body.end_byte())?;
16117    let close = children[close_index];
16118    if close.is_missing() {
16119        return Some(NamespaceBodyBraceProof::Missing);
16120    }
16121    let namespace_terminator_sibling = body
16122        .parent()
16123        .filter(|namespace| namespace.kind() == "namespace_definition")
16124        .and_then(|namespace| namespace.next_sibling())
16125        .is_some_and(|sibling| {
16126            sibling.kind() == ";"
16127                || (sibling.kind() == "expression_statement"
16128                    && sibling.child_count() == 1
16129                    && sibling.child(0).is_some_and(|child| child.kind() == ";"))
16130        });
16131    // A recovered class close can become the namespace body boundary while
16132    // its class terminator remains as a sibling semicolon. A malformed class
16133    // can also leak its final expression or label directly into the namespace
16134    // body. Neither shape proves that this `}` belongs to the namespace.
16135    if namespace_terminator_sibling
16136        || children
16137            .get(close_index + 1)
16138            .is_some_and(|sibling| sibling.kind() == ";")
16139        || children[..close_index]
16140            .iter()
16141            .rev()
16142            .find(|child| !child.is_extra() && child.kind() != "{")
16143            .is_some_and(|previous| {
16144                matches!(
16145                    previous.kind(),
16146                    "expression_statement" | "labeled_statement"
16147                )
16148            })
16149    {
16150        return None;
16151    }
16152    Some(NamespaceBodyBraceProof::Real(node_range(close)))
16153}
16154
16155#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16156pub enum RecoveredDeclaratorTypeContext {
16157    Declaration,
16158    FunctionDefinition,
16159    Parameter,
16160}
16161
16162/// Recognize a real type displaced into a qualified declarator by parser
16163/// recovery.
16164///
16165/// Tree-sitter parses `API Result *make(Arg);` as if `API` were the declared
16166/// type and `Result` were the scope of a qualified declarator with a missing
16167/// `::`. A template return such as `API Result<T> make()` uses a
16168/// `template_type` for the same recovered scope. The same recovery occurs for
16169/// macro-prefixed definitions, extern variables, and macro-decorated
16170/// parameters (`f(MACRO T* p)`, where the parameter's own `type` field takes
16171/// the macro). Keep this intentionally structural: the recovered scope must
16172/// have the grammar's missing separator, the qualified node must occupy the
16173/// declaration's declarator chain, a separate nonempty type must occupy the
16174/// normal type field, and the recovered name must unwrap to a real declarator
16175/// name.
16176pub fn recovered_macro_decorated_declarator_type(
16177    node: Node<'_>,
16178) -> Option<RecoveredDeclaratorTypeContext> {
16179    recovered_macro_decorated_type_node(node).map(|(_, context)| context)
16180}
16181
16182/// Return the declaration/function `type` displaced by a macro-shaped
16183/// qualified declarator, together with the enclosing declaration context.
16184/// Callers use the macro scope only as structural admission evidence; the
16185/// returned node is the real type reference to resolve and record.
16186pub fn recovered_macro_decorated_type_node(
16187    node: Node<'_>,
16188) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
16189    if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
16190        return None;
16191    }
16192    let qualified = node.parent()?;
16193    if qualified.kind() != "qualified_identifier"
16194        || qualified.child_by_field_name("scope") != Some(node)
16195        || !(0..qualified.child_count())
16196            .filter_map(|index| qualified.child(index))
16197            .any(|child| child.kind() == "::" && child.is_missing())
16198    {
16199        return None;
16200    }
16201    if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
16202        return None;
16203    }
16204
16205    let (declaration, context) = recovered_declarator_container(qualified)?;
16206    let type_node = declaration
16207        .child_by_field_name("type")
16208        .filter(|type_node| {
16209            *type_node != qualified
16210                && !type_node.is_missing()
16211                && type_node.start_byte() != type_node.end_byte()
16212        })?;
16213    Some((type_node, context))
16214}
16215
16216/// The macro-declaration grammar's clean reading of the head
16217/// [`recovered_macro_decorated_type_node`] recovers: `API static inline T
16218/// make()` parses as a leading `macro_modifier` and `T` as the declaration's
16219/// `type`. Accepts the same type kinds and declaration containers as the
16220/// recovered form, so a rule keyed on that macro-decorated head keeps its
16221/// scope across both grammars.
16222pub fn macro_modifier_decorated_type_node(node: Node<'_>) -> bool {
16223    matches!(node.kind(), "type_identifier" | "template_type")
16224        && node.parent().is_some_and(|declaration| {
16225            matches!(
16226                declaration.kind(),
16227                "declaration"
16228                    | "function_definition"
16229                    | "parameter_declaration"
16230                    | "optional_parameter_declaration"
16231            ) && declaration.child_by_field_name("type") == Some(node)
16232                && declaration.child_by_field_name("declarator").is_some()
16233                && crate::declarations::cpp_declaration_type_follows_macro_modifier(
16234                    declaration,
16235                    node,
16236                )
16237        })
16238}
16239
16240fn recovered_declarator_container(
16241    mut declarator: Node<'_>,
16242) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
16243    loop {
16244        let parent = declarator.parent()?;
16245        if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
16246            return Some((
16247                parent
16248                    .parent()
16249                    .filter(|declaration| declaration.kind() == "declaration")?,
16250                RecoveredDeclaratorTypeContext::Declaration,
16251            ));
16252        }
16253        if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
16254            return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
16255        }
16256        if parent.kind() == "function_definition"
16257            && has_field_child(parent, "declarator", declarator)
16258        {
16259            return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
16260        }
16261        // `f(MACRO T* p)` recovers exactly like `MACRO T *make(...)` does, one
16262        // level down: the parameter's `type` field takes the macro token and
16263        // the real type `T` becomes the recovered scope of the declarator.
16264        // Declining here left every xxhash `XXH_NOESCAPE` parameter with no
16265        // candidate at all (#1830).
16266        if matches!(
16267            parent.kind(),
16268            "parameter_declaration" | "optional_parameter_declaration"
16269        ) && has_field_child(parent, "declarator", declarator)
16270        {
16271            return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
16272        }
16273        if !matches!(
16274            parent.kind(),
16275            "array_declarator"
16276                | "function_declarator"
16277                | "parenthesized_declarator"
16278                | "pointer_declarator"
16279                | "pointer_type_declarator"
16280                | "reference_declarator"
16281        ) || !has_field_child(parent, "declarator", declarator)
16282        {
16283            return None;
16284        }
16285        declarator = parent;
16286    }
16287}
16288
16289fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
16290    brokk_bifrost_core::analyzer::structural::adapter_helpers::is_field_of(parent, target, field)
16291}
16292
16293fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
16294    loop {
16295        if node.is_missing() || node.start_byte() == node.end_byte() {
16296            return false;
16297        }
16298        match node.kind() {
16299            "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
16300                return true;
16301            }
16302            "array_declarator"
16303            | "function_declarator"
16304            | "parenthesized_declarator"
16305            | "pointer_declarator"
16306            | "pointer_type_declarator"
16307            | "reference_declarator" => {
16308                let Some(declarator) = node.child_by_field_name("declarator") else {
16309                    return false;
16310                };
16311                node = declarator;
16312            }
16313            _ => return false,
16314        }
16315    }
16316}
16317
16318/// Aggregate-owner proof for a structurally recognized designated initializer.
16319pub enum DesignatedInitializerOwner {
16320    Resolved(CodeUnit),
16321    Unresolved,
16322}
16323
16324enum InitializerOwnerStep {
16325    Field(String),
16326    AggregateWrapper,
16327}
16328
16329/// Recognize a designated-initializer field and, when possible, resolve its
16330/// aggregate owner.
16331///
16332/// Covers both the grammar's ordinary `field_designator` shape and the exact
16333/// recovery used for `.field = value` after a preprocessor-split array
16334/// initializer. Ordered multi-component designators follow each preceding
16335/// field's declared aggregate type. `None` means the node is not a designator
16336/// at all; an unresolved designator remains classified so callers cannot fall
16337/// through to unrelated global/member heuristics.
16338pub fn designated_initializer_owner(
16339    analyzer: &CppGraphSource<'_>,
16340    visibility: &VisibilityIndex<'_>,
16341    file: &ProjectFile,
16342    source: &str,
16343    node: Node<'_>,
16344) -> Option<DesignatedInitializerOwner> {
16345    if let Some(designator) = node
16346        .parent()
16347        .filter(|parent| parent.kind() == "field_designator")
16348    {
16349        let pair = designator.parent()?;
16350        if pair.kind() != "initializer_pair" {
16351            return None;
16352        }
16353        let mut cursor = pair.walk();
16354        let designators = pair
16355            .children_by_field_name("designator", &mut cursor)
16356            .collect::<Vec<_>>();
16357        let position = designators
16358            .iter()
16359            .position(|candidate| same_node(*candidate, designator))?;
16360        let initializer = pair.parent()?;
16361        if initializer.kind() != "initializer_list" {
16362            return None;
16363        }
16364        let mut owner = initializer_list_owner(analyzer, visibility, file, source, initializer);
16365        for prior in &designators[..position] {
16366            let field = prior
16367                .child_by_field_name("field")
16368                .or_else(|| first_named_child_of_kind(*prior, "field_identifier"))?;
16369            owner = owner.and_then(|owner| {
16370                initializer_field_owner(analyzer, visibility, file, owner, node_text(field, source))
16371            });
16372        }
16373        return Some(classified_designated_owner(owner));
16374    }
16375
16376    let init_declarator = node.parent()?;
16377    if init_declarator.child_by_field_name("declarator") != Some(node)
16378        || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
16379    {
16380        return None;
16381    }
16382    Some(classified_designated_owner(declaration_owner(
16383        analyzer,
16384        visibility,
16385        file,
16386        source,
16387        init_declarator.parent()?,
16388    )))
16389}
16390
16391fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
16392    owner.map_or(
16393        DesignatedInitializerOwner::Unresolved,
16394        DesignatedInitializerOwner::Resolved,
16395    )
16396}
16397
16398fn initializer_list_owner(
16399    analyzer: &CppGraphSource<'_>,
16400    visibility: &VisibilityIndex<'_>,
16401    file: &ProjectFile,
16402    source: &str,
16403    initializer: Node<'_>,
16404) -> Option<CodeUnit> {
16405    let mut current = initializer;
16406    let mut steps = Vec::new();
16407    loop {
16408        let parent = current.parent()?;
16409        match parent.kind() {
16410            "initializer_pair" if parent.child_by_field_name("value") == Some(current) => {
16411                let designator = parent.child_by_field_name("designator")?;
16412                let step = designator
16413                    .child_by_field_name("field")
16414                    .or_else(|| first_named_child_of_kind(designator, "field_identifier"))
16415                    .map(|field| InitializerOwnerStep::Field(node_text(field, source).to_string()))
16416                    .unwrap_or(InitializerOwnerStep::AggregateWrapper);
16417                steps.push(step);
16418                current = parent.parent()?;
16419            }
16420            "initializer_list" => {
16421                current = parent;
16422            }
16423            "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
16424                let declaration = parent.parent()?;
16425                let owner = declaration_owner(analyzer, visibility, file, source, declaration)?;
16426                return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
16427            }
16428            "compound_literal_expression"
16429                if parent.child_by_field_name("value") == Some(current) =>
16430            {
16431                let type_node = parent.child_by_field_name("type")?;
16432                let owner =
16433                    resolve_designated_owner_type(analyzer, visibility, file, source, type_node)?;
16434                return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
16435            }
16436            "ERROR" => current = parent,
16437            _ => return None,
16438        }
16439    }
16440}
16441
16442fn apply_initializer_owner_steps(
16443    analyzer: &CppGraphSource<'_>,
16444    visibility: &VisibilityIndex<'_>,
16445    file: &ProjectFile,
16446    mut owner: CodeUnit,
16447    steps: Vec<InitializerOwnerStep>,
16448) -> Option<CodeUnit> {
16449    for step in steps.into_iter().rev() {
16450        if let InitializerOwnerStep::Field(field_name) = step {
16451            owner = initializer_field_owner(analyzer, visibility, file, owner, &field_name)?;
16452        }
16453    }
16454    Some(owner)
16455}
16456
16457fn initializer_field_owner(
16458    analyzer: &CppGraphSource<'_>,
16459    visibility: &VisibilityIndex<'_>,
16460    file: &ProjectFile,
16461    owner: CodeUnit,
16462    field_name: &str,
16463) -> Option<CodeUnit> {
16464    let fields = visibility
16465        .visible_members_for_owner_name(file, &owner, field_name)
16466        .into_iter()
16467        .filter(|field| field.is_field())
16468        .collect::<Vec<_>>();
16469    let field = match fields.as_slice() {
16470        [field] => *field,
16471        _ => return None,
16472    };
16473    field_declared_binding(analyzer, visibility, file, field)?.unit
16474}
16475
16476fn declaration_owner(
16477    analyzer: &CppGraphSource<'_>,
16478    visibility: &VisibilityIndex<'_>,
16479    file: &ProjectFile,
16480    source: &str,
16481    declaration: Node<'_>,
16482) -> Option<CodeUnit> {
16483    if !matches!(declaration.kind(), "declaration" | "field_declaration") {
16484        return None;
16485    }
16486    let type_node = declaration
16487        .child_by_field_name("type")
16488        .or_else(|| first_type_child(declaration))?;
16489    resolve_designated_owner_type(analyzer, visibility, file, source, type_node)
16490}
16491
16492fn resolve_designated_owner_type(
16493    analyzer: &CppGraphSource<'_>,
16494    visibility: &VisibilityIndex<'_>,
16495    file: &ProjectFile,
16496    source: &str,
16497    type_node: Node<'_>,
16498) -> Option<CodeUnit> {
16499    if let Some(owner) = anonymous_aggregate_owner(analyzer, file, type_node) {
16500        return Some(owner);
16501    }
16502    if !is_globally_qualified_cpp_name(type_node)
16503        && let Some(components) = cpp_type_name_components(type_node, source)
16504        && let Some(resolution) =
16505            function_local_type_resolution(analyzer, file, type_node, &components, source)
16506    {
16507        return match resolution {
16508            LexicalTypeResolution::Resolved { unit, .. } => Some(unit).filter(CodeUnit::is_class),
16509            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
16510        };
16511    }
16512    let type_name = normalize_type_text(node_text(type_node, source));
16513    visibility
16514        .resolve_type(file, &type_name)
16515        .filter(CodeUnit::is_class)
16516}
16517
16518pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
16519    let mut cursor = node.walk();
16520    node.named_children(&mut cursor).find(|child| {
16521        matches!(
16522            child.kind(),
16523            "type_identifier"
16524                | "primitive_type"
16525                | "qualified_identifier"
16526                | "scoped_type_identifier"
16527                | "struct_specifier"
16528                | "union_specifier"
16529                | "enum_specifier"
16530        )
16531    })
16532}
16533
16534pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
16535    visibility: &VisibilityIndex<'_>,
16536    file: &ProjectFile,
16537    source: &str,
16538    declarator: Node<'_>,
16539    type_text: Option<&str>,
16540    bindings: &LocalInferenceEngine<T>,
16541) -> bool {
16542    if !has_ancestor_kind(declarator, "compound_statement") {
16543        return false;
16544    }
16545    if declarator
16546        .child_by_field_name("declarator")
16547        .is_none_or(|declarator| declarator.kind() != "identifier")
16548    {
16549        return false;
16550    }
16551    if !type_text
16552        .and_then(|text| visibility.resolve_type(file, text))
16553        .is_some_and(|unit| unit.is_class())
16554    {
16555        return false;
16556    }
16557    declarator
16558        .child_by_field_name("parameters")
16559        .is_some_and(|parameters| {
16560            constructor_parameters_look_like_expressions(parameters, source, bindings)
16561        })
16562}
16563
16564fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
16565    parameters: Node<'_>,
16566    source: &str,
16567    bindings: &LocalInferenceEngine<T>,
16568) -> bool {
16569    let mut cursor = parameters.walk();
16570    parameters.named_children(&mut cursor).any(|parameter| {
16571        !matches!(
16572            parameter.kind(),
16573            "parameter_declaration" | "optional_parameter_declaration"
16574        ) || parameter_declaration_is_local_expression(parameter, source, bindings)
16575    })
16576}
16577
16578fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
16579    parameter: Node<'_>,
16580    source: &str,
16581    bindings: &LocalInferenceEngine<T>,
16582) -> bool {
16583    let text = node_text(parameter, source).trim();
16584    if text
16585        .chars()
16586        .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
16587        && bindings.is_shadowed(text)
16588    {
16589        return true;
16590    }
16591
16592    let Some(base) = parameter
16593        .child_by_field_name("type")
16594        .filter(|base| base.kind() == "type_identifier")
16595    else {
16596        return false;
16597    };
16598    let Some(subscript) = parameter
16599        .child_by_field_name("declarator")
16600        .filter(|declarator| declarator.kind() == "abstract_array_declarator")
16601    else {
16602        return false;
16603    };
16604    subscript.child_by_field_name("size").is_some()
16605        && bindings.is_shadowed(node_text(base, source).trim())
16606}
16607
16608pub fn is_declaration_name(node: Node<'_>) -> bool {
16609    let Some(parent) = node.parent() else {
16610        return false;
16611    };
16612    if parent
16613        .child_by_field_name("name")
16614        .is_some_and(|name| same_node(name, node))
16615    {
16616        if matches!(
16617            parent.kind(),
16618            "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
16619        ) {
16620            return cpp_tag_specifier_declares_name(parent);
16621        }
16622        if matches!(
16623            parent.kind(),
16624            "namespace_definition"
16625                | "namespace_alias_definition"
16626                | "alias_declaration"
16627                | "enumerator"
16628        ) {
16629            return true;
16630        }
16631    }
16632
16633    let mut current = Some(parent);
16634    while let Some(ancestor) = current {
16635        let type_definition = ancestor.kind() == "type_definition";
16636        let mut child_cursor = ancestor.walk();
16637        if ancestor.named_children(&mut child_cursor).any(|child| {
16638            declaration_declarator(ancestor, child).is_some_and(|declarator| {
16639                declarator_name_path_contains(declarator, node, type_definition)
16640            })
16641        }) {
16642            return true;
16643        }
16644        if matches!(
16645            ancestor.kind(),
16646            "declaration"
16647                | "field_declaration"
16648                | "parameter_declaration"
16649                | "optional_parameter_declaration"
16650                | "function_definition"
16651                | "type_definition"
16652                | "alias_declaration"
16653                | "template_instantiation"
16654                | "class_specifier"
16655                | "struct_specifier"
16656                | "union_specifier"
16657                | "enum_specifier"
16658        ) {
16659            return false;
16660        }
16661        current = ancestor.parent();
16662    }
16663    false
16664}
16665
16666/// Whether tree-sitter recovered a qualified friend-class type as an ordinary
16667/// declaration's declarator inside a malformed class body.
16668///
16669/// An export macro between `class` and the class name can make the containing
16670/// body parse as a function body. A source declaration such as
16671/// `friend class internal::Friend;` then retains this exact structure:
16672/// `declaration(type: friend, ERROR(class), declarator: internal::Friend)`.
16673/// The declarator is a type reference despite its field role.
16674pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
16675    if !matches!(
16676        node.kind(),
16677        "qualified_identifier" | "scoped_type_identifier"
16678    ) {
16679        return false;
16680    }
16681    let Some(declaration) = node
16682        .parent()
16683        .filter(|parent| parent.kind() == "declaration")
16684    else {
16685        return false;
16686    };
16687    if declaration.child_by_field_name("declarator") != Some(node)
16688        || !declaration
16689            .child_by_field_name("type")
16690            .is_some_and(|friend| {
16691                friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
16692            })
16693    {
16694        return false;
16695    }
16696    let mut cursor = declaration.walk();
16697    let mut errors = declaration
16698        .named_children(&mut cursor)
16699        .filter(|child| child.kind() == "ERROR");
16700    let Some(error) = errors.next() else {
16701        return false;
16702    };
16703    errors.next().is_none()
16704        && error.named_child_count() == 1
16705        && error.named_child(0).is_some_and(|class| {
16706            class.kind() == "identifier" && node_text(class, source) == "class"
16707        })
16708}
16709
16710pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
16711    if !matches!(node.kind(), "identifier" | "field_identifier") {
16712        return false;
16713    }
16714    if let Some(parent) = node.parent() {
16715        if parent.kind() == "call_expression"
16716            && parent.child_by_field_name("function") == Some(node)
16717        {
16718            return false;
16719        }
16720        if matches!(parent.kind(), "labeled_statement" | "goto_statement")
16721            && parent.child_by_field_name("label") == Some(node)
16722        {
16723            return false;
16724        }
16725    }
16726    if is_declaration_name(node) {
16727        return false;
16728    }
16729    let mut current = node.parent();
16730    while let Some(ancestor) = current {
16731        match ancestor.kind() {
16732            "preproc_ifdef" | "preproc_ifndef" => {
16733                if ancestor
16734                    .child_by_field_name("name")
16735                    .is_some_and(|name| node_range_contains(name, node))
16736                {
16737                    return false;
16738                }
16739            }
16740            "preproc_if" | "preproc_elif" => {
16741                if ancestor
16742                    .child_by_field_name("condition")
16743                    .is_some_and(|condition| node_range_contains(condition, node))
16744                {
16745                    return false;
16746                }
16747            }
16748            "preproc_else" => {}
16749            kind if kind.starts_with("preproc_") => return false,
16750            _ => {}
16751        }
16752        if matches!(
16753            ancestor.kind(),
16754            "translation_unit" | "function_definition" | "compound_statement"
16755        ) {
16756            break;
16757        }
16758        current = ancestor.parent();
16759    }
16760    true
16761}
16762
16763fn recovered_c_reference_node(
16764    visibility: &VisibilityIndex<'_>,
16765    file: &ProjectFile,
16766    node: Node<'_>,
16767    source: &str,
16768) -> bool {
16769    if node.start_byte() >= node.end_byte()
16770        || node.is_error()
16771        || node.is_missing()
16772        || !matches!(
16773            node.kind(),
16774            "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
16775        )
16776        || recovered_c_macro_binding_role(node)
16777        || recovered_c_label_role(node)
16778    {
16779        return false;
16780    }
16781    // A declaration name can be an identifier child of a recovered ERROR
16782    // (for example `EFI_STATUS Encode()` in C files parsed as C++). Do not
16783    // let a same-named macro turn that binder into a reference. An
16784    // assignment whose C callee follows the recovered `explicit` token is
16785    // the one expression-shaped exception: the generic declarator walk sees
16786    // its function_declarator as a declaration path, but the ERROR sibling
16787    // proves it is a call.
16788    let name = node_text(node, source);
16789    let recovered_function_call = recovered_c_function_call(visibility, file, node, name);
16790    let recovered_macro_call = recovered_c_function_declarator_invocation(node)
16791        && visibility.macro_name_may_be_bound_at(file, name, node.start_byte());
16792    let recovered_parenthesized_reference = recovered_c_parenthesized_declarator_reference(node);
16793    if is_declaration_name(node)
16794        && !recovered_c_explicit_assignment_callee(visibility, file, node, name)
16795        && !recovered_function_call
16796        && !recovered_macro_call
16797        && !recovered_parenthesized_reference
16798    {
16799        return false;
16800    }
16801
16802    if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
16803        return true;
16804    }
16805    if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
16806        return true;
16807    }
16808    if recovered_parenthesized_reference {
16809        return true;
16810    }
16811    if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
16812        if recovered_function_call {
16813            return true;
16814        }
16815        return visibility
16816            .visible_identifier_candidates(file, name)
16817            .any(|candidate| candidate.is_class() || candidate.is_module());
16818    }
16819    let visible = visibility
16820        .visible_identifier_candidates(file, name)
16821        .next()
16822        .is_some();
16823    visible
16824        && (recovered_c_reference_anchor(node)
16825            || recovered_c_error_expression_leaf(node)
16826            || recovered_function_call)
16827}
16828
16829fn push_recovered_c_range(
16830    ranges: &mut Vec<Range>,
16831    seen: &mut HashSet<(usize, usize)>,
16832    start_byte: usize,
16833    end_byte: usize,
16834    node: Node<'_>,
16835    limit: usize,
16836) -> bool {
16837    if start_byte >= end_byte || !seen.insert((start_byte, end_byte)) {
16838        return true;
16839    }
16840    if ranges.len() >= limit {
16841        return false;
16842    }
16843    ranges.push(Range {
16844        start_byte,
16845        end_byte,
16846        start_line: node.start_position().row,
16847        end_line: node.end_position().row,
16848    });
16849    true
16850}
16851
16852/// A reference leaf can sit directly beneath an ERROR while its ERROR parent
16853/// is still attached to a real expression (most often a recovered macro call
16854/// argument). The expression parent is the structured proof; an unindexed
16855/// identifier beneath a bare recovery envelope has no such proof.
16856fn recovered_c_error_expression_leaf(node: Node<'_>) -> bool {
16857    let mut current = node.parent();
16858    while let Some(parent) = current {
16859        if parent.is_error() {
16860            let Some(anchor) = parent.parent() else {
16861                return false;
16862            };
16863            return anchor.kind().ends_with("_expression")
16864                || matches!(
16865                    anchor.kind(),
16866                    "argument_list"
16867                        | "return_statement"
16868                        | "expression_statement"
16869                        | "case_statement"
16870                        | "initializer_list"
16871                        | "field_designator"
16872                        | "enumerator"
16873                );
16874        }
16875        if matches!(
16876            parent.kind(),
16877            "translation_unit" | "function_definition" | "compound_statement"
16878        ) {
16879            return false;
16880        }
16881        current = parent.parent();
16882    }
16883    false
16884}
16885
16886/// C recovery may represent a call as `identifier > function_declarator >
16887/// function_declarator > ERROR > compound_statement`. This shape is only a
16888/// call when the malformed declarator is attached to a real function body and
16889/// the name is an indexed visible callable. A declaration's
16890/// `ERROR > function_declarator > declaration` shape deliberately fails this
16891/// test.
16892fn recovered_c_function_call(
16893    visibility: &VisibilityIndex<'_>,
16894    file: &ProjectFile,
16895    node: Node<'_>,
16896    name: &str,
16897) -> bool {
16898    if !matches!(
16899        node.kind(),
16900        "identifier" | "field_identifier" | "type_identifier"
16901    ) {
16902        return false;
16903    }
16904    // C++ keywords used as C arguments can leave only the call prefix under
16905    // ERROR, with the remaining arguments in a following expression statement.
16906    // Require a statement boundary in a real block, an opening parenthesis,
16907    // and a recovered argument. A bare ERROR identifier supplies no call role.
16908    let error_call_prefix = node.parent().is_some_and(|error| {
16909        error.is_error()
16910            && error
16911                .parent()
16912                .is_some_and(|parent| parent.kind() == "compound_statement")
16913    }) && node
16914        .prev_sibling()
16915        .is_none_or(|previous| previous.kind() == ";")
16916        && node.next_sibling().is_some_and(|open| {
16917            open.kind() == "("
16918                && open.next_named_sibling().is_some_and(|argument| {
16919                    argument.kind() == "parameter_declaration" && !argument.has_error()
16920                })
16921        });
16922    (error_call_prefix || recovered_c_function_declarator_invocation(node))
16923        && visibility
16924            .visible_identifier_candidates(file, name)
16925            .any(CodeUnit::is_function)
16926}
16927
16928/// The bogus `function_definition` the macro-declaration grammar builds for a
16929/// C call whose argument list a Clang block literal breaks: `f(first, ^(T b)
16930/// { ... })` reads as a definition whose declarator ends at the block's `(`,
16931/// with the block body as the function body. The older grammar kept the
16932/// `call_expression` and left the damage inside its argument list. The
16933/// declarator must carry the damage, and the envelope must sit in statement
16934/// context: a clean type-less definition is a real one (a constructor).
16935pub fn recovered_c_block_call_envelope(declarator: Node<'_>) -> Option<Node<'_>> {
16936    if declarator.kind() != "function_declarator" || !declarator.has_error() {
16937        return None;
16938    }
16939    let definition = declarator.parent().filter(|parent| {
16940        parent.kind() == "function_definition"
16941            && parent.child_by_field_name("declarator") == Some(declarator)
16942    })?;
16943    if definition.child_by_field_name("type").is_some()
16944        || definition.named_child(0) != Some(declarator)
16945        || definition.child_by_field_name("body").is_none()
16946        || definition.parent()?.kind() != "compound_statement"
16947    {
16948        return None;
16949    }
16950    Some(definition)
16951}
16952
16953/// Return whether a C identifier belongs to a call-shaped declarator that the
16954/// C++ grammar put under an `ERROR` node.
16955///
16956/// The malformed call can be direct (`f(arg)`) or nested in a parameter
16957/// declaration when one of its arguments looks like a type (`f(TYPE, value)`).
16958/// In both cases the CST retains the function-declarator and its enclosing
16959/// recovery envelope. We walk only those declarator/parameter nodes and stop
16960/// at a real expression-bearing boundary; declarations therefore cannot pass
16961/// this predicate merely because they have a parameter list.
16962fn recovered_c_function_declarator_invocation(node: Node<'_>) -> bool {
16963    // The macro-declaration grammar keeps a function-like macro invocation at
16964    // declaration scope as a `macro_type_specifier` (a function-like macro
16965    // wrapping a type), admitted only where the parse would otherwise contain
16966    // an error. Its name is the invocation.
16967    let mut current = if node.parent().is_some_and(|parent| {
16968        parent.kind() == "macro_type_specifier" && parent.child_by_field_name("name") == Some(node)
16969    }) {
16970        node.parent().expect("checked macro_type_specifier parent")
16971    } else {
16972        let mut function_declarator = if node.parent().is_some_and(|parent| {
16973            parent.kind() == "function_declarator"
16974                && parent.child_by_field_name("declarator") == Some(node)
16975        }) {
16976            node.parent().expect("checked function declarator parent")
16977        } else {
16978            let Some(parameter) = node.parent().filter(|parent| {
16979                parent.kind() == "parameter_declaration"
16980                    && parent.child_by_field_name("type") == Some(node)
16981            }) else {
16982                return false;
16983            };
16984            if !parameter
16985                .child_by_field_name("declarator")
16986                .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
16987            {
16988                return false;
16989            }
16990            let Some(parameters) = parameter
16991                .parent()
16992                .filter(|parent| parent.kind() == "parameter_list")
16993            else {
16994                return false;
16995            };
16996            let Some(function_declarator) = parameters
16997                .parent()
16998                .filter(|parent| parent.kind() == "function_declarator")
16999            else {
17000                return false;
17001            };
17002            function_declarator
17003        };
17004
17005        // Recovery may absorb the next statement's parenthesized cast into a
17006        // second function declarator. Follow only the original declarator chain.
17007        while let Some(parent) = function_declarator.parent().filter(|parent| {
17008            parent.kind() == "function_declarator"
17009                && parent.child_by_field_name("declarator") == Some(function_declarator)
17010        }) {
17011            function_declarator = parent;
17012        }
17013        // The macro-declaration grammar can attach the malformed call's
17014        // declarator directly to the bogus `function_definition` (a block
17015        // literal argument reads as the body) without the older grammar's
17016        // ERROR wrapper.
17017        let Some(current) = function_declarator.parent().filter(|parent| {
17018            parent.is_error() || recovered_c_block_call_envelope(function_declarator).is_some()
17019        }) else {
17020            return false;
17021        };
17022        current
17023    };
17024    loop {
17025        let Some(parent) = current.parent() else {
17026            return false;
17027        };
17028        if matches!(
17029            parent.kind(),
17030            "translation_unit"
17031                | "compound_statement"
17032                | "preproc_if"
17033                | "preproc_ifdef"
17034                | "preproc_ifndef"
17035                | "preproc_else"
17036                | "preproc_elif"
17037        ) {
17038            return true;
17039        }
17040        if parent.kind() == "function_definition"
17041            && parent.child_by_field_name("declarator") == Some(current)
17042            && parent.named_child(0) == Some(current)
17043            && parent.child_by_field_name("body").is_some()
17044        {
17045            return true;
17046        }
17047        if parent.is_error()
17048            || matches!(
17049                parent.kind(),
17050                "parameter_declaration"
17051                    | "parameter_list"
17052                    | "function_declarator"
17053                    | "abstract_function_declarator"
17054                    | "parenthesized_declarator"
17055            )
17056        {
17057            current = parent;
17058            continue;
17059        }
17060        return false;
17061    }
17062}
17063
17064/// C permits an identifier named `typename`. The C++ grammar can recover an
17065/// assignment using that identifier as a declaration whose declarator is a
17066/// parenthesized argument list, for example `typename = f(ctx, value)`. Only
17067/// the argument retained beneath the nested `ERROR` is a reference; sibling
17068/// declarator identifiers remain binders/grammar artifacts.
17069fn recovered_c_parenthesized_declarator_reference(node: Node<'_>) -> bool {
17070    // The macro-declaration grammar keeps the same line as a `dependent_type`
17071    // whose `macro_type_specifier` argument list retains the first argument
17072    // beneath the nested `ERROR`; that argument's `type_descriptor` is the
17073    // reference, and the specifier's own `type` field is the following
17074    // sibling, never a reference.
17075    if node
17076        .parent()
17077        .is_some_and(|parent| parent.kind() == "type_descriptor")
17078    {
17079        let Some(type_descriptor) = node.parent() else {
17080            return false;
17081        };
17082        let Some(error) = type_descriptor.parent().filter(|parent| parent.is_error()) else {
17083            return false;
17084        };
17085        if error.named_child_count() != 1 || error.named_child(0) != Some(type_descriptor) {
17086            return false;
17087        }
17088        let Some(dependent) = error
17089            .parent()
17090            .and_then(|parent| parent.parent())
17091            .filter(|parent| parent.kind() == "dependent_type")
17092        else {
17093            return false;
17094        };
17095        return dependent
17096            .child(0)
17097            .is_some_and(|keyword| keyword.kind() == "typename");
17098    }
17099    let Some(error) = node.parent().filter(|parent| parent.is_error()) else {
17100        return false;
17101    };
17102    if error.named_child_count() != 1 || error.named_child(0) != Some(node) {
17103        return false;
17104    }
17105    let Some(declarator) = error
17106        .parent()
17107        .filter(|parent| parent.kind() == "parenthesized_declarator")
17108    else {
17109        return false;
17110    };
17111    let Some(declaration) = declarator
17112        .parent()
17113        .filter(|parent| parent.kind() == "declaration")
17114    else {
17115        return false;
17116    };
17117    if declaration.child_by_field_name("declarator") != Some(declarator) {
17118        return false;
17119    }
17120    let Some(type_node) = declaration.child_by_field_name("type") else {
17121        return false;
17122    };
17123    type_node.kind() == "dependent_type"
17124        && type_node
17125            .child(0)
17126            .is_some_and(|keyword| keyword.kind() == "typename")
17127}
17128
17129fn recovered_c_explicit_assignment_callee(
17130    visibility: &VisibilityIndex<'_>,
17131    file: &ProjectFile,
17132    node: Node<'_>,
17133    name: &str,
17134) -> bool {
17135    let mut current = node;
17136    let error = loop {
17137        let Some(parent) = current.parent() else {
17138            return false;
17139        };
17140        if parent.is_error() {
17141            break parent;
17142        }
17143        current = parent;
17144    };
17145    let mut cursor = error.walk();
17146    let explicit_recovery_precedes_callee = error
17147        .named_children(&mut cursor)
17148        .take_while(|child| child.start_byte() < node.start_byte())
17149        .any(|child| child.kind() == "explicit_function_specifier");
17150    if !explicit_recovery_precedes_callee {
17151        return false;
17152    }
17153    visibility
17154        .visible_identifier_candidates(file, name)
17155        .any(CodeUnit::is_function)
17156}
17157
17158fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
17159    while let Some(parent) = node.parent() {
17160        if matches!(
17161            parent.kind(),
17162            "preproc_def" | "preproc_function_def" | "preproc_params"
17163        ) {
17164            return true;
17165        }
17166        if parent.is_error()
17167            || matches!(
17168                parent.kind(),
17169                "translation_unit" | "function_definition" | "compound_statement"
17170            )
17171        {
17172            return false;
17173        }
17174        node = parent;
17175    }
17176    false
17177}
17178
17179fn recovered_c_label_role(node: Node<'_>) -> bool {
17180    node.parent().is_some_and(|parent| {
17181        matches!(parent.kind(), "labeled_statement" | "goto_statement")
17182            && parent.child_by_field_name("label") == Some(node)
17183    })
17184}
17185
17186fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
17187    while let Some(parent) = node.parent() {
17188        if parent.is_error() {
17189            return false;
17190        }
17191        // A C macro call recovered as a function declarator can parse an
17192        // assignment-shaped argument as an optional parameter. Its
17193        // `default_value` field remains an expression role even though the
17194        // surrounding call shape is beneath ERROR.
17195        if parent.kind() == "optional_parameter_declaration"
17196            && parent
17197                .child_by_field_name("default_value")
17198                .is_some_and(|value| node_range_contains(value, node))
17199        {
17200            return true;
17201        }
17202        if parent.kind().ends_with("_expression")
17203            || matches!(
17204                parent.kind(),
17205                "argument_list"
17206                    | "return_statement"
17207                    | "expression_statement"
17208                    | "case_statement"
17209                    | "initializer_list"
17210                    | "init_declarator"
17211                    | "array_declarator"
17212                    | "field_designator"
17213                    | "enumerator"
17214            )
17215        {
17216            return true;
17217        }
17218        if matches!(
17219            parent.kind(),
17220            "translation_unit"
17221                | "function_definition"
17222                | "compound_statement"
17223                | "declaration"
17224                | "field_declaration"
17225                | "parameter_declaration"
17226        ) {
17227            return false;
17228        }
17229        node = parent;
17230    }
17231    false
17232}
17233
17234/// Whether a parameter declaration belongs to the callable scope whose body can
17235/// contain references to it.
17236///
17237/// Error recovery can wrap a macro-decorated class body in a synthetic outer
17238/// `function_definition`. Merely finding any callable ancestor would then leak
17239/// parameters from member prototypes into later member bodies. Require the
17240/// parameter to be inside that definition's own declarator instead.
17241pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
17242    let mut current = parameter.parent();
17243    while let Some(ancestor) = current {
17244        if ancestor.kind() == "lambda_expression" {
17245            return ancestor
17246                .child_by_field_name("declarator")
17247                .is_some_and(|declarator| {
17248                    declarator.start_byte() <= parameter.start_byte()
17249                        && parameter.end_byte() <= declarator.end_byte()
17250                });
17251        }
17252        if ancestor.kind() == "function_definition" {
17253            return ancestor
17254                .child_by_field_name("declarator")
17255                .is_some_and(|declarator| {
17256                    declarator.start_byte() <= parameter.start_byte()
17257                        && parameter.end_byte() <= declarator.end_byte()
17258                });
17259        }
17260        current = ancestor.parent();
17261    }
17262    false
17263}
17264
17265pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
17266    let mut current = node.parent();
17267    while let Some(ancestor) = current {
17268        if matches!(
17269            ancestor.kind(),
17270            "parameter_declaration" | "optional_parameter_declaration"
17271        ) {
17272            return crate::declarations::cpp_type_field_contains_node(ancestor, node);
17273        }
17274        if matches!(
17275            ancestor.kind(),
17276            "function_definition" | "lambda_expression" | "compound_statement"
17277        ) {
17278            return false;
17279        }
17280        current = ancestor.parent();
17281    }
17282    false
17283}
17284
17285/// Walk tag-specifier ancestors until a type-reference context makes its name
17286/// an operand rather than a declaration. Callers retain their own terminal
17287/// declaration boundaries after this shared exclusion scan.
17288pub fn cpp_tag_specifier_ancestors_before_type_reference_context(
17289    specifier: Node<'_>,
17290) -> impl Iterator<Item = Node<'_>> {
17291    std::iter::successors(specifier.parent(), |ancestor| ancestor.parent()).take_while(|ancestor| {
17292        !matches!(
17293            ancestor.kind(),
17294            "type_descriptor"
17295                | "parameter_declaration"
17296                | "optional_parameter_declaration"
17297                | "template_argument_list"
17298                | "cast_expression"
17299        )
17300    })
17301}
17302
17303pub fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
17304    if specifier.child_by_field_name("body").is_some() {
17305        return true;
17306    }
17307    for ancestor in cpp_tag_specifier_ancestors_before_type_reference_context(specifier) {
17308        match ancestor.kind() {
17309            "declaration" | "field_declaration" => {
17310                let mut cursor = ancestor.walk();
17311                return ancestor
17312                    .children_by_field_name("declarator", &mut cursor)
17313                    .next()
17314                    .is_none();
17315            }
17316            "translation_unit" => return true,
17317            _ => {}
17318        }
17319    }
17320    false
17321}
17322
17323pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
17324    match node.kind() {
17325        "identifier"
17326        | "field_identifier"
17327        | "qualified_identifier"
17328        | "scoped_identifier"
17329        | "operator_name"
17330        | "destructor_name"
17331        | "literal_operator_name" => Some(node),
17332        "reference_declarator" | "parenthesized_declarator" => {
17333            node.named_child(0).and_then(declarator_name_node)
17334        }
17335        _ => node
17336            .child_by_field_name("declarator")
17337            .or_else(|| node.child_by_field_name("name"))
17338            .or_else(|| node.child_by_field_name("field"))
17339            .and_then(declarator_name_node),
17340    }
17341}
17342
17343/// Whether `candidate` occurs on the declarator path before its selected name.
17344pub fn declarator_path_contains_before_name(
17345    declarator: Node<'_>,
17346    name: Node<'_>,
17347    candidate: Node<'_>,
17348) -> bool {
17349    let mut current = Some(declarator);
17350    while let Some(node) = current {
17351        if same_node(node, candidate) {
17352            return true;
17353        }
17354        if same_node(node, name) {
17355            return false;
17356        }
17357        current = node
17358            .child_by_field_name("declarator")
17359            .or_else(|| node.child_by_field_name("name"))
17360            .or_else(|| node.child_by_field_name("field"));
17361    }
17362    false
17363}
17364
17365fn declarator_name_path_contains(
17366    declarator: Node<'_>,
17367    candidate: Node<'_>,
17368    allow_type_identifier: bool,
17369) -> bool {
17370    let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
17371        return false;
17372    };
17373    declarator_path_contains_before_name(declarator, name, candidate)
17374}
17375
17376fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
17377    match node.kind() {
17378        "identifier"
17379        | "field_identifier"
17380        | "operator_name"
17381        | "destructor_name"
17382        | "literal_operator_name" => Some(node),
17383        "type_identifier" if allow_type_identifier => Some(node),
17384        _ => node
17385            .child_by_field_name("declarator")
17386            .or_else(|| node.child_by_field_name("name"))
17387            .or_else(|| node.child_by_field_name("field"))
17388            .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
17389    }
17390}
17391
17392/// True when `node` is a component of a larger structured type node whose outer
17393/// range is the single reference surfaced to callers.
17394pub fn is_nested_type_node(node: Node<'_>) -> bool {
17395    node.parent().is_some_and(|parent| {
17396        matches!(
17397            parent.kind(),
17398            "qualified_identifier" | "scoped_type_identifier" | "template_type"
17399        )
17400    })
17401}
17402
17403pub struct OutOfLineMemberDefinitionOwners<'tree> {
17404    pub owners: Vec<(Node<'tree>, CodeUnit)>,
17405    innermost: Option<(Node<'tree>, CodeUnit)>,
17406}
17407
17408impl OutOfLineMemberDefinitionOwners<'_> {
17409    pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
17410        self.innermost.as_ref().map(|(node, owner)| (*node, owner))
17411    }
17412}
17413
17414pub struct QualifiedOwnerComponents<'tree> {
17415    pub nodes: Vec<Node<'tree>>,
17416    pub names: Vec<String>,
17417    pub global: bool,
17418}
17419
17420/// True when each structured qualifier on the callable-name path has a real
17421/// `::` token. A macro-prefixed return type can make tree-sitter insert a
17422/// zero-width missing separator and parse `TYPE Result<T> method()` as the
17423/// false qualified declarator `Result<T>::method`.
17424pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
17425    let mut stack = vec![node];
17426    let mut found_separator = false;
17427    while let Some(current) = stack.pop() {
17428        if !matches!(
17429            current.kind(),
17430            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
17431        ) {
17432            continue;
17433        }
17434        let mut current_has_separator = false;
17435        for child in children_iter(current) {
17436            if child.kind() == "::" {
17437                if child.is_missing() {
17438                    return false;
17439                }
17440                current_has_separator = true;
17441                found_separator = true;
17442            }
17443        }
17444        if !current_has_separator {
17445            return false;
17446        }
17447        for field in ["scope", "name"] {
17448            if let Some(child) = current.child_by_field_name(field)
17449                && matches!(
17450                    child.kind(),
17451                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
17452                )
17453            {
17454                stack.push(child);
17455            }
17456        }
17457    }
17458    found_separator
17459}
17460
17461pub fn qualified_owner_components<'tree>(
17462    node: Node<'tree>,
17463    source: &str,
17464) -> Option<QualifiedOwnerComponents<'tree>> {
17465    if !qualified_name_has_concrete_scope_separators(node) {
17466        return None;
17467    }
17468    let mut nodes = cpp_name_component_nodes(node)?;
17469    nodes.pop()?;
17470    if nodes.is_empty() {
17471        return None;
17472    }
17473    let names = nodes
17474        .iter()
17475        .map(|component| node_text(*component, source).to_string())
17476        .collect();
17477    Some(QualifiedOwnerComponents {
17478        nodes,
17479        names,
17480        global: is_globally_qualified_cpp_name(node),
17481    })
17482}
17483
17484pub fn out_of_line_member_definition_owner<'tree>(
17485    analyzer: &CppGraphSource<'_>,
17486    visibility: &VisibilityIndex<'_>,
17487    file: &ProjectFile,
17488    source: &str,
17489    node: Node<'tree>,
17490) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
17491    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
17492        || !has_ancestor_kind(node, "function_definition")
17493        || !is_function_declarator_name_root(node)
17494    {
17495        return None;
17496    }
17497    let qualified = qualified_owner_components(node, source)?;
17498    let lexical_scope = enclosing_namespace_components(node, source)?;
17499    let mut owners = Vec::new();
17500    let mut innermost = None;
17501
17502    for component_count in 1..=qualified.names.len() {
17503        if let LexicalTypeResolution::Resolved { unit, .. } = visibility
17504            .resolve_type_components_lexically(
17505                analyzer,
17506                file,
17507                &qualified.names[..component_count],
17508                qualified.global,
17509                &lexical_scope,
17510            )
17511            && !owners
17512                .iter()
17513                .any(|(_, existing)| same_visible_symbol(existing, &unit))
17514        {
17515            if component_count == qualified.names.len() {
17516                innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
17517            }
17518            owners.push((qualified.nodes[component_count - 1], unit));
17519        }
17520    }
17521
17522    // The C++ analyzer has already reconciled an indexed out-of-line callable
17523    // against the include-visible class table. Consult that canonical owner
17524    // chain only when ordinary lexical lookup could not recover the innermost
17525    // owner.  A one-segment qualifier is safe here only when the enclosing
17526    // indexed callable has an authoritative class owner and the parser's
17527    // namespace path is a (possibly sparse) subsequence of that owner path.
17528    // The latter is what lets macro-wrapped namespace sentinels recover a
17529    // missing `time_internal`/`cord_internal` component without guessing an
17530    // unrelated short name.
17531    if innermost.is_none() {
17532        let indexed_owner_components = visibility
17533            .indexed_enclosing_owner_scope(analyzer, file, node)
17534            .or_else(|| {
17535                // Retain the legacy rendered-name fallback for the existing
17536                // multi-segment path when an enclosing owner chain is not
17537                // available (for example, cache-loaded units without parent
17538                // links).  One-segment recovery must stay canonical-only.
17539                if qualified.names.len() <= 1 {
17540                    return None;
17541                }
17542                let range = Range {
17543                    start_byte: node.start_byte(),
17544                    end_byte: node.end_byte(),
17545                    start_line: node.start_position().row,
17546                    end_line: node.end_position().row,
17547                };
17548                let start = analyzer.enclosing_code_unit(file, &range)?;
17549                let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
17550                    brokk_bifrost_core::analyzer::Language::Cpp,
17551                    &cpp_name_for(&start),
17552                );
17553                components.pop();
17554                Some(components)
17555            });
17556        if let Some(indexed_owner_components) = indexed_owner_components
17557            && indexed_owner_components.len() > qualified.names.len()
17558            && indexed_owner_components.ends_with(&qualified.names)
17559            && indexed_namespace_path_is_recoverable(
17560                &lexical_scope,
17561                &indexed_owner_components,
17562                qualified.names.len(),
17563            )
17564            // A globally-qualified one-segment owner is an explicit request
17565            // for the top-level binding; do not reinterpret it as a missing
17566            // namespace component.  Existing multi-segment global lookups
17567            // retain their historical indexed recovery.
17568            && (qualified.names.len() > 1 || !qualified.global)
17569        {
17570            let namespace_count = indexed_owner_components.len() - qualified.names.len();
17571            for component_count in 1..=qualified.names.len() {
17572                let expected = &indexed_owner_components[..namespace_count + component_count];
17573                let owner_node = qualified.nodes[component_count - 1];
17574                for owner in visibility
17575                    .visible_identifier_candidates(file, &qualified.names[component_count - 1])
17576                    .filter(|candidate| candidate.is_class())
17577                    .filter(|candidate| {
17578                        canonical_cpp_scope_components(candidate) == expected
17579                            && visibility.external_type_candidate_visible_in_context(
17580                                analyzer, file, candidate, node,
17581                            )
17582                    })
17583                {
17584                    if component_count == qualified.names.len() && innermost.is_none() {
17585                        innermost = Some((owner_node, owner.clone()));
17586                    }
17587                    if !owners
17588                        .iter()
17589                        .any(|(_, existing)| same_symbol(existing, owner))
17590                    {
17591                        owners.push((owner_node, owner.clone()));
17592                    }
17593                }
17594            }
17595        }
17596    }
17597    (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
17598}
17599
17600fn is_function_declarator_name_root(node: Node<'_>) -> bool {
17601    let mut current = node;
17602    while let Some(parent) = current.parent() {
17603        if parent.kind() == "function_declarator" {
17604            return parent.child_by_field_name("declarator") == Some(current);
17605        }
17606        if matches!(
17607            parent.kind(),
17608            "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
17609        ) && parent.child_by_field_name("declarator") == Some(current)
17610        {
17611            current = parent;
17612            continue;
17613        }
17614        return false;
17615    }
17616    false
17617}
17618
17619pub fn append_cpp_name_components(
17620    node: Node<'_>,
17621    source: &str,
17622    out: &mut Vec<String>,
17623) -> Option<()> {
17624    out.extend(
17625        cpp_name_component_nodes(node)?
17626            .into_iter()
17627            .map(|component| node_text(component, source).to_string()),
17628    );
17629    Some(())
17630}
17631
17632/// The components of a C++ name with the source range of the token that
17633/// writes each one, outermost first.
17634fn cpp_name_component_ranges(node: Node<'_>, source: &str) -> Option<Vec<(String, Range)>> {
17635    cpp_name_component_nodes(node).map(|components| {
17636        components
17637            .into_iter()
17638            .map(|component| {
17639                (
17640                    node_text(component, source).to_string(),
17641                    node_range(component),
17642                )
17643            })
17644            .collect()
17645    })
17646}
17647
17648pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
17649    let mut components = Vec::new();
17650    append_cpp_name_components(node, source, &mut components)?;
17651    Some(components)
17652}
17653
17654/// Resolve a structured type spelling from an object-like macro replacement
17655/// when definition-site source order has no answer.
17656///
17657/// Macro replacement tokens are looked up where the macro is expanded, so a
17658/// type declared later in the defining header can still be their destination.
17659/// Without expanding every invocation, accept only one include-visible logical
17660/// class or alias whose structured path ends in the replacement components.
17661/// An ordinary lexical answer always takes precedence at the call site.
17662pub fn unique_macro_replacement_type_candidate(
17663    analyzer: &CppGraphSource<'_>,
17664    visibility: &VisibilityIndex<'_>,
17665    file: &ProjectFile,
17666    components: &[String],
17667) -> Option<CodeUnit> {
17668    let terminal = components.last()?;
17669    let mut candidates = Vec::new();
17670    for candidate in visibility
17671        .visible_identifier_candidates(file, terminal)
17672        .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
17673        .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
17674    {
17675        if !candidates
17676            .iter()
17677            .any(|existing| same_logical_symbol(existing, candidate))
17678        {
17679            candidates.push(candidate.clone());
17680        }
17681    }
17682    (candidates.len() == 1).then(|| candidates.remove(0))
17683}
17684
17685/// Whether a structured using-declaration scope can name `qualified` as an
17686/// ancestor class. The boundary check prevents `Base` from matching
17687/// `OtherBase` while allowing a relative `Base` spelling to match `ns::Base`.
17688pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
17689    qualified == scope
17690        || qualified
17691            .strip_suffix(scope)
17692            .is_some_and(|prefix| prefix.ends_with("::"))
17693}
17694
17695/// Whether `node` is the direct structured type payload of a template
17696/// argument. This role remains meaningful even when a surrounding expression
17697/// is below tree-sitter recovery, because both the `template_argument_list`
17698/// and the `type_descriptor` retain their named fields.
17699pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
17700    let Some(type_descriptor) = node.parent() else {
17701        return false;
17702    };
17703    if type_descriptor.kind() != "type_descriptor"
17704        || type_descriptor.child_by_field_name("type") != Some(node)
17705    {
17706        return false;
17707    }
17708    let Some(arguments) = type_descriptor.parent() else {
17709        return false;
17710    };
17711    if arguments.kind() != "template_argument_list" {
17712        return false;
17713    }
17714    arguments.parent().is_some_and(|parent| {
17715        matches!(parent.kind(), "template_type" | "template_function")
17716            && parent.child_by_field_name("arguments") == Some(arguments)
17717    })
17718}
17719
17720pub fn cpp_template_reference_arguments(
17721    mut node: Node<'_>,
17722    source: &str,
17723) -> Option<Vec<CppTemplateExpression>> {
17724    loop {
17725        match node.kind() {
17726            "template_type" | "template_function" => {
17727                let arguments = node.child_by_field_name("arguments")?;
17728                let mut cursor = arguments.walk();
17729                return Some(
17730                    arguments
17731                        .named_children(&mut cursor)
17732                        .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
17733                        .map(|argument| CppTemplateExpression {
17734                            text: normalize_cpp_whitespace(node_text(argument, source)),
17735                            // One template term from a resolver query; see `ParentIndex::unindexed`.
17736                            term: cpp_template_term(
17737                                argument,
17738                                source,
17739                                &[],
17740                                &ParentIndex::unindexed(),
17741                            ),
17742                        })
17743                        .collect(),
17744                );
17745            }
17746            "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
17747                node = node
17748                    .child_by_field_name("name")
17749                    .or_else(|| node.child_by_field_name("type"))?;
17750            }
17751            _ => return None,
17752        }
17753    }
17754}
17755
17756fn cpp_reconcile_primary_template_parameters(
17757    candidates: &[(&CodeUnit, &CppTemplateMetadata)],
17758    preferred: &CodeUnit,
17759) -> Option<Vec<CppTemplateParameterMetadata>> {
17760    let canonical = candidates
17761        .iter()
17762        .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
17763    let mut merged = canonical
17764        .parameters
17765        .iter()
17766        .map(|parameter| CppTemplateParameterMetadata {
17767            name: parameter.name.clone(),
17768            kind: parameter.kind,
17769            variadic: parameter.variadic,
17770            default: None,
17771        })
17772        .collect::<Vec<_>>();
17773
17774    for (_, metadata) in candidates {
17775        if metadata.parameters.len() != merged.len() {
17776            return None;
17777        }
17778        let rename_bindings = metadata
17779            .parameters
17780            .iter()
17781            .zip(&merged)
17782            .map(|(parameter, canonical)| {
17783                (
17784                    parameter.name.clone(),
17785                    CppTemplateTerm::Parameter(canonical.name.clone()),
17786                )
17787            })
17788            .collect::<HashMap<_, _>>();
17789        for ((parameter, canonical), merged_parameter) in metadata
17790            .parameters
17791            .iter()
17792            .zip(&canonical.parameters)
17793            .zip(&mut merged)
17794        {
17795            if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
17796                return None;
17797            }
17798            let Some(default) = &parameter.default else {
17799                continue;
17800            };
17801            let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
17802            if let Some(existing) = &merged_parameter.default {
17803                if !cpp_template_terms_equal(&existing.term, &normalized_term) {
17804                    return None;
17805                }
17806            } else {
17807                merged_parameter.default = Some(CppTemplateExpression {
17808                    text: default.text.clone(),
17809                    term: normalized_term,
17810                });
17811            }
17812        }
17813    }
17814    Some(merged)
17815}
17816
17817pub fn cpp_bind_template_arguments(
17818    parameters: &[CppTemplateParameterMetadata],
17819    explicit_arguments: &[CppTemplateExpression],
17820) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
17821    let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
17822    if variadic_index.is_some_and(|index| {
17823        index + 1 != parameters.len()
17824            || parameters[index + 1..]
17825                .iter()
17826                .any(|parameter| parameter.variadic)
17827    }) {
17828        return None;
17829    }
17830    let fixed_count = variadic_index.unwrap_or(parameters.len());
17831    if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
17832        return None;
17833    }
17834    let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
17835    let mut expanded = explicit_arguments[..explicit_fixed_count]
17836        .iter()
17837        .map(cpp_clone_template_expression_iterative)
17838        .collect::<Vec<_>>();
17839    let mut bindings = HashMap::default();
17840    for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
17841        bindings.insert(
17842            parameter.name.clone(),
17843            cpp_clone_template_term_iterative(&argument.term),
17844        );
17845    }
17846    for parameter in &parameters[explicit_fixed_count..fixed_count] {
17847        let default = parameter.default.as_ref()?;
17848        let term = cpp_substitute_template_term(&default.term, &bindings)?;
17849        bindings.insert(parameter.name.clone(), term.clone());
17850        expanded.push(CppTemplateExpression {
17851            text: default.text.clone(),
17852            term,
17853        });
17854    }
17855    if let Some(index) = variadic_index {
17856        let packed_arguments = &explicit_arguments[explicit_fixed_count..];
17857        expanded.extend(
17858            packed_arguments
17859                .iter()
17860                .map(cpp_clone_template_expression_iterative),
17861        );
17862        bindings.insert(
17863            parameters[index].name.clone(),
17864            CppTemplateTerm::Node {
17865                kind: "parameter_pack".to_string(),
17866                children: packed_arguments
17867                    .iter()
17868                    .map(|argument| cpp_clone_template_term_iterative(&argument.term))
17869                    .collect(),
17870            },
17871        );
17872    }
17873    Some((expanded, bindings))
17874}
17875
17876fn cpp_specialization_matches(
17877    metadata: &CppTemplateMetadata,
17878    arguments: &[CppTemplateExpression],
17879) -> bool {
17880    cpp_specialization_terms_match(
17881        &metadata.parameters,
17882        &metadata.specialization_arguments,
17883        arguments,
17884    )
17885}
17886
17887fn cpp_specialization_terms_match(
17888    parameters: &[CppTemplateParameterMetadata],
17889    patterns: &[CppTemplateExpression],
17890    arguments: &[CppTemplateExpression],
17891) -> bool {
17892    if patterns.len() != arguments.len() {
17893        return false;
17894    }
17895    let parameter_names = parameters
17896        .iter()
17897        .map(|parameter| parameter.name.as_str())
17898        .collect::<HashSet<_>>();
17899    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
17900    patterns.iter().zip(arguments).all(|(pattern, argument)| {
17901        cpp_unify_template_term(
17902            &pattern.term,
17903            &argument.term,
17904            &parameter_names,
17905            &mut bindings,
17906        )
17907    })
17908}
17909
17910fn cpp_specialization_more_specialized(
17911    candidate: &CppTemplateMetadata,
17912    other: &CppTemplateMetadata,
17913) -> bool {
17914    cpp_specialization_pattern_accepts(other, candidate)
17915        && !cpp_specialization_pattern_accepts(candidate, other)
17916}
17917
17918fn cpp_specialization_pattern_accepts(
17919    broader: &CppTemplateMetadata,
17920    narrower: &CppTemplateMetadata,
17921) -> bool {
17922    cpp_specialization_terms_match(
17923        &broader.parameters,
17924        &broader.specialization_arguments,
17925        &narrower.specialization_arguments,
17926    )
17927}
17928
17929pub fn cpp_substitute_template_term(
17930    term: &CppTemplateTerm,
17931    bindings: &HashMap<String, CppTemplateTerm>,
17932) -> Option<CppTemplateTerm> {
17933    enum Work<'a> {
17934        Visit(&'a CppTemplateTerm),
17935        Build { kind: String, child_count: usize },
17936    }
17937
17938    let mut work = vec![Work::Visit(term)];
17939    let mut substituted = Vec::new();
17940    while let Some(next) = work.pop() {
17941        match next {
17942            Work::Visit(CppTemplateTerm::Parameter(name)) => {
17943                substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
17944            }
17945            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
17946                substituted.push(CppTemplateTerm::Atom {
17947                    kind: kind.clone(),
17948                    text: text.clone(),
17949                });
17950            }
17951            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
17952                work.push(Work::Build {
17953                    kind: kind.clone(),
17954                    child_count: children.len(),
17955                });
17956                work.extend(children.iter().rev().map(Work::Visit));
17957            }
17958            Work::Build { kind, child_count } => {
17959                let children = substituted.split_off(substituted.len() - child_count);
17960                substituted.push(CppTemplateTerm::Node { kind, children });
17961            }
17962        }
17963    }
17964    substituted.pop()
17965}
17966
17967pub fn cpp_substitute_template_arguments(
17968    arguments: &[CppTemplateExpression],
17969    bindings: &HashMap<String, CppTemplateTerm>,
17970) -> Option<Vec<CppTemplateExpression>> {
17971    let mut substituted = Vec::new();
17972    for argument in arguments {
17973        let CppTemplateTerm::Node { kind, children } = &argument.term else {
17974            substituted.push(CppTemplateExpression {
17975                text: argument.text.clone(),
17976                term: cpp_substitute_template_term(&argument.term, bindings)?,
17977            });
17978            continue;
17979        };
17980        if kind != "parameter_pack_expansion" {
17981            substituted.push(CppTemplateExpression {
17982                text: argument.text.clone(),
17983                term: cpp_substitute_template_term(&argument.term, bindings)?,
17984            });
17985            continue;
17986        }
17987        let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
17988            return None;
17989        };
17990        if ellipsis != "..." {
17991            return None;
17992        }
17993
17994        let mut pack_names = Vec::new();
17995        let mut work = vec![pattern];
17996        while let Some(term) = work.pop() {
17997            match term {
17998                CppTemplateTerm::Parameter(name)
17999                    if matches!(
18000                        bindings.get(name),
18001                        Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
18002                    ) =>
18003                {
18004                    if !pack_names.contains(name) {
18005                        pack_names.push(name.clone());
18006                    }
18007                }
18008                CppTemplateTerm::Node { children, .. } => work.extend(children),
18009                CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
18010            }
18011        }
18012        let first_pack = pack_names.first()?;
18013        let CppTemplateTerm::Node {
18014            children: first_elements,
18015            ..
18016        } = bindings.get(first_pack)?
18017        else {
18018            return None;
18019        };
18020        let pack_len = first_elements.len();
18021        for pack_name in &pack_names {
18022            let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
18023                return None;
18024            };
18025            if children.len() != pack_len {
18026                return None;
18027            }
18028        }
18029        for index in 0..pack_len {
18030            let mut element_bindings = bindings.clone();
18031            for pack_name in &pack_names {
18032                let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
18033                    return None;
18034                };
18035                element_bindings.insert(
18036                    pack_name.clone(),
18037                    cpp_clone_template_term_iterative(&children[index]),
18038                );
18039            }
18040            substituted.push(CppTemplateExpression {
18041                text: argument.text.clone(),
18042                term: cpp_substitute_template_term(pattern, &element_bindings)?,
18043            });
18044        }
18045    }
18046    Some(substituted)
18047}
18048
18049fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
18050    enum Work<'a> {
18051        Visit(&'a CppTemplateTerm),
18052        Build { kind: String, child_count: usize },
18053    }
18054
18055    let mut work = vec![Work::Visit(term)];
18056    let mut cloned = Vec::new();
18057    while let Some(next) = work.pop() {
18058        match next {
18059            Work::Visit(CppTemplateTerm::Parameter(name)) => {
18060                cloned.push(CppTemplateTerm::Parameter(name.clone()));
18061            }
18062            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
18063                cloned.push(CppTemplateTerm::Atom {
18064                    kind: kind.clone(),
18065                    text: text.clone(),
18066                });
18067            }
18068            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
18069                work.push(Work::Build {
18070                    kind: kind.clone(),
18071                    child_count: children.len(),
18072                });
18073                work.extend(children.iter().rev().map(Work::Visit));
18074            }
18075            Work::Build { kind, child_count } => {
18076                let children = cloned.split_off(cloned.len() - child_count);
18077                cloned.push(CppTemplateTerm::Node { kind, children });
18078            }
18079        }
18080    }
18081    cloned
18082        .pop()
18083        .expect("template term traversal emits one root")
18084}
18085
18086fn cpp_clone_template_expression_iterative(
18087    expression: &CppTemplateExpression,
18088) -> CppTemplateExpression {
18089    CppTemplateExpression {
18090        text: expression.text.clone(),
18091        term: cpp_clone_template_term_iterative(&expression.term),
18092    }
18093}
18094
18095pub fn cpp_unify_template_term(
18096    pattern: &CppTemplateTerm,
18097    argument: &CppTemplateTerm,
18098    parameters: &HashSet<&str>,
18099    bindings: &mut HashMap<String, CppTemplateTerm>,
18100) -> bool {
18101    let mut work = vec![(pattern, argument)];
18102    while let Some((pattern, argument)) = work.pop() {
18103        match pattern {
18104            CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
18105                if let Some(bound) = bindings.get(name) {
18106                    if !cpp_template_terms_equal(bound, argument) {
18107                        return false;
18108                    }
18109                } else {
18110                    bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
18111                }
18112            }
18113            CppTemplateTerm::Atom {
18114                kind: pattern_kind,
18115                text: pattern_text,
18116            } => {
18117                if !matches!(
18118                    argument,
18119                    CppTemplateTerm::Atom { kind, text }
18120                        if kind == pattern_kind && text == pattern_text
18121                ) {
18122                    return false;
18123                }
18124            }
18125            CppTemplateTerm::Node {
18126                kind: pattern_kind,
18127                children: pattern_children,
18128            } => {
18129                let CppTemplateTerm::Node { kind, children } = argument else {
18130                    return false;
18131                };
18132                if kind != pattern_kind || children.len() != pattern_children.len() {
18133                    return false;
18134                }
18135                work.extend(pattern_children.iter().zip(children).rev());
18136            }
18137            CppTemplateTerm::Parameter(_) => return false,
18138        }
18139    }
18140    true
18141}
18142
18143pub(crate) fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
18144    let mut work = vec![(left, right)];
18145    while let Some((left, right)) = work.pop() {
18146        match (left, right) {
18147            (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
18148                if left != right {
18149                    return false;
18150                }
18151            }
18152            (
18153                CppTemplateTerm::Atom {
18154                    kind: left_kind,
18155                    text: left_text,
18156                },
18157                CppTemplateTerm::Atom {
18158                    kind: right_kind,
18159                    text: right_text,
18160                },
18161            ) => {
18162                if left_kind != right_kind || left_text != right_text {
18163                    return false;
18164                }
18165            }
18166            (
18167                CppTemplateTerm::Node {
18168                    kind: left_kind,
18169                    children: left_children,
18170                },
18171                CppTemplateTerm::Node {
18172                    kind: right_kind,
18173                    children: right_children,
18174                },
18175            ) => {
18176                if left_kind != right_kind || left_children.len() != right_children.len() {
18177                    return false;
18178                }
18179                work.extend(left_children.iter().zip(right_children).rev());
18180            }
18181            _ => return false,
18182        }
18183    }
18184    true
18185}
18186
18187pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
18188    let mut components = Vec::new();
18189    let mut stack = vec![node];
18190    while let Some(current) = stack.pop() {
18191        match current.kind() {
18192            "identifier"
18193            | "field_identifier"
18194            | "namespace_identifier"
18195            | "type_identifier"
18196            | "operator_name"
18197            | "destructor_name" => components.push(current),
18198            "template_type" | "template_function" => {
18199                stack.push(current.child_by_field_name("name")?);
18200            }
18201            "dependent_name" => stack.push(current.named_child(0)?),
18202            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
18203                stack.push(current.child_by_field_name("name")?);
18204                if let Some(scope) = current.child_by_field_name("scope") {
18205                    stack.push(scope);
18206                }
18207            }
18208            "nested_namespace_specifier" => {
18209                for index in (0..current.named_child_count()).rev() {
18210                    stack.push(current.named_child(index)?);
18211                }
18212            }
18213            _ => return None,
18214        }
18215    }
18216    Some(components)
18217}
18218
18219pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
18220    node.child_by_field_name("scope").is_none()
18221        && node.child(0).is_some_and(|child| child.kind() == "::")
18222}
18223
18224fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
18225    let mut namespaces = Vec::new();
18226    for parent in enclosing_cpp_namespace_nodes(node) {
18227        if let Some(name) = parent.child_by_field_name("name") {
18228            let mut components = Vec::new();
18229            append_cpp_name_components(name, source, &mut components)?;
18230            namespaces.push(components);
18231        }
18232    }
18233    Some(namespaces.into_iter().flatten().collect())
18234}
18235
18236/// Whether a parser-derived namespace path can be reconciled with an indexed
18237/// owner scope without inventing an unrelated short-name binding.
18238///
18239/// Macro namespace sentinels can make tree-sitter omit one or more namespace
18240/// definitions from the ancestor chain. Preserve the order of every namespace
18241/// that did survive parsing, but allow indexed components between them. An
18242/// empty path is accepted only when the declarator itself supplies a nested
18243/// owner suffix such as `Outer::Inner`: together with the indexed enclosing
18244/// owner chain, that suffix is structural evidence that a namespace was lost.
18245/// A one-segment owner at the translation-unit root remains insufficient.
18246fn indexed_namespace_path_is_recoverable(
18247    lexical_scope: &[String],
18248    indexed_owner_scope: &[String],
18249    explicit_owner_component_count: usize,
18250) -> bool {
18251    if lexical_scope.is_empty() {
18252        return explicit_owner_component_count > 1;
18253    }
18254    if lexical_scope.len() >= indexed_owner_scope.len() {
18255        return false;
18256    }
18257    super::cpp_namespace_path_is_ordered_subsequence(lexical_scope, indexed_owner_scope)
18258}
18259
18260pub use brokk_bifrost_core::analyzer::tree_walk::has_ancestor_kind;
18261
18262/// Whether a declaration type is initialized with a pointer cast.
18263///
18264/// This structured shape has an independent qualified occurrence in addition
18265/// to the cast descriptor below it. Other declarations must keep their normal
18266/// full-range occurrence only.
18267pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
18268    let mut current = Some(node);
18269    while let Some(candidate) = current {
18270        if candidate.kind() == "declaration" {
18271            if !crate::declarations::cpp_type_field_contains_node(candidate, node) {
18272                return false;
18273            }
18274            let mut cursor = candidate.walk();
18275            return candidate.named_children(&mut cursor).any(|child| {
18276                child.kind() == "init_declarator"
18277                    && child
18278                        .child_by_field_name("value")
18279                        .is_some_and(|value| value.kind() == "cast_expression")
18280            });
18281        }
18282        current = candidate.parent();
18283    }
18284    false
18285}
18286
18287#[derive(Clone, Copy, PartialEq, Eq)]
18288pub(crate) enum QualifiedAliasReferenceKind {
18289    Ordinary,
18290    ConstructorWithExpressionArgument,
18291    ExhaustiveTemplate,
18292}
18293
18294/// Whether a qualified alias reference preserves the requested target.
18295///
18296/// The complete qualified spelling and its terminal identifier are both valid
18297/// occurrences when the visible alias path is structurally proven to name the
18298/// target. Template aliases use their bound arguments; ordinary aliases use
18299/// their structured primary chain.
18300pub(crate) fn qualified_alias_reference_preserves_target(
18301    node: Node<'_>,
18302    target: &CodeUnit,
18303    analyzer: &CppGraphSource<'_>,
18304    visibility: &VisibilityIndex<'_>,
18305    file: &ProjectFile,
18306    source: &str,
18307) -> Option<QualifiedAliasReferenceKind> {
18308    if !matches!(
18309        node.kind(),
18310        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
18311    ) {
18312        return None;
18313    }
18314    let components = cpp_type_name_components(node, source)?;
18315    let name = components.last()?;
18316    analyzer.type_alias_provider().and_then(|provider| {
18317        visibility
18318            .visible_identifier_candidates(file, name)
18319            .find_map(|candidate| {
18320                let proof = provider.is_type_alias(candidate)
18321                    && canonical_cpp_scope_components(candidate) == components
18322                    && visibility.external_type_candidate_visible_in_context(
18323                        analyzer, file, candidate, node,
18324                    )
18325                    && match cpp_template_reference_arguments(node, source) {
18326                        Some(arguments) => visibility.template_alias_arguments_preserve_target(
18327                            analyzer, file, candidate, &arguments, target,
18328                        ),
18329                        None => visibility.structured_alias_primary_preserves_target(
18330                            analyzer, file, candidate, target,
18331                        ),
18332                    };
18333                proof.then(|| {
18334                    if cpp_template_reference_arguments(node, source).is_some()
18335                        && visibility.is_exhaustive_same_fqn_type_declaration_family(
18336                            analyzer, file, candidate,
18337                        )
18338                    {
18339                        QualifiedAliasReferenceKind::ExhaustiveTemplate
18340                    } else if qualified_alias_constructor_has_expression_argument(node)
18341                        || qualified_alias_local_constructor_declaration(node)
18342                    {
18343                        QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
18344                    } else {
18345                        QualifiedAliasReferenceKind::Ordinary
18346                    }
18347                })
18348            })
18349    })
18350}
18351
18352pub(crate) fn qualified_alias_reference_requires_terminal(
18353    reference: Option<QualifiedAliasReferenceKind>,
18354) -> bool {
18355    matches!(
18356        reference,
18357        Some(
18358            QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
18359                | QualifiedAliasReferenceKind::ExhaustiveTemplate
18360        )
18361    )
18362}
18363
18364fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
18365    let Some(declaration) = node.parent().filter(|parent| {
18366        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
18367    }) else {
18368        return false;
18369    };
18370    let mut cursor = declaration.walk();
18371    declaration.named_children(&mut cursor).any(|child| {
18372        child.kind() == "init_declarator"
18373            && child
18374                .child_by_field_name("value")
18375                .filter(|value| value.kind() == "argument_list")
18376                .is_some_and(|arguments| {
18377                    let mut cursor = arguments.walk();
18378                    arguments.named_children(&mut cursor).any(|argument| {
18379                        let is_parameter = matches!(
18380                            argument.kind(),
18381                            "parameter_declaration" | "optional_parameter_declaration"
18382                        );
18383                        if is_parameter {
18384                            argument
18385                                .child_by_field_name("type")
18386                                .is_some_and(|type_node| {
18387                                    type_node.kind() == "type_identifier"
18388                                        && argument.child_by_field_name("declarator").is_none()
18389                                })
18390                        } else {
18391                            !argument.kind().ends_with("_literal")
18392                                && !matches!(argument.kind(), "true" | "false" | "nullptr")
18393                        }
18394                    })
18395                })
18396    })
18397}
18398
18399/// Tree-sitter represents a local C++ direct construction such as
18400/// `Alias value(argument)` as a function declarator. Restrict that recovery to
18401/// declarations inside a compound statement so namespace-scope function
18402/// declarations with the same qualified return type stay full-range only.
18403fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
18404    let Some(declaration) = node.parent().filter(|parent| {
18405        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
18406    }) else {
18407        return false;
18408    };
18409    if declaration
18410        .parent()
18411        .is_none_or(|parent| parent.kind() != "compound_statement")
18412    {
18413        return false;
18414    }
18415    let mut cursor = declaration.walk();
18416    declaration
18417        .named_children(&mut cursor)
18418        .any(|child| child.kind() == "function_declarator")
18419}
18420
18421/// Return the terminal identifier represented by a callable or type callee.
18422///
18423/// Qualified, scoped, template, and field wrappers are traversed through their
18424/// grammar fields so both function calls and type constructions emit the token
18425/// that names the referenced declaration.
18426pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
18427    loop {
18428        let next = match node.kind() {
18429            "qualified_identifier"
18430            | "scoped_identifier"
18431            | "template_method"
18432            | "template_function"
18433            | "template_type" => node.child_by_field_name("name"),
18434            "field_expression" => node.child_by_field_name("field"),
18435            _ => None,
18436        };
18437        let Some(next) = next else {
18438            return node;
18439        };
18440        node = next;
18441    }
18442}
18443
18444#[derive(Clone, Copy)]
18445pub struct RecoveredRelationalTemplateMemberCall<'tree> {
18446    pub receiver: Node<'tree>,
18447    pub member: Node<'tree>,
18448    pub arity: usize,
18449}
18450
18451/// Recover `receiver.member<argument>(call_arguments)` when tree-sitter chose
18452/// nested relational expressions instead of a `template_method` call.
18453///
18454/// The recovery uses only grammar fields: the selected field must be the left
18455/// side of `<`, that expression must be the left side of `>`, and the right
18456/// side of `>` must be the parenthesized call arguments. Semantic callers must
18457/// additionally prove the receiver owner and the member's template status.
18458pub fn recovered_relational_template_member_call(
18459    field: Node<'_>,
18460) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
18461    if field.kind() != "field_expression" {
18462        return None;
18463    }
18464    let receiver = field
18465        .child_by_field_name("argument")
18466        .or_else(|| field.child_by_field_name("object"))?;
18467    let member = field.child_by_field_name("field")?;
18468    let less = field.parent()?;
18469    if less.kind() != "binary_expression"
18470        || less.child_by_field_name("left") != Some(field)
18471        || less
18472            .child_by_field_name("operator")
18473            .is_none_or(|operator| operator.kind() != "<")
18474        || less.child_by_field_name("right").is_none()
18475    {
18476        return None;
18477    }
18478    let greater = less.parent()?;
18479    if greater.kind() != "binary_expression"
18480        || greater.child_by_field_name("left") != Some(less)
18481        || greater
18482            .child_by_field_name("operator")
18483            .is_none_or(|operator| operator.kind() != ">")
18484    {
18485        return None;
18486    }
18487    let arguments = greater.child_by_field_name("right")?;
18488    if arguments.kind() != "parenthesized_expression" {
18489        return None;
18490    }
18491    let arity = parenthesized_call_argument_arity(arguments)?;
18492    Some(RecoveredRelationalTemplateMemberCall {
18493        receiver,
18494        member,
18495        arity,
18496    })
18497}
18498
18499fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
18500    let expression = arguments.named_child(0)?;
18501    if expression.kind() != "comma_expression" {
18502        return Some(1);
18503    }
18504    let mut arity = 0usize;
18505    let mut stack = vec![expression];
18506    while let Some(node) = stack.pop() {
18507        if node.kind() == "comma_expression" {
18508            stack.push(node.child_by_field_name("right")?);
18509            stack.push(node.child_by_field_name("left")?);
18510        } else {
18511            arity += 1;
18512        }
18513    }
18514    Some(arity)
18515}
18516
18517/// Whether `node` is part of a call's callee expression, walking only through
18518/// the grammar wrappers that can structurally contain that callee.
18519pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
18520    while let Some(parent) = node.parent() {
18521        match parent.kind() {
18522            "call_expression" => {
18523                return parent
18524                    .child_by_field_name("function")
18525                    .or_else(|| parent.named_child(0))
18526                    == Some(node);
18527            }
18528            "qualified_identifier"
18529            | "scoped_identifier"
18530            | "template_function"
18531            | "template_type"
18532            | "field_expression" => node = parent,
18533            _ => return false,
18534        }
18535    }
18536    false
18537}
18538
18539/// Expand a C++ type-name component to the complete written reference that
18540/// owns it. Qualified path wrappers that stop before a later component remain
18541/// separate, so a type used as an owner keeps its prefix range.
18542pub fn type_reference_hit_node(mut node: Node<'_>) -> Node<'_> {
18543    loop {
18544        let Some(parent) = node.parent() else {
18545            return node;
18546        };
18547        if parent.kind() == "template_type"
18548            && parent
18549                .child_by_field_name("name")
18550                .is_some_and(|name| same_node(name, node))
18551        {
18552            node = parent;
18553            continue;
18554        }
18555        if matches!(
18556            parent.kind(),
18557            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
18558        ) && (parent
18559            .child_by_field_name("scope")
18560            .is_some_and(|scope| same_node(scope, node))
18561            || parent
18562                .child_by_field_name("name")
18563                .is_some_and(|name| same_node(name, node)))
18564            && parent.end_byte() == node.end_byte()
18565        {
18566            node = parent;
18567            continue;
18568        }
18569        return node;
18570    }
18571}
18572
18573pub fn normalize_type_text(value: &str) -> String {
18574    strip_tag_type_prefix(
18575        normalize_cpp_whitespace(value)
18576            .trim_start_matches("const ")
18577            .trim_end_matches('*')
18578            .trim_end_matches('&')
18579            .trim(),
18580    )
18581    .to_string()
18582}
18583
18584use crate::declarations::strip_tag_type_prefix;
18585
18586pub fn normalize_reference_name(value: &str) -> Option<String> {
18587    let normalized = normalize_cpp_reference_text(value);
18588    (!normalized.is_empty()).then_some(normalized)
18589}
18590
18591pub fn normalize_cpp_reference_text(value: &str) -> String {
18592    let mut text = normalize_cpp_whitespace(value)
18593        .trim_start_matches("new ")
18594        .trim()
18595        .to_string();
18596    if let Some(index) = text.find(['(', '{']) {
18597        text.truncate(index);
18598    }
18599    if let Some(index) = text.find('<') {
18600        text.truncate(index);
18601    }
18602    let normalized = text
18603        .trim()
18604        .trim_start_matches("const ")
18605        .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
18606        .trim_matches(':')
18607        .trim();
18608    strip_tag_type_prefix(normalized).to_string()
18609}
18610
18611pub fn cpp_name_for(unit: &CodeUnit) -> String {
18612    let short = unit.short_name().replace(['.', '$'], "::");
18613    if unit.package_name().is_empty() {
18614        short
18615    } else {
18616        format!("{}::{}", unit.package_name(), short)
18617    }
18618}
18619
18620/// Render an indexed C++ qualified name from its authoritative FqName
18621/// segments. Unlike the legacy `cpp_name_for` renderer, this preserves dots
18622/// that belong to a template argument (for example `Args...`).
18623fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
18624    let fq = unit.fq();
18625    if fq.is_empty() {
18626        return None;
18627    }
18628    let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
18629    Some(
18630        fq.segments()
18631            .iter()
18632            .map(|&segment| interner.resolve(segment).0)
18633            .collect::<Vec<_>>()
18634            .join("::"),
18635    )
18636}
18637
18638fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
18639    canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
18640        || unit.fq().is_empty() && cpp_name_for(unit) == expected
18641}
18642
18643/// Return the indexed C++ owner scope without reparsing its rendered name.
18644///
18645/// Template spellings are opaque within an indexed `FqName` segment.  In
18646/// particular, the ellipsis in a parameter pack (`Args...`) is part of the
18647/// `AtomicHook<...>` type segment; feeding the legacy all-`::` rendering back
18648/// through `parse_symbol_path` would mistake those dots for component
18649/// separators.  Cache-loaded/legacy units may still have an empty structured
18650/// name, so retain the parser only as that explicit fallback.
18651///
18652/// Only a terminal member is not a scope. A callable that encloses a
18653/// function-local class (`run$block@3:12$Local`) is one of that class's scope
18654/// components, exactly as its canonical name spells it.
18655pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
18656    let fq = unit.fq();
18657    if !fq.is_empty() {
18658        let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
18659        let segments = fq.segments();
18660        let scope = segments
18661            .iter()
18662            .enumerate()
18663            .filter_map(|(index, &segment)| {
18664                let (text, kind) = interner.resolve(segment);
18665                match kind {
18666                    SegmentKind::Package | SegmentKind::Type | SegmentKind::Nested => true,
18667                    SegmentKind::Member => index + 1 < segments.len(),
18668                    SegmentKind::Path | SegmentKind::Companion | SegmentKind::Unknown => false,
18669                }
18670                .then(|| text.to_string())
18671            })
18672            .collect();
18673        return scope;
18674    }
18675    brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
18676        brokk_bifrost_core::analyzer::Language::Cpp,
18677        &cpp_name_for(unit),
18678    )
18679}
18680
18681// fqname-M4: the second stage splits on the individual chars '.', '-', '>'
18682// (not the substring "->"), which deliberately reduces an `operator->`-style
18683// terminal segment to an empty tail rather than keeping it intact; the shared
18684// structured splitter's cpp operator-token merge would keep `operator->`
18685// whole instead, changing this function's result — `name_matches_callable`'s
18686// `expected.starts_with("operator")` fallback exists specifically to
18687// compensate for that reduction, and a pinned regression test
18688// (`operator-> must not be reduced with terminal_name-style punctuation
18689// splitting`) asserts today's char-class behavior. Not equivalence-provable;
18690// revisit alongside that pinned test if it is ever relaxed.
18691pub fn terminal_name(value: &str) -> &str {
18692    value
18693        .rsplit("::")
18694        .next()
18695        .unwrap_or(value)
18696        .rsplit(['.', '-', '>'])
18697        .next()
18698        .unwrap_or(value)
18699        .trim()
18700}
18701
18702pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
18703    terminal_name(&normalize_cpp_reference_text(value)) == expected
18704}
18705
18706pub fn name_matches_callable(value: &str, expected: &str) -> bool {
18707    name_matches_terminal(value, expected)
18708        || expected.starts_with("operator")
18709            && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
18710}
18711
18712pub fn name_mentions(value: &str, expected: &str) -> bool {
18713    normalize_cpp_reference_text(value)
18714        .split("::")
18715        .any(|part| part == expected)
18716}
18717
18718pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
18719    let cpp_name = cpp_name_for(unit);
18720    if reference.contains("::") {
18721        return reference == cpp_name;
18722    }
18723    reference == cpp_name
18724        || terminal_name(reference) == unit.identifier()
18725            && (unit.package_name().is_empty() || reference == unit.identifier())
18726}
18727
18728pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
18729    match kind {
18730        TargetKind::Type
18731        | TargetKind::Constructor
18732        | TargetKind::Method
18733        | TargetKind::MemberField => true,
18734        TargetKind::FreeFunction => unit.is_function(),
18735        TargetKind::GlobalField => unit.is_field(),
18736        TargetKind::Macro => unit.is_macro(),
18737    }
18738}
18739
18740fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
18741    let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
18742    let target_name = cpp_name_for(target);
18743    if normalized.contains("::") {
18744        return normalized == target_name;
18745    }
18746    if let Some(namespace) = alias.namespace.as_deref() {
18747        return namespace_prefixes(namespace)
18748            .into_iter()
18749            .any(|prefix| format!("{prefix}::{normalized}") == target_name);
18750    }
18751    target.package_name().is_empty() && normalized == target.identifier()
18752}
18753
18754/// The declared return type text of a C++ function unit, with leading declaration specifiers
18755/// stripped, e.g. `T*` for `T* operator->()`.
18756pub fn cpp_function_return_type_text(
18757    analyzer: &CppGraphSource<'_>,
18758    function: &CodeUnit,
18759) -> Option<String> {
18760    let metadata = analyzer.signature_metadata(function);
18761    let first = metadata.first()?.return_type_text()?;
18762    metadata
18763        .iter()
18764        .all(|metadata| metadata.return_type_text() == Some(first))
18765        .then(|| first.to_owned())
18766}
18767
18768pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
18769    // fqname-M4: `cpp_name_for` is a bespoke all-`::` rendering of the unit's
18770    // name (it replaces every `.`/`$` in `short_name` with `::`), which is NOT
18771    // the same string `default_parent_fq_name`/`fq().parent()` would render:
18772    // the structured `FqName`'s native cpp display deliberately keeps `.` (not
18773    // `::`) between a trailing `Package` segment and a following `Type`
18774    // segment (see `separator` in `fq_name.rs`, landed for issue #1163), so
18775    // popping the unit's own `fq()` segment would NOT reproduce this
18776    // fully-`::`-joined string. Left as a split on the locally-built
18777    // all-colon string rather than the unit's structured name.
18778    cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
18779        namespace
18780            .strip_prefix("anonymous_namespace::")
18781            .unwrap_or(namespace)
18782            .to_string()
18783    })
18784}
18785
18786/// The namespace lookup tiers, from the innermost namespace outward.
18787pub fn namespace_prefixes(namespace: &str) -> Vec<String> {
18788    // `namespace` is built by `cpp_name_for`/`cpp_namespace_for` with every
18789    // non-`::` separator already converted to `::`, so re-tokenizing it with
18790    // the shared structured splitter and progressively popping the last
18791    // component reproduces the `rsplit_once("::")` outward walk exactly (same
18792    // shape as `cpp_qualifier_lookup_tiers`'s namespace-chain walk).
18793    let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
18794        brokk_bifrost_core::analyzer::Language::Cpp,
18795        namespace,
18796    );
18797    let mut prefixes = Vec::new();
18798    while !parts.is_empty() {
18799        prefixes.push(parts.join("::"));
18800        parts.pop();
18801    }
18802    prefixes
18803}
18804
18805fn nearest_namespace_candidates(
18806    candidates: Vec<CodeUnit>,
18807    normalized: &str,
18808    lexical_namespace: Option<&str>,
18809) -> Vec<CodeUnit> {
18810    if normalized.contains("::") {
18811        return candidates;
18812    }
18813    if let Some(namespace) = lexical_namespace {
18814        for prefix in namespace_prefixes(namespace) {
18815            let scoped = candidates
18816                .iter()
18817                .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
18818                .cloned()
18819                .collect::<Vec<_>>();
18820            if !scoped.is_empty() {
18821                return scoped;
18822            }
18823        }
18824    }
18825    candidates
18826        .into_iter()
18827        .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
18828        .collect()
18829}
18830
18831pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
18832    let mut namespaces = Vec::new();
18833    for parent in enclosing_cpp_namespace_nodes(node) {
18834        if let Some(name) = parent.child_by_field_name("name") {
18835            let namespace = normalize_cpp_reference_text(node_text(name, source));
18836            if !namespace.is_empty() {
18837                namespaces.push(namespace);
18838            }
18839        }
18840    }
18841    if namespaces.is_empty() {
18842        None
18843    } else {
18844        Some(namespaces.join("::"))
18845    }
18846}
18847
18848/// Like [`precise_parent_of`], but drops module (namespace) parents. A namespace is a scope, not a
18849/// type or receiver, so namespace-scoped functions and constants resolve as free functions and
18850/// globals rather than members.
18851pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
18852    type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
18853}
18854
18855fn type_owner_resolution(
18856    analyzer: &CppGraphSource<'_>,
18857    code_unit: &CodeUnit,
18858) -> Option<ResolvedTypeOwner> {
18859    precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
18860}
18861
18862fn target_type_owner_resolution(
18863    analyzer: &CppGraphSource<'_>,
18864    code_unit: &CodeUnit,
18865) -> Option<ResolvedTypeOwner> {
18866    match type_owner_resolution(analyzer, code_unit) {
18867        Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
18868        Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
18869    }
18870}
18871
18872/// Recover method identity for an indexed out-of-line definition when the
18873/// ordinary parent edge is absent. Prefer the unique include-visible forward
18874/// declaration, then classify exact-FQN class declarations elsewhere in the
18875/// workspace. A unique complete declaration wins; otherwise multiple forward
18876/// declarations are one owner only when they all share one logical identity.
18877/// The qualified callable FQN proves that owner spelling even when its defining
18878/// header is outside the scan file's include closure, while unknown or competing
18879/// complete declarations remain ambiguous.
18880/// This is deliberately target-only: canonical declaration resolution must
18881/// continue to prefer the callable definition rather than replacing it with
18882/// the recovered owner.
18883fn target_forward_owner_resolution(
18884    analyzer: &CppGraphSource<'_>,
18885    code_unit: &CodeUnit,
18886) -> Option<ResolvedTypeOwner> {
18887    if !code_unit.is_function() {
18888        return None;
18889    }
18890    // A top-level free function has no owner at all, and `FqName::parent`
18891    // answers the empty name rather than `None` for a one-segment identity.
18892    // `default_parent_fq_name`, which this replaced, filtered that case out;
18893    // asking the relational store for the empty name is a batch error that
18894    // fails the whole target frontier.
18895    let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
18896    let cpp = analyzer.cpp?;
18897    let mut visible_files = HashSet::default();
18898    collect_include_closure(
18899        analyzer,
18900        cpp.include_target_index(),
18901        code_unit.source(),
18902        &mut visible_files,
18903        None,
18904    );
18905    let candidates = analyzer.workspace_definitions().exact(&owner_name);
18906    let visible_candidates = candidates
18907        .iter()
18908        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
18909        .cloned()
18910        .collect::<Vec<_>>();
18911    match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
18912        DirectOwnerResolution::UniqueFull(unit) => {
18913            return Some(ResolvedTypeOwner {
18914                unit,
18915                is_forward_declaration: false,
18916            });
18917        }
18918        DirectOwnerResolution::ForwardsOnly(forwards) => {
18919            return (forwards.len() == 1).then(|| ResolvedTypeOwner {
18920                unit: forwards.into_iter().next().unwrap(),
18921                is_forward_declaration: true,
18922            });
18923        }
18924        DirectOwnerResolution::Ambiguous => return None,
18925        DirectOwnerResolution::None => {}
18926    }
18927
18928    let candidates = candidates
18929        .into_iter()
18930        .filter(|candidate| candidate.is_class())
18931        .collect::<Vec<_>>();
18932    let (unit, is_forward_declaration) =
18933        match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
18934            DirectOwnerResolution::UniqueFull(unit) => (unit, false),
18935            DirectOwnerResolution::ForwardsOnly(forwards) => {
18936                (unique_logical_forward_owner(forwards)?, true)
18937            }
18938            DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
18939        };
18940    Some(ResolvedTypeOwner {
18941        unit,
18942        is_forward_declaration,
18943    })
18944}
18945
18946pub fn precise_parent_of(
18947    analyzer: &CppGraphSource<'_>,
18948    visibility: &VisibilityIndex<'_>,
18949    code_unit: &CodeUnit,
18950) -> Option<CodeUnit> {
18951    visibility.cached_precise_parent_of(analyzer, code_unit)
18952}
18953
18954fn precise_parent_resolution(
18955    analyzer: &CppGraphSource<'_>,
18956    code_unit: &CodeUnit,
18957) -> Option<ResolvedTypeOwner> {
18958    #[cfg(any(test, feature = "test-support"))]
18959    if let Some(cpp) = analyzer.cpp {
18960        cpp.record_cpp_parent_resolution_for_test();
18961    }
18962    if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
18963        return Some(ResolvedTypeOwner {
18964            unit,
18965            is_forward_declaration: false,
18966        });
18967    }
18968    let fallback = analyzer.parent_of(code_unit);
18969    if !code_unit.owner_is_type_scope() {
18970        return fallback.map(|unit| ResolvedTypeOwner {
18971            unit,
18972            is_forward_declaration: false,
18973        });
18974    }
18975    let owner_fq = code_unit
18976        .fq()
18977        .parent()
18978        .expect("a unit with an owner identifier has a structured parent");
18979    let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
18980    match same_source_owner(analyzer, code_unit, &owner_candidates) {
18981        DirectOwnerResolution::UniqueFull(owner) => {
18982            return Some(ResolvedTypeOwner {
18983                unit: owner,
18984                is_forward_declaration: false,
18985            });
18986        }
18987        DirectOwnerResolution::Ambiguous => return None,
18988        DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
18989    }
18990    match directly_included_owner(analyzer, code_unit, &owner_candidates) {
18991        DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
18992            unit: owner,
18993            is_forward_declaration: false,
18994        }),
18995        DirectOwnerResolution::Ambiguous => None,
18996        DirectOwnerResolution::ForwardsOnly(forwards) => {
18997            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
18998                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
18999                    unit: owner,
19000                    is_forward_declaration: false,
19001                }),
19002                FullOwnerResolution::None => {
19003                    unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
19004                        unit,
19005                        is_forward_declaration: true,
19006                    })
19007                }
19008                FullOwnerResolution::Ambiguous => None,
19009            }
19010        }
19011        DirectOwnerResolution::None => {
19012            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
19013                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
19014                    unit: owner,
19015                    is_forward_declaration: false,
19016                }),
19017                FullOwnerResolution::Ambiguous => None,
19018                FullOwnerResolution::None => fallback
19019                    .filter(|parent| {
19020                        parent.source() == code_unit.source()
19021                            && parent.fq() == &owner_fq
19022                            && (!parent.is_class()
19023                                || cpp_class_declaration_strength(analyzer, parent)
19024                                    == CppClassDeclarationStrength::Full)
19025                    })
19026                    .map(|unit| ResolvedTypeOwner {
19027                        unit,
19028                        is_forward_declaration: false,
19029                    }),
19030            }
19031        }
19032    }
19033}
19034
19035fn exact_structural_type_parent(
19036    analyzer: &CppGraphSource<'_>,
19037    code_unit: &CodeUnit,
19038) -> Option<CodeUnit> {
19039    if !code_unit.is_function() && !code_unit.is_field() {
19040        return None;
19041    }
19042    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
19043    let cpp = analyzer.cpp?;
19044    let parent = cpp.structural_parent_of(code_unit)?;
19045    (!parent.is_module()
19046        && parent.source() == code_unit.source()
19047        && parent.package_name() == code_unit.package_name()
19048        && parent.short_name() == encoded_owner)
19049        .then_some(parent)
19050}
19051
19052fn same_source_owner(
19053    analyzer: &CppGraphSource<'_>,
19054    code_unit: &CodeUnit,
19055    owner_candidates: &[CodeUnit],
19056) -> DirectOwnerResolution {
19057    let candidates = owner_candidates
19058        .iter()
19059        .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
19060        .cloned()
19061        .collect::<Vec<_>>();
19062    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
19063    classify_direct_owner_candidates(analyzer, candidates.into_iter())
19064}
19065
19066fn visible_full_cpp_owner(
19067    analyzer: &CppGraphSource<'_>,
19068    code_unit: &CodeUnit,
19069    owner_candidates: &[CodeUnit],
19070) -> FullOwnerResolution {
19071    let Some(cpp) = analyzer.cpp else {
19072        return FullOwnerResolution::None;
19073    };
19074    let mut visible_files = HashSet::default();
19075    collect_include_closure(
19076        analyzer,
19077        cpp.include_target_index(),
19078        code_unit.source(),
19079        &mut visible_files,
19080        None,
19081    );
19082    let candidates = owner_candidates
19083        .iter()
19084        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
19085        .cloned()
19086        .collect::<Vec<_>>();
19087    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
19088    match collapse_owner_candidates(candidates.into_iter().map(|candidate| {
19089        let strength = cpp_class_declaration_strength(analyzer, &candidate);
19090        (candidate, strength)
19091    })) {
19092        DirectOwnerResolution::UniqueFull(owner) => FullOwnerResolution::Unique(owner),
19093        DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {
19094            FullOwnerResolution::None
19095        }
19096        DirectOwnerResolution::Ambiguous => FullOwnerResolution::Ambiguous,
19097    }
19098}
19099
19100pub enum DirectOwnerResolution {
19101    None,
19102    ForwardsOnly(Vec<CodeUnit>),
19103    UniqueFull(CodeUnit),
19104    Ambiguous,
19105}
19106
19107enum FullOwnerResolution {
19108    None,
19109    Unique(CodeUnit),
19110    Ambiguous,
19111}
19112
19113fn directly_included_owner(
19114    analyzer: &CppGraphSource<'_>,
19115    code_unit: &CodeUnit,
19116    owner_candidates: &[CodeUnit],
19117) -> DirectOwnerResolution {
19118    let Some(cpp) = analyzer.cpp else {
19119        return DirectOwnerResolution::None;
19120    };
19121    let direct_includes: HashSet<ProjectFile> = analyzer
19122        .canonical_include_paths(code_unit.source())
19123        .unwrap_or_default()
19124        .into_iter()
19125        .flat_map(|include| {
19126            resolve_include_targets_with_index(
19127                code_unit.source(),
19128                &include,
19129                cpp.include_target_index(),
19130            )
19131        })
19132        .collect();
19133    let candidates = owner_candidates
19134        .iter()
19135        .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
19136        .cloned()
19137        .collect::<Vec<_>>();
19138    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
19139    classify_direct_owner_candidates(analyzer, candidates.into_iter())
19140}
19141
19142fn prefer_member_declaring_owners(
19143    analyzer: &CppGraphSource<'_>,
19144    member: &CodeUnit,
19145    candidates: Vec<CodeUnit>,
19146) -> Vec<CodeUnit> {
19147    let matching = candidates
19148        .iter()
19149        .filter(|owner| owner_declares_member(analyzer, owner, member))
19150        .cloned()
19151        .collect::<Vec<_>>();
19152    if matching.is_empty() {
19153        candidates
19154    } else {
19155        matching
19156    }
19157}
19158
19159fn owner_declares_member(
19160    analyzer: &CppGraphSource<'_>,
19161    owner: &CodeUnit,
19162    member: &CodeUnit,
19163) -> bool {
19164    analyzer.direct_children(owner).into_iter().any(|child| {
19165        child.kind() == member.kind()
19166            && child.identifier() == member.identifier()
19167            && child.signature() == member.signature()
19168    })
19169}
19170
19171fn classify_direct_owner_candidates(
19172    analyzer: &CppGraphSource<'_>,
19173    candidates: impl Iterator<Item = CodeUnit>,
19174) -> DirectOwnerResolution {
19175    collapse_owner_candidates(candidates.map(|candidate| {
19176        let strength = cpp_class_declaration_strength(analyzer, &candidate);
19177        (candidate, strength)
19178    }))
19179}
19180
19181pub fn collapse_owner_candidates(
19182    candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
19183) -> DirectOwnerResolution {
19184    let mut full_definition = None;
19185    let mut forwards = Vec::new();
19186    for (candidate, strength) in candidates {
19187        match strength {
19188            CppClassDeclarationStrength::Full if full_definition.is_some() => {
19189                return DirectOwnerResolution::Ambiguous;
19190            }
19191            CppClassDeclarationStrength::Full => full_definition = Some(candidate),
19192            CppClassDeclarationStrength::Forward => forwards.push(candidate),
19193            CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
19194        }
19195    }
19196    if let Some(owner) = full_definition {
19197        DirectOwnerResolution::UniqueFull(owner)
19198    } else if !forwards.is_empty() {
19199        DirectOwnerResolution::ForwardsOnly(forwards)
19200    } else {
19201        DirectOwnerResolution::None
19202    }
19203}
19204
19205#[cfg(any(test, feature = "test-support"))]
19206pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
19207    unique_logical_forward_owner(forwards)
19208}
19209
19210fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
19211    let first = forwards.pop()?;
19212    forwards
19213        .iter()
19214        .all(|forward| same_logical_symbol(forward, &first))
19215        .then_some(first)
19216}
19217
19218pub fn cpp_class_declaration_strength(
19219    analyzer: &CppGraphSource<'_>,
19220    candidate: &CodeUnit,
19221) -> CppClassDeclarationStrength {
19222    // The answer is a pure function of the unit's ranges and its file's tree,
19223    // and the inverse scan asks it once per declaration seed. On a translation
19224    // unit the parser could not fully recover, each ask re-derives the
19225    // export-macro recovery shapes from the file's `ERROR` subtrees, so without
19226    // this memo one file's scan is quadratic in its own size: 97% of Catch2's
19227    // 284 s inverse scan of `extras/catch_amalgamated.cpp` was in this call
19228    // (#1496).
19229    let Some(cpp) = analyzer.cpp else {
19230        return uncached_cpp_class_declaration_strength(analyzer, candidate);
19231    };
19232    if let Some(strength) = cpp.cached_class_declaration_strength(candidate) {
19233        return strength;
19234    }
19235    let strength = uncached_cpp_class_declaration_strength(analyzer, candidate);
19236    cpp.cache_class_declaration_strength(candidate, strength);
19237    strength
19238}
19239
19240fn uncached_cpp_class_declaration_strength(
19241    analyzer: &CppGraphSource<'_>,
19242    candidate: &CodeUnit,
19243) -> CppClassDeclarationStrength {
19244    if let Some(facts) = analyzer.declaration_source_properties(candidate) {
19245        if facts
19246            .iter()
19247            .any(|fact| fact.class_strength == CppClassDeclarationStrength::Full)
19248        {
19249            return CppClassDeclarationStrength::Full;
19250        }
19251        if facts
19252            .iter()
19253            .any(|fact| fact.class_strength == CppClassDeclarationStrength::Forward)
19254        {
19255            return CppClassDeclarationStrength::Forward;
19256        }
19257    }
19258    if let Some(cpp) = analyzer.cpp
19259        && let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source())
19260    {
19261        return cpp_class_declaration_strength_in_tree(
19262            analyzer,
19263            &cpp.recovered_export_class_index(analyzer.token, candidate.source()),
19264            candidate,
19265            prepared.source(),
19266            prepared.tree().root_node(),
19267        );
19268    }
19269    let Some(source) = analyzer.indexed_source(candidate.source()) else {
19270        return CppClassDeclarationStrength::Unknown;
19271    };
19272    let mut parser = Parser::new();
19273    if parser
19274        .set_language(&tree_sitter_cpp::LANGUAGE.into())
19275        .is_err()
19276    {
19277        return CppClassDeclarationStrength::Unknown;
19278    }
19279    let Some(tree) = parser.parse(&source, None) else {
19280        return CppClassDeclarationStrength::Unknown;
19281    };
19282    // This branch reparses a file the analyzer has no prepared tree for, so its
19283    // recovery index is that one tree's and cannot be shared.
19284    let recovered_export_classes =
19285        CppRecoveredExportClassIndex::build(tree.root_node(), source.as_str());
19286    cpp_class_declaration_strength_in_tree(
19287        analyzer,
19288        &recovered_export_classes,
19289        candidate,
19290        &source,
19291        tree.root_node(),
19292    )
19293}
19294
19295fn cpp_class_declaration_strength_in_tree(
19296    analyzer: &CppGraphSource<'_>,
19297    recovered_export_classes: &CppRecoveredExportClassIndex,
19298    candidate: &CodeUnit,
19299    source: &str,
19300    root: Node<'_>,
19301) -> CppClassDeclarationStrength {
19302    let ranges = analyzer.ranges(candidate);
19303    let mut saw_forward = false;
19304    for range in ranges {
19305        // The recovered export-macro shapes answer for their own ranges; only a
19306        // range no recovery claims is read as a plain specifier.
19307        match recovered_class_body_at(
19308            recovered_export_classes,
19309            root,
19310            source,
19311            candidate.identifier(),
19312            &range,
19313        ) {
19314            Some(true) => return CppClassDeclarationStrength::Full,
19315            Some(false) => {
19316                saw_forward = true;
19317                continue;
19318            }
19319            None => {}
19320        }
19321        // Only a node covering the range's start byte can be the specifier for
19322        // this range, so apply that test where nodes enter the stack rather
19323        // than where they leave it. Pushing first meant one ask enqueued every
19324        // sibling at every level it descended, which on a translation unit with
19325        // thousands of top-level declarations is a per-ask cost proportional to
19326        // the file (#1496).
19327        let covers_range_start = |node: &Node<'_>| {
19328            node.start_byte() <= range.start_byte && node.end_byte() >= range.start_byte
19329        };
19330        let mut stack = Vec::new();
19331        if covers_range_start(&root) {
19332            stack.push(root);
19333        }
19334        while let Some(node) = stack.pop() {
19335            if node.start_byte() == range.start_byte
19336                && node.end_byte() == range.end_byte
19337                && matches!(
19338                    node.kind(),
19339                    "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
19340                )
19341            {
19342                if cpp_class_node_has_body(node) {
19343                    return CppClassDeclarationStrength::Full;
19344                }
19345                saw_forward = true;
19346            }
19347            let mut cursor = node.walk();
19348            stack.extend(node.named_children(&mut cursor).filter(covers_range_start));
19349        }
19350    }
19351    if saw_forward {
19352        CppClassDeclarationStrength::Forward
19353    } else {
19354        CppClassDeclarationStrength::Unknown
19355    }
19356}
19357
19358fn cpp_class_node_has_body(node: Node<'_>) -> bool {
19359    child_by_field_or_named_child_of_kinds(
19360        node,
19361        "body",
19362        &[
19363            "declaration_list",
19364            "field_declaration_list",
19365            "enumerator_list",
19366        ],
19367    )
19368    .is_some()
19369}
19370
19371#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19372enum CppCTagKind {
19373    Struct,
19374    Union,
19375}
19376
19377fn indexed_c_tag_kind(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CppCTagKind> {
19378    let declaration = analyzer.get_source(code_unit, false)?;
19379    let mut parser = Parser::new();
19380    parser
19381        .set_language(&tree_sitter_cpp::LANGUAGE.into())
19382        .ok()?;
19383    let tree = parser.parse(&declaration, None)?;
19384    let mut stack = vec![tree.root_node()];
19385    while let Some(node) = stack.pop() {
19386        let kind = match node.kind() {
19387            "struct_specifier" => CppCTagKind::Struct,
19388            "union_specifier" => CppCTagKind::Union,
19389            _ => {
19390                let mut cursor = node.walk();
19391                stack.extend(node.named_children(&mut cursor));
19392                continue;
19393            }
19394        };
19395        if node
19396            .child_by_field_name("name")
19397            .is_some_and(|name| node_text(name, &declaration) == code_unit.identifier())
19398        {
19399            return Some(kind);
19400        }
19401        let mut cursor = node.walk();
19402        stack.extend(node.named_children(&mut cursor));
19403    }
19404    None
19405}
19406
19407pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
19408    if !code_unit.owner_is_type_scope() {
19409        return None;
19410    }
19411    let owner_fq = code_unit.fq().parent()?;
19412    ctx.analyzer
19413        .workspace_definitions()
19414        .exact(&owner_fq)
19415        .into_iter()
19416        .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
19417}
19418
19419pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
19420    same_logical_symbol(left, right) && left.source() == right.source()
19421}
19422
19423pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
19424    same_symbol(left, right) || same_logical_symbol(left, right)
19425}
19426
19427pub fn same_visible_global_field_symbol(
19428    analyzer: &CppGraphSource<'_>,
19429    internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
19430    left: &CodeUnit,
19431    right: &CodeUnit,
19432) -> bool {
19433    if same_symbol(left, right) {
19434        return true;
19435    }
19436    if !same_logical_symbol(left, right) {
19437        return false;
19438    }
19439    if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
19440        || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
19441    {
19442        left.source() == right.source()
19443    } else {
19444        true
19445    }
19446}
19447
19448fn cpp_global_field_has_internal_linkage_cached(
19449    analyzer: &CppGraphSource<'_>,
19450    cache: &mut HashMap<CodeUnit, bool>,
19451    candidate: &CodeUnit,
19452) -> bool {
19453    if let Some(internal) = cache.get(candidate) {
19454        return *internal;
19455    }
19456    #[cfg(any(test, feature = "test-support"))]
19457    note_cpp_global_field_internal_linkage_classification_for_test();
19458    let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
19459    cache.insert(candidate.clone(), internal);
19460    internal
19461}
19462
19463#[cfg(any(test, feature = "test-support"))]
19464thread_local! {
19465    static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
19466}
19467
19468#[cfg(any(test, feature = "test-support"))]
19469fn note_cpp_global_field_internal_linkage_classification_for_test() {
19470    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
19471        count.set(count.get() + 1);
19472    });
19473}
19474
19475#[cfg(any(test, feature = "test-support"))]
19476pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
19477    body: impl FnOnce() -> T,
19478) -> (T, usize) {
19479    with_thread_local_counter_for_test(
19480        &CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST,
19481        body,
19482    )
19483}
19484
19485#[cfg(any(test, feature = "test-support"))]
19486fn with_thread_local_counter_for_test<T>(
19487    counter: &'static std::thread::LocalKey<Cell<usize>>,
19488    body: impl FnOnce() -> T,
19489) -> (T, usize) {
19490    counter.with(|count| {
19491        count.set(0);
19492        let result = body();
19493        let observed = count.get();
19494        count.set(0);
19495        (result, observed)
19496    })
19497}
19498
19499pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
19500    left.kind() == right.kind()
19501        && left.fq_name() == right.fq_name()
19502        && left.signature() == right.signature()
19503}
19504
19505/// Select one visible spelling from an alias family only when its members
19506/// agree on one structured target. A single candidate needs no target proof.
19507pub fn select_alias_spelling_with_agreed_targets<'a, T: Eq>(
19508    candidates: &[&'a CodeUnit],
19509    mut structured_target: impl FnMut(&CodeUnit) -> Option<T>,
19510) -> Option<&'a CodeUnit> {
19511    let first = *candidates.first()?;
19512    if candidates.len() == 1 {
19513        return Some(first);
19514    }
19515    let first_target = structured_target(first)?;
19516    candidates
19517        .iter()
19518        .skip(1)
19519        .all(|candidate| structured_target(candidate).is_some_and(|target| target == first_target))
19520        .then_some(first)
19521}
19522
19523pub fn cpp_global_field_has_internal_linkage(
19524    analyzer: &CppGraphSource<'_>,
19525    candidate: &CodeUnit,
19526) -> bool {
19527    if !candidate.is_field() || candidate.short_name().contains('.') {
19528        return false;
19529    }
19530    let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
19531        return false;
19532    };
19533    match local_linkage {
19534        CppFieldLinkage::Internal => true,
19535        CppFieldLinkage::External => false,
19536        CppFieldLinkage::InternalUnlessExternalPeer => {
19537            !cpp_global_field_linkage_peers(analyzer, candidate)
19538                .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
19539                .any(|linkage| matches!(linkage, CppFieldLinkage::External))
19540        }
19541    }
19542}
19543
19544fn cpp_global_field_linkage_peers<'a>(
19545    analyzer: &CppGraphSource<'a>,
19546    candidate: &'a CodeUnit,
19547) -> impl Iterator<Item = CodeUnit> + 'a {
19548    let name = candidate.fq().clone();
19549    analyzer
19550        .workspace_definitions()
19551        .exact(&name)
19552        .into_iter()
19553        .filter(move |peer| {
19554            if peer == candidate {
19555                return false;
19556            }
19557            #[cfg(any(test, feature = "test-support"))]
19558            note_cpp_global_field_linkage_peer_inspection_for_test();
19559            same_logical_symbol(peer, candidate)
19560        })
19561}
19562
19563#[cfg(any(test, feature = "test-support"))]
19564thread_local! {
19565    static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
19566}
19567
19568#[cfg(any(test, feature = "test-support"))]
19569fn note_cpp_global_field_linkage_peer_inspection_for_test() {
19570    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
19571        count.set(count.get() + 1);
19572    });
19573}
19574
19575#[cfg(any(test, feature = "test-support"))]
19576pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
19577    body: impl FnOnce() -> T,
19578) -> (T, usize) {
19579    with_thread_local_counter_for_test(&CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST, body)
19580}
19581
19582fn cpp_global_field_declaration_linkage(
19583    analyzer: &CppGraphSource<'_>,
19584    candidate: &CodeUnit,
19585) -> Option<CppFieldLinkage> {
19586    analyzer.cpp_field_linkage(candidate)
19587}
19588
19589/// Agreed alias target spelling captured from the declaring AST type field.
19590pub fn cpp_canonical_alias_target_text(
19591    analyzer: &CppGraphSource<'_>,
19592    unit: &CodeUnit,
19593) -> Option<String> {
19594    let facts = analyzer.declaration_source_properties(unit)?;
19595    let first_fact = facts.first()?;
19596    let first = first_fact.alias_target_text.as_ref()?;
19597    let normalized = normalize_cpp_whitespace(first);
19598    // Alias signatures collapse whitespace, so repeated declarations of one
19599    // unit can retain different raw spellings. Require structural agreement
19600    // too: whitespace inside a literal can change a type expression's value.
19601    facts
19602        .iter()
19603        .all(|fact| {
19604            fact.alias_target_text.as_ref().is_some_and(|target| {
19605                target == first
19606                    || (first_fact.alias_target.is_some()
19607                        && fact.alias_target == first_fact.alias_target
19608                        && normalize_cpp_whitespace(target) == normalized)
19609            })
19610        })
19611        .then(|| first.clone())
19612}
19613
19614#[cfg(test)]
19615mod tests {
19616
19617    #[test]
19618    fn resolver_displaced_conditional_grammar_boundary() {
19619        // With FLAG defined this is an ordinary array declaration. The
19620        // unpreprocessed CST swallows the directive into its array bound and
19621        // incorrectly keeps the following declaration under the conditional.
19622        let source = "#ifdef FLAG\nint values[\n#endif\n1];\nint outside;\n";
19623        let tree = parse_cpp(source);
19624        let conditional = tree.root_node().named_child(0).unwrap();
19625        assert_eq!(conditional.kind(), "preproc_ifdef");
19626        assert!(conditional.has_error());
19627        let boundary = cpp_displaced_preprocessor_boundary(conditional).unwrap();
19628        let directive = crate::grammar_test_support::nodes(conditional)
19629            .into_iter()
19630            .find(|node| node.kind() == "#endif" && !node.is_missing())
19631            .unwrap();
19632        assert_eq!(directive.parent().unwrap().kind(), "ERROR");
19633        assert_eq!(directive.end_byte(), boundary.end_byte);
19634        let inside = named_node_at(&tree, source, "values");
19635        let outside = named_node_at(&tree, source, "outside");
19636        assert!(has_ancestor_kind(inside, "preproc_ifdef"));
19637        assert!(has_ancestor_kind(outside, "preproc_ifdef"));
19638        assert!(inside.end_byte() < directive.start_byte());
19639        assert!(outside.start_byte() > directive.end_byte());
19640        assert!(preprocessor_conditional_contains_descendant(
19641            conditional,
19642            inside
19643        ));
19644        assert!(!preprocessor_conditional_contains_descendant(
19645            conditional,
19646            outside
19647        ));
19648    }
19649
19650    #[test]
19651    fn resolver_recovered_namespace_boundaries() {
19652        let source = STOLEN_BRACE_CASCADE;
19653        let tree = parse_cpp(source);
19654        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
19655        assert!(!index.regions.is_empty(), "fixture must enter recovery");
19656        for (needle, expected) in [
19657            ("First", vec!["app", "matchers", "detail"]),
19658            ("Second", vec!["app", "matchers", "detail"]),
19659            ("tail_function", vec!["app", "matchers"]),
19660            ("TailClass", vec!["app", "matchers"]),
19661            ("AfterAll", vec![]),
19662        ] {
19663            let node = named_node_at(&tree, source, needle);
19664            let mut parsed: Vec<_> = std::iter::successors(node.parent(), |node| node.parent())
19665                .filter(|node| node.kind() == "namespace_definition")
19666                .filter_map(|namespace| {
19667                    let name = namespace.child_by_field_name("name")?;
19668                    let mut components = Vec::new();
19669                    append_cpp_name_components(name, source, &mut components)?;
19670                    Some((
19671                        namespace.start_byte(),
19672                        components,
19673                        namespace_definition_has_real_braces(namespace),
19674                    ))
19675                })
19676                .collect();
19677            parsed.reverse();
19678            assert_eq!(
19679                index.restore_enclosing_namespaces(parsed, node.start_byte()),
19680                expected,
19681                "{needle}"
19682            );
19683        }
19684        // A range owns its bytes, not the first byte following the range.
19685        for region in &index.regions {
19686            assert!(region.start < region.end);
19687            assert!(index.region_at(region.start).is_some());
19688            assert!(
19689                index
19690                    .region_at(region.end)
19691                    .is_none_or(|found| !std::ptr::eq(found, region)),
19692                "region leaked beyond its half-open boundary: {region:?}"
19693            );
19694        }
19695        // Real recovery ERRORs can carry a close and additional tokens. The
19696        // close still terminates lexical scope; it need not be the only token.
19697        let errors: Vec<_> = crate::grammar_test_support::nodes(tree.root_node())
19698            .into_iter()
19699            .filter(|node| node.kind() == "ERROR")
19700            .filter(|node| {
19701                let mut cursor = node.walk();
19702                node.children(&mut cursor).any(|child| child.kind() == "}")
19703            })
19704            .collect();
19705        assert!(
19706            !errors.is_empty(),
19707            "fixture must retain unmatched close tokens"
19708        );
19709        for error in errors {
19710            assert!(direct_unmatched_closing_brace(error), "{error:?}");
19711        }
19712    }
19713
19714    // These tables pin the fields and recovery tokens supplied by the released
19715    // grammar, alongside the role that the resolver reads at that boundary.
19716    #[test]
19717    fn resolver_declarator_grammar_boundary_table() {
19718        for (source, name, kind, binder) in [
19719            ("int first, values[3];", "values", "identifier", true),
19720            (
19721                "void f() { int first, values[3]; }",
19722                "values",
19723                "identifier",
19724                true,
19725            ),
19726            (
19727                "typedef unsigned Underlying; enum Named : Underlying { Value };",
19728                "Underlying {",
19729                "type_identifier",
19730                false,
19731            ),
19732        ] {
19733            let tree = parse_cpp(source);
19734            assert!(!tree.root_node().has_error(), "{source}");
19735            let spelling = name.split_whitespace().next().unwrap();
19736            let start = source.find(name).unwrap();
19737            let node = tree
19738                .root_node()
19739                .named_descendant_for_byte_range(start, start + spelling.len())
19740                .unwrap();
19741            assert_eq!(node.kind(), kind, "{source}");
19742            if spelling == "values" {
19743                let array = node.parent().unwrap();
19744                assert_eq!(array.kind(), "array_declarator");
19745                assert_eq!(array.child_by_field_name("declarator"), Some(node));
19746                assert!(is_declarator_node(array), "{source}");
19747            } else {
19748                let enumeration = node.parent().unwrap();
19749                assert_eq!(enumeration.kind(), "enum_specifier");
19750                assert_eq!(enumeration.child_by_field_name("base"), Some(node));
19751                assert_eq!(
19752                    enumeration.child_by_field_name("name").is_some(),
19753                    source.contains("enum Named")
19754                );
19755            }
19756            assert_eq!(is_declaration_name(node), binder, "{source}");
19757        }
19758    }
19759
19760    #[test]
19761    fn resolver_alias_scope_and_argument_grammar_table() {
19762        for (source, scope) in [
19763            ("template<class T> using Alias = T;", true),
19764            ("namespace n { template<class T> using Alias = T; }", true),
19765            (
19766                "struct Owner { template<class T> using Alias = T; };",
19767                false,
19768            ),
19769            ("void f() { using Alias = int; }", false),
19770        ] {
19771            let tree = parse_cpp(source);
19772            assert!(!tree.root_node().has_error(), "{source}");
19773            let alias = crate::grammar_test_support::nodes(tree.root_node())
19774                .into_iter()
19775                .find(|node| node.kind() == "alias_declaration")
19776                .unwrap();
19777            assert_eq!(
19778                node_text(alias.child_by_field_name("name").unwrap(), source),
19779                "Alias"
19780            );
19781            assert!(alias.child_by_field_name("type").is_some());
19782            assert_eq!(alias_has_visible_file_scope(alias), scope, "{source}");
19783        }
19784        for (source, argument_kinds, expression) in [
19785            (
19786                "ns::Alias value(42, arg);",
19787                vec!["number_literal", "identifier"],
19788                true,
19789            ),
19790            (
19791                "ns::Alias value(42, 7);",
19792                vec!["number_literal", "number_literal"],
19793                false,
19794            ),
19795            ("ns::Alias value();", vec![], false),
19796        ] {
19797            let tree = parse_cpp(source);
19798            assert!(!tree.root_node().has_error(), "{source}");
19799            let declaration = tree.root_node().named_child(0).unwrap();
19800            let ty = declaration.child_by_field_name("type").unwrap();
19801            assert_eq!(node_text(ty, source), "ns::Alias");
19802            let argument_lists: Vec<_> = crate::grammar_test_support::nodes(declaration)
19803                .into_iter()
19804                .filter(|node| node.kind() == "argument_list")
19805                .collect();
19806            let kinds: Vec<_> = argument_lists
19807                .iter()
19808                .flat_map(|node| {
19809                    let mut cursor = node.walk();
19810                    node.named_children(&mut cursor)
19811                        .map(|child| child.kind())
19812                        .collect::<Vec<_>>()
19813                })
19814                .collect();
19815            assert_eq!(kinds, argument_kinds, "{source}");
19816            assert_eq!(
19817                qualified_alias_constructor_has_expression_argument(ty),
19818                expression,
19819                "{source}"
19820            );
19821        }
19822    }
19823
19824    #[test]
19825    fn resolver_namespace_brace_grammar_table() {
19826        for (source, complete, closing_missing) in [
19827            ("namespace n { int item; }", true, false),
19828            ("namespace n { int item;", false, true),
19829        ] {
19830            let tree = parse_cpp(source);
19831            let namespace = tree.root_node().named_child(0).unwrap();
19832            assert_eq!(namespace.kind(), "namespace_definition");
19833            let body = namespace.child_by_field_name("body").unwrap();
19834            assert_eq!(body.kind(), "declaration_list");
19835            let open = body.child(0).unwrap();
19836            let close = body.child(body.child_count() - 1).unwrap();
19837            assert_eq!(open.kind(), "{");
19838            assert!(!open.is_missing());
19839            assert_eq!(close.kind(), "}");
19840            assert_eq!(close.is_missing(), closing_missing);
19841            assert_eq!(
19842                namespace_definition_has_real_braces(namespace),
19843                complete,
19844                "{source}"
19845            );
19846        }
19847        let source = "namespace { namespace detail { struct Derived {}; void run() {} } }";
19848        let tree = parse_cpp(source);
19849        assert!(!tree.root_node().has_error());
19850        let anonymous = tree.root_node().named_child(0).unwrap();
19851        assert_eq!(anonymous.kind(), "namespace_definition");
19852        assert!(anonymous.child_by_field_name("name").is_none());
19853        let run = named_node_at(&tree, source, "run");
19854        assert_eq!(
19855            enclosing_namespace_components(run, source),
19856            Some(vec!["detail".to_owned()])
19857        );
19858    }
19859
19860    #[test]
19861    fn resolver_c_block_envelope_grammar_table() {
19862        for (source, name, damaged, recovered) in [
19863            ("void outer(void) { nested() {} }", "nested", false, false),
19864            (
19865                "void outer(void) { Apply(first, ^(Bucket b) { return b.value; }); }",
19866                "Apply",
19867                true,
19868                true,
19869            ),
19870        ] {
19871            let tree = parse_cpp(source);
19872            let node = named_node_at(&tree, source, name);
19873            let declarator = node.parent().unwrap();
19874            assert_eq!(
19875                declarator.kind(),
19876                "function_declarator",
19877                "{source}: {:?}",
19878                tree.root_node()
19879            );
19880            assert_eq!(declarator.child_by_field_name("declarator"), Some(node));
19881            assert_eq!(declarator.has_error(), damaged, "{source}");
19882            let definition = declarator.parent().unwrap();
19883            assert_eq!(definition.kind(), "function_definition");
19884            assert!(definition.child_by_field_name("type").is_none());
19885            assert!(definition.child_by_field_name("body").is_some());
19886            assert_eq!(
19887                recovered_c_block_call_envelope(declarator).is_some(),
19888                recovered,
19889                "{source}"
19890            );
19891        }
19892    }
19893
19894    #[test]
19895    fn resolver_linkage_guard_grammar_table() {
19896        for (source, recovered, visible) in [
19897            (
19898                "#ifdef __cplusplus\nextern \"C\" {\n#endif\nvoid target();\n#ifdef __cplusplus\n}\n#endif\n",
19899                false,
19900                true,
19901            ),
19902            (
19903                "#ifdef __cplusplus\nextern \"C\" {\nvoid target();\n#ifdef __cplusplus\n}\n#endif\n",
19904                true,
19905                false,
19906            ),
19907            (
19908                "#ifdef __cplusplus\nextern \"C\" {\nvoid target();\n}\n#endif\n",
19909                true,
19910                false,
19911            ),
19912        ] {
19913            let tree = parse_cpp(source);
19914            let name = named_node_at(&tree, source, "target");
19915            let mut declaration = name;
19916            while declaration.kind() != "declaration" {
19917                declaration = declaration.parent().unwrap();
19918            }
19919            assert_eq!(
19920                has_ancestor_kind(declaration, "linkage_specification"),
19921                recovered,
19922                "{source}"
19923            );
19924            assert_eq!(
19925                has_ancestor_kind(declaration, "preproc_ifdef"),
19926                recovered,
19927                "{source}"
19928            );
19929            if !recovered {
19930                assert!(!tree.root_node().has_error());
19931                let open = tree.root_node().named_child(0).unwrap();
19932                assert_eq!(open.kind(), "preproc_linkage_open");
19933                assert!(open.child_by_field_name("value").is_some());
19934            }
19935            assert_eq!(
19936                callable_preprocessor_context_is_visible(declaration, source),
19937                visible,
19938                "{source}"
19939            );
19940        }
19941    }
19942
19943    #[test]
19944    #[hardwon("#6902: grammar 0.24.4 parsed an unnamed enum's base as its name")]
19945    fn resolver_anonymous_enum_base_grammar_boundary() {
19946        let source = "typedef unsigned Underlying; enum : Underlying { Value };";
19947        let tree = parse_cpp(source);
19948        assert!(
19949            !tree.root_node().has_error(),
19950            "{}",
19951            tree.root_node().to_sexp()
19952        );
19953        let start = source.find("Underlying {").unwrap();
19954        let base = tree
19955            .root_node()
19956            .named_descendant_for_byte_range(start, start + "Underlying".len())
19957            .unwrap();
19958        let enumeration = base.parent().unwrap();
19959        assert_eq!(enumeration.kind(), "enum_specifier");
19960        assert!(enumeration.child_by_field_name("name").is_none());
19961        assert_eq!(enumeration.child_by_field_name("base"), Some(base));
19962        assert!(!is_declaration_name(base));
19963    }
19964
19965    #[test]
19966    #[hardwon(
19967        "db00de0653f1: quicksilver round 4 on brokk-bifrost-cpp: spliced macro tokens need physical source coordinates"
19968    )]
19969    fn macro_splices_preserve_statement_origin_ranges() {
19970        for newline in ["\n", "\r\n"] {
19971            for (replacement, name, written) in [
19972                (
19973                    format!("jo\\{newline}ined\\{newline}();\\{newline}after();"),
19974                    "joined",
19975                    format!("jo\\{newline}ined"),
19976                ),
19977                (
19978                    format!("do {{ if (ready) bl\\{newline}ock }} while (0)"),
19979                    "block",
19980                    format!("bl\\{newline}ock"),
19981                ),
19982            ] {
19983                let parameters = vec!["block".to_owned()];
19984                let (source, tree, original_offsets) =
19985                    VisibilityIndex::parse_macro_replacement_body(&replacement, &parameters)
19986                        .expect("joined replacement parses");
19987                let body = ParsedReplacementBody {
19988                    source,
19989                    tree,
19990                    body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
19991                    parameters,
19992                    original_offsets,
19993                };
19994                let nodes = crate::grammar_test_support::nodes(body.tree.root_node());
19995                let matching: Vec<_> = nodes
19996                    .iter()
19997                    .filter(|node| {
19998                        node.kind() == "identifier" && node_text(**node, &body.source) == name
19999                    })
20000                    .collect();
20001                assert_eq!(matching.len(), 1, "{replacement}");
20002                let range = body.file_range(*matching[0], 0);
20003                assert_eq!(&replacement[range], written);
20004                for node in nodes.iter().filter(|node| {
20005                    node.kind() == "identifier" && node_text(**node, &body.source) == "after"
20006                }) {
20007                    assert_eq!(&replacement[body.file_range(*node, 0)], "after");
20008                }
20009            }
20010        }
20011    }
20012
20013    #[test]
20014    #[hardwon("05fbd05c6bd7: C++ declaration recovery regressed for this structured syntax edge")]
20015    fn issue_3089_statement_formal_at_end_of_replacement() {
20016        let parameters = vec!["handle".to_owned(), "block".to_owned()];
20017        for replacement in [
20018            "do { header_event_t* event; if ((handle)->active) block } while (0)",
20019            "do { header_event_t* event; block } while (0)",
20020        ] {
20021            assert!(
20022                super::VisibilityIndex::parse_macro_replacement_body(replacement, &parameters)
20023                    .is_some(),
20024                "{replacement}"
20025            );
20026        }
20027    }
20028    use super::*;
20029    use brokk_bifrost_test_macros::hardwon;
20030
20031    #[test]
20032    #[hardwon("2e5169325a14: C++ declaration recovery regressed for this structured syntax edge")]
20033    fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
20034        let source = "int size(void) { return sizeof(((Payload))); }\n";
20035        let mut parser = Parser::new();
20036        parser
20037            .set_language(&tree_sitter_cpp::LANGUAGE.into())
20038            .expect("C++ grammar");
20039        let tree = parser.parse(source, None).expect("fixture tree");
20040        let start = source.find("Payload").expect("sizeof operand");
20041        let node = tree
20042            .root_node()
20043            .named_descendant_for_byte_range(start, start + "Payload".len())
20044            .expect("focused operand");
20045        let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
20046        let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
20047
20048        assert_eq!(node.kind(), "identifier");
20049        assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
20050        assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
20051    }
20052
20053    #[test]
20054    fn recovered_macro_actuals_keep_their_cst_boundaries() {
20055        for (source, recovered) in [
20056            ("int f() { return M(Type, value); }", false),
20057            ("int f() { return M(Type, ^(int n){ return n; }); }", true),
20058        ] {
20059            let tree = parse_cpp(source);
20060            let first = named_node_at(&tree, source, "Type");
20061            let (call, index, actual) = c_function_macro_argument(first)
20062                .unwrap_or_else(|| panic!("first actual {source}: {}", tree.root_node().to_sexp()));
20063            assert_eq!(index, 0);
20064            assert_eq!(node_text(actual, source), "Type");
20065            let arguments = call.child_by_field_name("arguments").unwrap();
20066            let raw = arguments.named_child(0).unwrap();
20067            if recovered {
20068                assert_eq!(raw.kind(), "binary_expression");
20069                assert_eq!(raw.child_by_field_name("left"), Some(actual));
20070                let inside = named_node_at(&tree, source, "return n;");
20071                let (_, index, block) = c_function_macro_argument(inside).expect("block actual");
20072                assert_eq!(index, 1);
20073                assert_eq!(block.kind(), "compound_literal_expression");
20074                assert_eq!(node_text(block, source), "(int n){ return n; }");
20075                assert_eq!(raw.child_by_field_name("right"), Some(block));
20076            } else {
20077                assert_eq!(raw, actual);
20078                let second = named_node_at(&tree, source, "value");
20079                let (_, index, actual) = c_function_macro_argument(second).expect("second actual");
20080                assert_eq!(index, 1);
20081                assert_eq!(node_text(actual, source), "value");
20082            }
20083        }
20084    }
20085
20086    fn parse_cpp(source: &str) -> Tree {
20087        crate::diagnostics::parse_cpp_tree(source)
20088    }
20089
20090    #[test]
20091    #[hardwon("#4271: source splitting included a new-array declarator in the allocation type")]
20092    fn cpp_new_expression_type_comes_from_the_structured_type_field() {
20093        let source = "auto value = new  ns::Box<int> [ 3 ];";
20094        let tree = parse_cpp(source);
20095        let mut pending = vec![tree.root_node()];
20096        let allocation = loop {
20097            let node = pending.pop().expect("new expression in fixture");
20098            if node.kind() == "new_expression" {
20099                break node;
20100            }
20101            let mut cursor = node.walk();
20102            pending.extend(
20103                node.named_children(&mut cursor)
20104                    .collect::<Vec<_>>()
20105                    .into_iter()
20106                    .rev(),
20107            );
20108        };
20109
20110        let type_node = cpp_new_expression_type_node(allocation).expect("structured type field");
20111        assert_eq!(allocation.child_by_field_name("type"), Some(type_node));
20112        assert_eq!(node_text(type_node, source), "ns::Box<int>");
20113        assert_eq!(
20114            allocation
20115                .child_by_field_name("declarator")
20116                .expect("array declarator")
20117                .kind(),
20118            "new_declarator"
20119        );
20120
20121        let old_split = normalize_cpp_whitespace(node_text(allocation, source));
20122        let old_split = old_split
20123            .strip_prefix("new ")
20124            .unwrap_or(old_split.as_str())
20125            .split(['(', '{'])
20126            .next()
20127            .expect("old split result");
20128        assert_ne!(old_split.trim(), node_text(type_node, source));
20129    }
20130
20131    fn named_node_at<'tree>(tree: &'tree Tree, source: &str, needle: &str) -> Node<'tree> {
20132        let start = source.find(needle).expect("fixture needle");
20133        tree.root_node()
20134            .named_descendant_for_byte_range(start, start + needle.len())
20135            .expect("node at needle")
20136    }
20137
20138    fn prepared_cpp(source: &str) -> PreparedSyntaxTree {
20139        let tree = crate::diagnostics::parse_cpp_tree(source);
20140        PreparedSyntaxTree::new(
20141            PreparedSyntaxSource::Exact(Arc::from(source)),
20142            tree,
20143            compute_line_starts(source),
20144            LanguageDialect::Standard(Language::Cpp),
20145            PreparedSourceOrigin::Disk,
20146            None,
20147        )
20148    }
20149
20150    fn unresolved_include_before(source: &str, reference: &str) -> bool {
20151        let file = ProjectFile::new(std::env::temp_dir(), "issue-3078.cpp");
20152        let prepared = prepared_cpp(source);
20153        let facts = collect_structured_include_facts(&prepared);
20154        let include_targets = IncludeTargetIndex::build([&file]);
20155        has_unresolved_include_visible_before_in_prepared(
20156            &file,
20157            &prepared,
20158            &include_targets,
20159            &facts,
20160            source.find(reference).expect("reference fixture"),
20161            &|guards| guards.cloned(),
20162        )
20163    }
20164
20165    #[test]
20166    #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
20167    fn unresolved_include_before_reference_is_visible() {
20168        let source = "#include \"missing.h\"\nint use = Missing;\n";
20169        assert!(unresolved_include_before(source, "Missing"));
20170    }
20171
20172    #[test]
20173    #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
20174    fn unresolved_include_after_reference_is_not_visible() {
20175        let source = "int use = Missing;\n#include \"missing.h\"\n";
20176        assert!(!unresolved_include_before(source, "Missing"));
20177    }
20178
20179    #[test]
20180    #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
20181    fn unresolved_include_in_incompatible_sibling_branch_is_not_visible() {
20182        let source = "#if FEATURE\n#include \"missing.h\"\n#else\nint use = Missing;\n#endif\n";
20183        assert!(!unresolved_include_before(source, "Missing"));
20184    }
20185
20186    #[test]
20187    #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
20188    fn unresolved_include_in_current_branch_is_visible() {
20189        let source =
20190            "#if FEATURE\n#include \"missing.h\"\nint use = Missing;\n#else\nint other;\n#endif\n";
20191        assert!(unresolved_include_before(source, "Missing"));
20192    }
20193
20194    /// Two macro-decorated class heads make tree-sitter close `detail` at the
20195    /// first class's `}`, `matchers` at the second's, and `app` at `detail`'s
20196    /// real `}`; the tail parses at translation-unit level and the two real
20197    /// closes for `matchers` and `app` land in a trailing ERROR (#1537).
20198    const STOLEN_BRACE_CASCADE: &str = r#"namespace app {
20199namespace matchers {
20200    namespace detail {
20201        class API [[nodiscard]] First {
20202        public:
20203            int value() const { return count_ + 1; }
20204        private:
20205            int count_;
20206        };
20207        class API [[nodiscard]] Second {
20208        public:
20209            int value() const { return count_ + 2; }
20210        private:
20211            int count_;
20212        };
20213    } // namespace detail
20214
20215    template <typename T>
20216    void tail_function(MatcherBase<T> const& value);
20217
20218    class TailClass {};
20219} // namespace matchers
20220} // namespace app
20221
20222struct AfterAll {};
20223"#;
20224
20225    #[test]
20226    #[hardwon("c820d8c6a8e3: C++ declaration recovery regressed for this structured syntax edge")]
20227    fn orphaned_namespace_scope_index_restores_a_stolen_brace_cascade() {
20228        let source = STOLEN_BRACE_CASCADE;
20229        let tree = parse_cpp(source);
20230        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
20231
20232        let tail_class = named_node_at(&tree, source, "TailClass");
20233        assert!(
20234            !has_ancestor_kind(tail_class, "namespace_definition"),
20235            "the fixture must reproduce the recovery: the tail has no namespace ancestor"
20236        );
20237        let displaced = named_node_at(&tree, source, "Second");
20238        assert_eq!(
20239            enclosing_namespace_components(displaced, source),
20240            Some(vec!["app".to_string(), "matchers".to_string()]),
20241            "the fixture must displace the second class out of detail"
20242        );
20243
20244        let components = |needle: &str| {
20245            index.enclosing_namespace_components(named_node_at(&tree, source, needle), source)
20246        };
20247        assert_eq!(components("First"), ["app", "matchers", "detail"]);
20248        assert_eq!(components("Second"), ["app", "matchers", "detail"]);
20249        assert_eq!(components("MatcherBase<T>"), ["app", "matchers"]);
20250        assert_eq!(components("tail_function"), ["app", "matchers"]);
20251        assert_eq!(components("TailClass"), ["app", "matchers"]);
20252        assert!(components("AfterAll").is_empty());
20253    }
20254
20255    #[test]
20256    #[hardwon("c820d8c6a8e3: C++ declaration recovery regressed for this structured syntax edge")]
20257    fn orphaned_namespace_scope_index_is_empty_without_lost_scopes() {
20258        let clean = "namespace a { namespace b { class C {}; } class D {}; }\n";
20259        let tree = parse_cpp(clean);
20260        assert!(!tree.root_node().has_error());
20261        assert!(OrphanedNamespaceScopeIndex::build(tree.root_node(), clean).is_empty());
20262
20263        // A namespace that merely contains a parse error closes where its
20264        // brace says; the declarations after it keep their parsed scope.
20265        let damaged = "namespace a { namespace b { UNKNOWN_MACRO(x) } class C {}; }\n";
20266        let tree = parse_cpp(damaged);
20267        assert!(tree.root_node().has_error());
20268        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), damaged);
20269        assert_eq!(
20270            index.enclosing_namespace_components(named_node_at(&tree, damaged, "class C"), damaged),
20271            ["a"]
20272        );
20273    }
20274
20275    /// A function-like `#define` in a class body, whose replacement returns a
20276    /// SFINAE type, makes tree-sitter give up on the whole enclosing namespace:
20277    /// `namespace app {` becomes an `ERROR` whose own `namespace`, name and `{`
20278    /// tokens are the only trace of the head, and every declaration the
20279    /// namespace holds becomes their flat sibling. This is Catch2's
20280    /// `catch_decomposer.hpp`, reduced (issue #3084).
20281    const COLLAPSED_NAMESPACE_HEAD: &str = r#"namespace app {
20282
20283    class Target {
20284        int value_;
20285    };
20286
20287    template <typename T>
20288    class Holder {
20289    public:
20290        explicit constexpr Holder( T lhs ): m_lhs( lhs ) {}
20291
20292#define HOLDER_DEFINE_OP( id, op )                                             \
20293    template <typename U>                                                      \
20294    constexpr friend auto operator op( Holder&& lhs, U&& rhs )                 \
20295        -> std::enable_if_t<is_##id##_comparable<T, U>::value, Target> {       \
20296        return Target{};                                                       \
20297    }
20298
20299        HOLDER_DEFINE_OP( equal, == )
20300#undef HOLDER_DEFINE_OP
20301        T m_lhs;
20302    };
20303
20304    class Tail {};
20305}
20306"#;
20307
20308    #[test]
20309    #[hardwon("a438f125e172: C++ declaration recovery regressed for this structured syntax edge")]
20310    fn orphaned_namespace_scope_index_names_a_collapsed_namespace_head() {
20311        let source = COLLAPSED_NAMESPACE_HEAD;
20312        let tree = parse_cpp(source);
20313        let target = named_node_at(&tree, source, "class Target");
20314
20315        // The macro-declaration grammar keeps the namespace head and its
20316        // members inside a surviving `namespace_definition` even with the
20317        // macro invocation shredding one declaration inside the body, so the
20318        // scope authority answers from parsed ancestry; the recovered-region
20319        // path this fixture exercised is no longer needed for it.
20320        assert!(
20321            tree.root_node().has_error(),
20322            "the fixture must retain the macro invocation's parse damage"
20323        );
20324        assert!(
20325            has_ancestor_kind(target, "namespace_definition"),
20326            "the namespace head survives: the class keeps its parsed namespace ancestor"
20327        );
20328
20329        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
20330        assert_eq!(
20331            index.enclosing_namespace_components(target, source),
20332            ["app"]
20333        );
20334        assert_eq!(
20335            index.enclosing_namespace_levels(target, source),
20336            vec![(
20337                "app".to_string(),
20338                Range {
20339                    start_byte: 0,
20340                    end_byte: source.len() - 1,
20341                    start_line: 1,
20342                    end_line: 25,
20343                },
20344                Range {
20345                    start_byte: 10,
20346                    end_byte: 13,
20347                    start_line: 1,
20348                    end_line: 1,
20349                },
20350            )],
20351            "the recovered namespace owns its full construct, not its first member or just its header"
20352        );
20353    }
20354
20355    #[test]
20356    #[hardwon("#5868: a literal namespace head inside a damaged function wrapper lost its owner")]
20357    fn namespace_body_name_components_recovers_literal_function_head() {
20358        // Independently authored direct-helper contract, not an index or
20359        // short-body reproduction. The index deliberately leaves clean
20360        // subtrees intact; head recognition alone does not make this simple
20361        // body an indexed recovery region. Corpus index/short-body/outside
20362        // ownership remains required by the private root golden prewriter.
20363        let source = "OPAQUE_PREFIX\nnamespace recovered\n{\nint member;\n}\nint member;\n";
20364        let tree = parse_cpp(source);
20365        let keyword_start = source.find("namespace recovered").unwrap();
20366        let keyword = tree
20367            .root_node()
20368            .named_descendant_for_byte_range(keyword_start, keyword_start + "namespace".len())
20369            .expect("literal keyword node");
20370        // The macro-declaration grammar reads the opaque prefix as a
20371        // `macro_modifier` and the literal `namespace` keyword as the bogus
20372        // function's type: a keyword cannot be a type, so the type field is
20373        // the same recovery evidence the older grammar's `ERROR` carried.
20374        assert_eq!(keyword.kind(), "type_identifier");
20375        assert_eq!(keyword.child_count(), 0);
20376        assert!(!keyword.is_missing() && !keyword.has_error());
20377        assert_eq!(node_text(keyword, source), "namespace");
20378        let wrapper = keyword.parent().expect("actual wrapper");
20379        assert_eq!(wrapper.kind(), "function_definition");
20380        let mut cursor = wrapper.walk();
20381        let children = wrapper
20382            .children(&mut cursor)
20383            .filter(|child| child.kind() != "comment")
20384            .collect::<Vec<_>>();
20385        assert_eq!(
20386            children.len(),
20387            4,
20388            "all named and unnamed children accounted: {children:?}"
20389        );
20390        let prefix = children[0];
20391        assert_eq!(prefix.kind(), "macro_modifier");
20392        assert!(!prefix.is_missing() && !prefix.has_error());
20393        assert_eq!(node_text(prefix, source), "OPAQUE_PREFIX");
20394        assert_eq!(children[1], keyword);
20395        assert_eq!(wrapper.child_by_field_name("type"), Some(keyword));
20396        let name = children[2];
20397        assert!(matches!(name.kind(), "identifier" | "namespace_identifier"));
20398        assert_eq!(name.child_count(), 0);
20399        assert!(!name.is_missing() && !name.has_error());
20400        assert_eq!(node_text(name, source), "recovered");
20401        assert_eq!(wrapper.child_by_field_name("declarator"), Some(name));
20402        let body = wrapper.child_by_field_name("body").expect("actual body");
20403        assert_eq!(children[3], body);
20404        assert_eq!(body.kind(), "compound_statement");
20405        assert!(!body.is_missing());
20406        let open = body.child(0).expect("actual first child");
20407        assert_eq!(open.kind(), "{");
20408        assert!(!open.is_missing() && !open.has_error());
20409        assert_eq!(open.start_byte(), body.start_byte());
20410        let (head, names) = namespace_body_name_components(wrapper, body, source)
20411            .expect("strict synthetic literal head recognized");
20412        assert_eq!(head, keyword);
20413        assert_eq!(
20414            names
20415                .iter()
20416                .map(|(name, _)| name.as_str())
20417                .collect::<Vec<_>>(),
20418            ["recovered"]
20419        );
20420        assert_eq!(
20421            (names[0].1.start_byte, names[0].1.end_byte),
20422            (name.start_byte(), name.end_byte())
20423        );
20424        let inside = named_node_at(&tree, source, "int member;");
20425        assert!(body.start_byte() < inside.start_byte() && inside.end_byte() < body.end_byte());
20426    }
20427
20428    #[test]
20429    #[hardwon("#5868: refusal controls must actually exercise the namespace body helper")]
20430    fn orphaned_namespace_scope_index_refuses_unproven_function_heads() {
20431        for refusal in [
20432            "int ordinary(int value) { return value; }",
20433            "OPAQUE_PREFIX namespace_ recovered { int member; }",
20434            "OPAQUE_PREFIX namespace recovered other { int member; }",
20435            "OPAQUE_PREFIX namespace recovered(int value) { return value; }",
20436        ] {
20437            let tree = parse_cpp(refusal);
20438            let mut stack = vec![tree.root_node()];
20439            let mut helper_calls = 0;
20440            while let Some(node) = stack.pop() {
20441                if node.kind() == "function_definition"
20442                    && let Some(body) = node.child_by_field_name("body")
20443                {
20444                    helper_calls += 1;
20445                    assert_eq!(
20446                        (node.start_byte(), node.end_byte()),
20447                        (0, refusal.len()),
20448                        "guard control must exercise its complete envelope: source={refusal}, node={node:?}"
20449                    );
20450                    assert!(
20451                        namespace_body_name_components(node, body, refusal).is_none(),
20452                        "refused shape acquired namespace: source={refusal}, node={node:?}, body={body:?}"
20453                    );
20454                }
20455                let mut cursor = node.walk();
20456                stack.extend(node.named_children(&mut cursor));
20457            }
20458            assert_eq!(
20459                helper_calls,
20460                1,
20461                "guard control must reach exactly its actual function/body helper call: source={refusal}, tree={:?}",
20462                tree.root_node()
20463            );
20464        }
20465
20466        // The measured '=' case has no function/body envelope. A missing
20467        // body is likewise parser-absence coverage, not a passed helper guard.
20468        // The macro-declaration grammar reads both as clean declarations whose
20469        // specifier words are `macro_modifier`s, so the parser-absence premise
20470        // is now their lack of a function/body envelope rather than an ERROR.
20471        for absent in [
20472            "OPAQUE_PREFIX namespace recovered = other { int member; }",
20473            "OPAQUE_PREFIX namespace recovered int member;",
20474        ] {
20475            let tree = parse_cpp(absent);
20476            let mut stack = vec![tree.root_node()];
20477            let mut helper_candidates = 0;
20478            while let Some(node) = stack.pop() {
20479                if node.kind() == "function_definition"
20480                    && node.child_by_field_name("body").is_some()
20481                {
20482                    helper_candidates += 1;
20483                }
20484                let mut cursor = node.walk();
20485                stack.extend(node.named_children(&mut cursor));
20486            }
20487            assert_eq!(
20488                helper_candidates,
20489                0,
20490                "parser-absence control unexpectedly acquired a helper envelope: source={absent}, tree={:?}",
20491                tree.root_node()
20492            );
20493        }
20494    }
20495
20496    /// A macro that opens a block: `APP_IF_ACTIVE` expands to `if (active) {`,
20497    /// so the `{` of a scope the source really opens is part of the macro's
20498    /// `preproc_arg` and never becomes a `{` token. The tree therefore holds
20499    /// one more real `}` than real `{`, and a file-global stack has to assign
20500    /// those closes to the wrong opens: the `}` on line 9 takes `check`'s body,
20501    /// `check`'s own `}` on line 11 takes `detail`, line 15 takes `app`, and
20502    /// line 16 arrives with nothing open. This is the state simdjson's
20503    /// `compile_time_json-inl.h` reaches eight times over (#3328).
20504    const MACRO_OPENED_SCOPE: &str = r#"#define APP_IF_ACTIVE if (active) {
20505
20506namespace app {
20507namespace detail {
20508
20509bool check(bool active) {
20510  APP_IF_ACTIVE
20511    return true;
20512  }
20513  return false;
20514}
20515
20516class Tail {};
20517
20518} // namespace detail
20519} // namespace app
20520"#;
20521
20522    /// simdjson's `include/simdjson/compile_time_json-inl.h` at `9b33047a`,
20523    /// reduced. ABI 15 with runtime 0.25.10 now keeps each case-label character
20524    /// literal and the nested namespace definitions. The malformed function
20525    /// body still causes a missing outer namespace close. Keep the original
20526    /// source as a positive for real inner closes, without inventing the outer
20527    /// close that the parser could not read (#3328).
20528    const REFLECTION_CASE_LABEL_RECOVERY: &str = r#"namespace simdjson {
20529namespace compile_time {
20530namespace number_parsing {
20531consteval int leading_zeroes(uint64_t input_num, int last_bit = 0) {
20532    auto hex_to_u32 = [&] [[nodiscard]] () -> uint32_t {
20533      auto digit = [](uint8_t c) -> uint32_t {
20534      }
20535    }
20536    if (a) {
20537      while (a) {
20538      }
20539    }
20540    switch (c) {
20541    case '{': {
20542    }
20543    }
20544  }
20545  while (a) {
20546    cursor += field.second;
20547    if (a) {
20548    case '{': {
20549      if (a) {
20550      }
20551    }
20552    case 'n': {
20553      if (a) {
20554      }
20555    }
20556    }
20557  }
20558  if (a) {
20559    simdjson_consteval_error("Expected '}'");
20560  }
20561}
20562}
20563}
20564"#;
20565
20566    /// Real `{` and `}` tokens of a tree, the stream the index pairs.
20567    fn real_brace_token_counts(root: Node<'_>) -> (usize, usize) {
20568        let mut stack = vec![root];
20569        let mut counts = (0, 0);
20570        while let Some(node) = stack.pop() {
20571            match (node.kind(), node.is_missing()) {
20572                ("{", false) => counts.0 += 1,
20573                ("}", false) => counts.1 += 1,
20574                _ => {}
20575            }
20576            let mut cursor = node.walk();
20577            stack.extend(node.children(&mut cursor));
20578        }
20579        counts
20580    }
20581
20582    /// The open brace of `head`, which is the key its close lands under.
20583    fn namespace_open_byte(source: &str, head: &str) -> usize {
20584        let at = source.find(head).expect("fixture namespace head");
20585        at + head.len() - 1
20586    }
20587
20588    fn assert_namespace_heads_have_no_matching_close(root: Node<'_>, source: &str, heads: &[&str]) {
20589        let index = OrphanedNamespaceScopeIndex::build(root, source);
20590        for head in heads {
20591            assert_eq!(
20592                index.matching_close_brace(namespace_open_byte(source, head)),
20593                None,
20594                "{head} must not claim a close belonging to an inner scope"
20595            );
20596        }
20597    }
20598
20599    #[test]
20600    #[hardwon("1352f739be3d: C++ declaration recovery regressed for this structured syntax edge")]
20601    fn orphaned_namespace_scope_index_pairs_nothing_when_a_scope_open_is_lost() {
20602        let source = MACRO_OPENED_SCOPE;
20603        let tree = parse_cpp(source);
20604        let root = tree.root_node();
20605        assert_eq!(
20606            real_brace_token_counts(root),
20607            (4, 5),
20608            "the fixture must reproduce the loss: one scope the source opens \
20609             has no `{{` token, so the stream holds an extra real close"
20610        );
20611
20612        assert_namespace_heads_have_no_matching_close(
20613            root,
20614            source,
20615            &["namespace app {", "namespace detail {"],
20616        );
20617    }
20618
20619    #[test]
20620    #[hardwon("5312adbcf9bf: C++ declaration recovery regressed for this structured syntax edge")]
20621    fn orphaned_namespace_scope_index_leaves_a_declaration_after_every_close_at_file_scope() {
20622        // The stack proves no pairing in this file, so every restored level
20623        // keeps its best-effort scope. None of it may reach a declaration the
20624        // file writes after its last close.
20625        let source = format!("{MACRO_OPENED_SCOPE}struct AfterAll {{}};\n");
20626        let tree = parse_cpp(&source);
20627        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), &source);
20628        let after_all = named_node_at(&tree, &source, "struct AfterAll");
20629        assert!(
20630            index
20631                .enclosing_namespace_components(after_all, &source)
20632                .is_empty()
20633        );
20634    }
20635
20636    #[test]
20637    #[hardwon("1352f739be3d: C++ declaration recovery regressed for this structured syntax edge")]
20638    fn orphaned_namespace_scope_index_preserves_structured_reflection_closes() {
20639        let source = REFLECTION_CASE_LABEL_RECOVERY;
20640        let tree = parse_cpp(source);
20641        let root = tree.root_node();
20642        let mut stack = vec![root];
20643        let mut mis_lexed_opens = Vec::new();
20644        while let Some(node) = stack.pop() {
20645            if node.kind() == "character"
20646                && &source[node.start_byte()..node.end_byte()] == "{"
20647                && node
20648                    .parent()
20649                    .is_some_and(|parent| parent.kind() != "char_literal")
20650            {
20651                mis_lexed_opens.push(node.start_position().row + 1);
20652            }
20653            let mut cursor = node.walk();
20654            stack.extend(node.children(&mut cursor));
20655        }
20656        assert!(
20657            mis_lexed_opens.is_empty(),
20658            "ABI15 should keep each case-label `{{` inside its `char_literal`; \
20659             the old ABI14 mis-lex is no longer a valid recovery witness"
20660        );
20661        assert_eq!(
20662            real_brace_token_counts(root),
20663            (17, 17),
20664            "the improved parse retains balanced real brace tokens"
20665        );
20666        assert_eq!(root.kind(), "translation_unit");
20667        assert!(root.has_error(), "the malformed body remains incomplete");
20668        let index = OrphanedNamespaceScopeIndex::build(root, source);
20669        assert_eq!(
20670            index.matching_close_brace(namespace_open_byte(source, "namespace simdjson {")),
20671            None,
20672            "a missing outer close must not borrow an inner namespace's real close"
20673        );
20674        for (head, close) in [
20675            ("namespace compile_time {", source.len() - 2),
20676            ("namespace number_parsing {", source.len() - 4),
20677        ] {
20678            assert_eq!(
20679                index
20680                    .matching_close_brace(namespace_open_byte(source, head))
20681                    .map(|range| range.start_byte),
20682                Some(close),
20683                "{head} retains its own real close"
20684            );
20685        }
20686    }
20687
20688    #[test]
20689    #[hardwon("#2243: an empty namespace could hide a nested indexed owner suffix")]
20690    fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
20691        let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
20692        assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
20693        assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
20694        assert!(indexed_namespace_path_is_recoverable(
20695            &["cache".to_string()],
20696            &indexed,
20697            1,
20698        ));
20699    }
20700
20701    #[test]
20702    #[hardwon("#1876: lookup ordering was partial and changed with unrelated workspace content")]
20703    fn sort_lookup_units_totally_orders_every_identity_field() {
20704        let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
20705        let base = CodeUnit::with_signature(
20706            file.clone(),
20707            CodeUnitType::Function,
20708            "scope",
20709            "value",
20710            Some("()".to_string()),
20711            false,
20712        );
20713        let different_kind = CodeUnit::with_signature(
20714            file.clone(),
20715            CodeUnitType::Field,
20716            "scope",
20717            "value",
20718            Some("()".to_string()),
20719            false,
20720        );
20721        let synthetic = base.with_synthetic(true);
20722
20723        let interner = segment_interner();
20724        let mut member_fq = FqName::new();
20725        member_fq.push(interner.intern("scope", SegmentKind::Package));
20726        member_fq.push(interner.intern("value", SegmentKind::Member));
20727        let different_package_boundary = CodeUnit::from_fq(
20728            file.clone(),
20729            CodeUnitType::Function,
20730            member_fq,
20731            0,
20732            Some("()".to_string()),
20733            false,
20734        );
20735
20736        let mut unknown_fq = FqName::new();
20737        unknown_fq.push(interner.intern("scope", SegmentKind::Package));
20738        unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
20739        let different_segment_kind = CodeUnit::from_fq(
20740            file,
20741            CodeUnitType::Function,
20742            unknown_fq,
20743            1,
20744            Some("()".to_string()),
20745            false,
20746        );
20747
20748        let input = vec![
20749            base,
20750            different_kind,
20751            synthetic,
20752            different_package_boundary,
20753            different_segment_kind,
20754        ];
20755        let mut expected = input.clone();
20756        sort_lookup_units(&mut expected);
20757        assert!(expected.windows(2).all(|pair| {
20758            let mut ordered = pair.to_vec();
20759            sort_lookup_units(&mut ordered);
20760            ordered == pair && pair[0] != pair[1]
20761        }));
20762
20763        let mut reversed = input.clone();
20764        reversed.reverse();
20765        sort_lookup_units(&mut reversed);
20766        assert_eq!(reversed, expected);
20767
20768        let mut rotated = input;
20769        rotated.rotate_left(2);
20770        sort_lookup_units(&mut rotated);
20771        assert_eq!(rotated, expected);
20772    }
20773
20774    #[test]
20775    #[hardwon("6ffe85773652: C++ declaration recovery regressed for this structured syntax edge")]
20776    fn structured_conditional_array_bound_preserves_the_real_guard() {
20777        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";
20778        let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
20779        let parse = |source: &str| {
20780            let mut parser = Parser::new();
20781            parser
20782                .set_language(&tree_sitter_cpp::LANGUAGE.into())
20783                .expect("C++ grammar");
20784            parser.parse(source, None).expect("fixture tree")
20785        };
20786
20787        let tree = parse(damaged);
20788        let root = tree.root_node();
20789        assert!(
20790            !root.has_error(),
20791            "the native conditional-array grammar now reads the original recovery witness cleanly: {}",
20792            root.to_sexp()
20793        );
20794        let target = damaged.find("target").expect("target byte");
20795        let declaration = root
20796            .descendant_for_byte_range(target, target + "target".len())
20797            .and_then(|mut node| {
20798                loop {
20799                    if node.kind() == "declaration" {
20800                        break Some(node);
20801                    }
20802                    node = node.parent()?;
20803                }
20804            })
20805            .expect("declaration after the displaced terminator");
20806        let outer = declaration
20807            .parent()
20808            .filter(|node| node.kind() == "preproc_ifdef")
20809            .expect("ordinary outer include guard");
20810        let bound = outer
20811            .named_children(&mut outer.walk())
20812            .filter(|node| node.kind() == "declaration")
20813            .filter_map(|node| node.child_by_field_name("declarator"))
20814            .find(|node| node.kind() == "array_declarator")
20815            .and_then(|node| node.child_by_field_name("size"))
20816            .expect("structured conditional array bound");
20817        assert_eq!(bound.kind(), "preproc_array_size");
20818        let conditional = bound
20819            .named_children(&mut bound.walk())
20820            .find(|node| node.kind() == "preproc_ifdef")
20821            .expect("array-size conditional");
20822        assert!(conditional.end_byte() <= declaration.start_byte());
20823        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
20824        assert!(
20825            !std::iter::successors(Some(declaration), |node| node.parent())
20826                .any(|ancestor| same_node(ancestor, conditional)),
20827            "the declaration must not inherit the array-size feature guard"
20828        );
20829        assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
20830        assert!(preprocessor_conditional_contains_descendant(
20831            outer,
20832            declaration
20833        ));
20834
20835        let tree = parse(guarded);
20836        let conditional = tree
20837            .root_node()
20838            .named_child(0)
20839            .filter(|node| node.kind() == "preproc_ifdef")
20840            .expect("ordinary conditional");
20841        let declaration = conditional
20842            .named_children(&mut conditional.walk())
20843            .find(|node| node.kind() == "declaration")
20844            .expect("guarded declaration");
20845        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
20846        assert!(preprocessor_conditional_contains_descendant(
20847            conditional,
20848            declaration
20849        ));
20850
20851        let damaged_alternative = format!(
20852            "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
20853            "UNUSED(value)\n".repeat(64)
20854        );
20855        let tree = parse(&damaged_alternative);
20856        let conditional = tree
20857            .root_node()
20858            .named_child(0)
20859            .filter(|node| node.kind() == "preproc_ifdef")
20860            .expect("outer conditional with an alternative");
20861        assert!(conditional.has_error());
20862        assert!(conditional.child_by_field_name("alternative").is_some());
20863        assert!(
20864            conditional
20865                .child(conditional.child_count() - 1)
20866                .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
20867        );
20868        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
20869
20870        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";
20871        let tree = parse(split_declaration);
20872        let root = tree.root_node();
20873        // The macro-declaration grammar folds the whole split declaration
20874        // into one damaged `type_definition` instead of keeping a
20875        // `preproc_ifdef` with a displaced terminator: the boundary the
20876        // recovery supplied is now the declaration node itself. What must not
20877        // change is the outcome -- the function written after the split
20878        // declaration is outside the damaged span and inherits no guard.
20879        let split = root
20880            .named_children(&mut root.walk())
20881            .find(|node| node.kind() == "type_definition")
20882            .expect("the split declaration folds into one type definition");
20883        assert_eq!(
20884            split.end_position().row + 1,
20885            9,
20886            "the split declaration ends at its own declarator line"
20887        );
20888        let target = split_declaration
20889            .find("static int target")
20890            .expect("target byte");
20891        assert!(split.end_byte() <= target);
20892        let target_node = root
20893            .descendant_for_byte_range(target, target + "static".len())
20894            .expect("target node");
20895        assert!(
20896            !std::iter::successors(Some(target_node), |node| node.parent())
20897                .any(|ancestor| ancestor.kind() == "preproc_ifdef" && ancestor.has_error()),
20898            "the declaration after the shredded split declaration must not \
20899             inherit a damaged conditional ancestor"
20900        );
20901    }
20902
20903    #[test]
20904    #[hardwon("6ba7704de3ed: C++ declaration recovery regressed for this structured syntax edge")]
20905    fn fragmented_reference_guard_is_recovered() {
20906        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";
20907        let mut parser = Parser::new();
20908        parser
20909            .set_language(&tree_sitter_cpp::LANGUAGE.into())
20910            .expect("C++ grammar");
20911        let tree = parser.parse(source, None).expect("fixture tree");
20912        let start = source.rfind("helper").expect("reference byte");
20913        let node = tree
20914            .root_node()
20915            .descendant_for_byte_range(start, start + "helper".len())
20916            .expect("reference node");
20917        let mut expected = HashSet::default();
20918        expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
20919            vec![
20920                BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
20921                BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
20922            ],
20923        )));
20924        assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
20925    }
20926
20927    #[test]
20928    #[hardwon(
20929        "#6325: grammar 0.24.4 displaced #else/#elif into a declaration ERROR and the branch boundary vanished"
20930    )]
20931    fn displaced_branch_directives_decide_guards_without_proving_unreadable_branches() {
20932        let parse = |source: &str| {
20933            let mut parser = Parser::new();
20934            parser
20935                .set_language(&tree_sitter_cpp::LANGUAGE.into())
20936                .expect("C++ grammar");
20937            parser.parse(source, None).expect("fixture tree")
20938        };
20939        fn at<'tree>(root: Node<'tree>, source: &str, needle: &str, token: &str) -> Node<'tree> {
20940            let start = source.find(needle).expect("needle") + needle.find(token).expect("token");
20941            root.descendant_for_byte_range(start, start + token.len())
20942                .expect("token node")
20943        }
20944        let guards = |node: Node<'_>, source: &str| preprocessor_guard_environment(node, source);
20945        let set = |items: &[PreprocessorGuard]| items.iter().cloned().collect::<HashSet<_>>();
20946
20947        // `#ifdef`: the `#else` is an ERROR child of the declaration that spans
20948        // both sentinels, and a `#define` after it stays a structured child of
20949        // the same conditional.
20950        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";
20951        let tree = parse(source);
20952        let root = tree.root_node();
20953        let x = at(root, source, "int x", "x");
20954        let conditional = std::iter::successors(Some(x), |node| node.parent())
20955            .find(|node| node.kind() == "preproc_ifdef")
20956            .expect("ifdef");
20957        assert!(conditional.child_by_field_name("alternative").is_none());
20958        let directive = cpp_displaced_preprocessor_branch(conditional).expect("displaced #else");
20959        assert_eq!(directive.kind(), "#else");
20960        assert_eq!(directive.parent().map(|node| node.kind()), Some("ERROR"));
20961        let defined = PreprocessorGuard::Defined("A".to_string());
20962        assert_eq!(guards(x, source), Some(set(std::slice::from_ref(&defined))));
20963        let class_name = at(root, source, "class T", "T");
20964        assert_eq!(guards(class_name, source), Some(set(&[defined.negated()])));
20965        let define = at(root, source, "#define MODE", "MODE")
20966            .parent()
20967            .filter(|node| node.kind() == "preproc_def")
20968            .expect("macro event");
20969        let mut cache = PreprocessorContextCache::default();
20970        assert!(matches!(
20971            preprocessor_branch_of_descendant(conditional, define, &mut cache),
20972            PreprocessorBranch::DisplacedElse
20973        ));
20974        assert_eq!(
20975            preprocessor_conditional_family_for_declaration_with_cache(class_name, &mut cache),
20976            Some(conditional),
20977            "a displaced #else completes the family"
20978        );
20979        assert_eq!(
20980            callable_guard_completion_byte(class_name, source),
20981            Some(conditional.end_byte())
20982        );
20983
20984        // A displaced `#elif B` reads `B` as the declaration's type: its
20985        // condition has no structured node, so nothing after it, including the
20986        // later displaced `#else`, may be read as `!A`.
20987        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";
20988        let tree = parse(source);
20989        let root = tree.root_node();
20990        let x = at(root, source, "int x", "x");
20991        let conditional = std::iter::successors(Some(x), |node| node.parent())
20992            .find(|node| node.kind() == "preproc_if")
20993            .expect("if");
20994        assert!(conditional.child_by_field_name("alternative").is_none());
20995        assert_eq!(
20996            cpp_displaced_preprocessor_branch(conditional).map(|node| node.kind()),
20997            Some("#elif")
20998        );
20999        let truthy = PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy("A".to_string()));
21000        assert_eq!(guards(x, source), Some(set(&[truthy])));
21001        let mut cache = PreprocessorContextCache::default();
21002        for needle in ["class T", "struct T"] {
21003            let name = at(root, source, needle, "T");
21004            assert_eq!(
21005                guards(name, source),
21006                None,
21007                "{needle} after a displaced #elif"
21008            );
21009            assert!(matches!(
21010                preprocessor_branch_of_descendant(conditional, name, &mut cache),
21011                PreprocessorBranch::Unreadable
21012            ));
21013            assert_eq!(
21014                preprocessor_conditional_family_for_declaration_with_cache(name, &mut cache),
21015                None,
21016                "{needle}: an unreadable #elif branch does not complete the family"
21017            );
21018            assert_eq!(callable_guard_completion_byte(name, source), None);
21019        }
21020
21021        // A structured `#elif` whose own `#else` was displaced: the content
21022        // after it excludes both readable conditions.
21023        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";
21024        let tree = parse(source);
21025        let root = tree.root_node();
21026        let start = source.rfind("class T").expect("else class") + "class ".len();
21027        let name = root
21028            .descendant_for_byte_range(start, start + 1)
21029            .expect("else class name");
21030        let elif = std::iter::successors(Some(name), |node| node.parent())
21031            .find(|node| node.kind() == "preproc_elif")
21032            .expect("structured elif");
21033        assert_eq!(
21034            cpp_displaced_preprocessor_branch(elif).map(|node| node.kind()),
21035            Some("#else")
21036        );
21037        assert_eq!(
21038            guards(name, source),
21039            Some(set(&[
21040                PreprocessorGuard::Defined("A".to_string()).negated(),
21041                PreprocessorGuard::Defined("B".to_string()).negated(),
21042            ]))
21043        );
21044        let mut cache = PreprocessorContextCache::default();
21045        assert!(
21046            preprocessor_conditional_family_for_declaration_with_cache(name, &mut cache).is_some()
21047        );
21048    }
21049
21050    #[test]
21051    #[hardwon(
21052        "#2184: grammar 0.24.4 reads a macro-decorated return type cleanly, so the recovered-only same-file alias gate stopped matching"
21053    )]
21054    fn macro_modifier_decorated_type_matches_the_recovered_gate_scope() {
21055        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";
21056        let mut parser = Parser::new();
21057        parser
21058            .set_language(&tree_sitter_cpp::LANGUAGE.into())
21059            .expect("C++ grammar");
21060        let tree = parser.parse(source, None).expect("fixture tree");
21061        let root = tree.root_node();
21062        let type_before = |declared: &str| {
21063            let end = source.find(declared).expect("declared name");
21064            let start = source[..end].rfind('T').expect("type token");
21065            root.descendant_for_byte_range(start, start + 1)
21066                .expect("type node")
21067        };
21068        for declared in ["make(", "value;", "p)", "body("] {
21069            let node = type_before(declared);
21070            assert_eq!(node.kind(), "type_identifier", "{declared}");
21071            assert!(
21072                macro_modifier_decorated_type_node(node),
21073                "{declared}: {}",
21074                node.parent().expect("declaration").to_sexp()
21075            );
21076        }
21077        for declared in ["plain(", "qualified("] {
21078            let node = type_before(declared);
21079            assert!(
21080                !macro_modifier_decorated_type_node(node),
21081                "{declared} is outside the macro-decorated head scope: {}",
21082                node.parent().expect("parent").to_sexp()
21083            );
21084        }
21085    }
21086
21087    #[test]
21088    fn expression_defined_and_ifndef_guards_are_incompatible() {
21089        let source = "#if defined(WIN_MODE)\nint selected;\n#endif\n#ifndef WIN_MODE\nint rejected;\n#endif\n";
21090        let mut parser = Parser::new();
21091        parser
21092            .set_language(&tree_sitter_cpp::LANGUAGE.into())
21093            .expect("C++ grammar");
21094        let tree = parser.parse(source, None).expect("fixture tree");
21095        let root = tree.root_node();
21096        let selected_start = source.find("selected").expect("selected declaration");
21097        let rejected_start = source.find("rejected").expect("rejected declaration");
21098        let selected = root
21099            .descendant_for_byte_range(selected_start, selected_start + "selected".len())
21100            .expect("selected node");
21101        let rejected = root
21102            .descendant_for_byte_range(rejected_start, rejected_start + "rejected".len())
21103            .expect("rejected node");
21104        let selected_guards =
21105            preprocessor_guard_environment(selected, source).expect("selected guards");
21106        let rejected_guards =
21107            preprocessor_guard_environment(rejected, source).expect("rejected guards");
21108
21109        assert!(
21110            merge_preprocessor_guards(&selected_guards, &rejected_guards).is_none(),
21111            "opposite spellings of one macro guard must contradict"
21112        );
21113    }
21114
21115    #[test]
21116    #[hardwon("02b3765c2a06: C++ declaration recovery regressed for this structured syntax edge")]
21117    fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
21118        let source = r#"#ifdef _WIN32
21119#if defined(__cplusplus)
21120extern "C"
21121#endif
21122int platform_api(void);
21123#endif
21124
21125#ifdef _WIN32
21126static int entropy_target(void) { return 0; }
21127#else
21128#ifdef HAVE_COMMON_RANDOM
21129static int other_target(void) { return 0; }
21130#elif defined(HAVE_GETENTROPY)
21131static int entropy_target(void) { return 1; }
21132static int use_entropy(void) { return entropy_target(); }
21133#endif
21134#endif
21135"#;
21136        let mut parser = Parser::new();
21137        parser
21138            .set_language(&tree_sitter_cpp::LANGUAGE.into())
21139            .expect("C++ grammar");
21140        let tree = parser.parse(source, None).expect("fixture tree");
21141        let start = source.rfind("entropy_target()").expect("reference");
21142        let node = tree
21143            .root_node()
21144            .descendant_for_byte_range(start, start + "entropy_target".len())
21145            .expect("reference node");
21146        let guards = preprocessor_guard_environment(node, source).expect("active C branch");
21147        assert!(
21148            guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
21149            "{guards:#?}"
21150        );
21151        assert!(
21152            guards.contains(&PreprocessorGuard::Undefined(
21153                "HAVE_COMMON_RANDOM".to_string()
21154            )),
21155            "{guards:#?}"
21156        );
21157        assert!(
21158            guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
21159            "{guards:#?}"
21160        );
21161        assert!(
21162            !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
21163            "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
21164        );
21165    }
21166
21167    #[test]
21168    #[hardwon("1e74d25492c1: C++ declaration recovery regressed for this structured syntax edge")]
21169    fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
21170        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";
21171        let mut parser = Parser::new();
21172        parser
21173            .set_language(&tree_sitter_cpp::LANGUAGE.into())
21174            .expect("C++ grammar");
21175        let tree = parser.parse(source, None).expect("fixture tree");
21176        let root = tree.root_node();
21177        let node_at = |text: &str, start: usize| {
21178            root.descendant_for_byte_range(start, start + text.len())
21179                .expect("token node")
21180        };
21181
21182        let key_start = source.find("case KEY").expect("case label") + "case ".len();
21183        let guard_start = source.find("ENABLE_KEYS").expect("guard name");
21184        assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
21185        assert!(!is_ordinary_macro_reference_node(node_at(
21186            "ENABLE_KEYS",
21187            guard_start,
21188        )));
21189    }
21190
21191    #[test]
21192    #[hardwon("31913fee42a0: C++ declaration recovery regressed for this structured syntax edge")]
21193    fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
21194        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";
21195        let mut parser = Parser::new();
21196        parser
21197            .set_language(&tree_sitter_cpp::LANGUAGE.into())
21198            .expect("C++ grammar");
21199        let tree = parser.parse(source, None).expect("fixture tree");
21200        let root = tree.root_node();
21201        let definition_start = source.find("target(void)").expect("definition");
21202        let reference_start = source.rfind("target()").expect("reference");
21203        let definition = root
21204            .descendant_for_byte_range(definition_start, definition_start + "target".len())
21205            .expect("definition node");
21206        let reference = root
21207            .descendant_for_byte_range(reference_start, reference_start + "target".len())
21208            .expect("reference node");
21209        let required =
21210            preprocessor_guard_environment(definition, source).expect("definition guard");
21211        let active = preprocessor_guard_environment(reference, source).expect("reference guard");
21212        assert!(guard_requirements_hold_at_reference(
21213            &required,
21214            Some(&active)
21215        ));
21216    }
21217
21218    #[test]
21219    #[hardwon("31913fee42a0: C++ declaration recovery regressed for this structured syntax edge")]
21220    fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
21221        let source = "g_autoptr(FuChunkArray) self = make_array();";
21222        let mut parser = Parser::new();
21223        parser
21224            .set_language(&tree_sitter_cpp::LANGUAGE.into())
21225            .expect("C++ grammar");
21226        let tree = parser.parse(source, None).expect("fixture tree");
21227        let statement = tree.root_node().named_child(0).expect("statement");
21228        let binding =
21229            recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
21230        assert_eq!(binding.name, "self");
21231        assert_eq!(binding.type_name, "FuChunkArray");
21232        assert_eq!(binding.pointer_depth, 1);
21233
21234        let near_miss = "holder(FuChunkArray) self = make_array();";
21235        let tree = parser.parse(near_miss, None).expect("near-miss tree");
21236        let statement = tree.root_node().named_child(0).expect("statement");
21237        assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
21238    }
21239
21240    #[test]
21241    #[hardwon("dc1b8c5a6ee8: C++ declaration recovery regressed for this structured syntax edge")]
21242    fn boolean_guard_normalization_proves_equivalence_and_implication() {
21243        let windows = BooleanGuardExpression::Defined("WIN32".to_string());
21244        let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
21245        let negated_windows_branch =
21246            BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
21247        let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
21248        assert_eq!(negated_windows_branch, portable);
21249
21250        let missing_a = BooleanGuardExpression::Undefined("A".to_string());
21251        let missing_b = BooleanGuardExpression::Undefined("B".to_string());
21252        let missing_c = BooleanGuardExpression::Undefined("C".to_string());
21253        let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
21254        let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
21255        assert!(fallback_branch.implies(&fallback_declaration));
21256        assert!(
21257            BooleanGuardExpression::Truthy("FEATURE".to_string())
21258                .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
21259        );
21260        assert!(
21261            BooleanGuardExpression::Undefined("FEATURE".to_string())
21262                .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
21263        );
21264        assert!(
21265            !BooleanGuardExpression::Defined("FEATURE".to_string())
21266                .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
21267        );
21268        assert!(!fallback_declaration.implies(&fallback_branch));
21269    }
21270
21271    #[test]
21272    #[hardwon("15e5ef579cdd: C++ declaration recovery regressed for this structured syntax edge")]
21273    fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
21274        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";
21275        let mut parser = Parser::new();
21276        parser
21277            .set_language(&tree_sitter_cpp::LANGUAGE.into())
21278            .expect("C++ grammar");
21279        let tree = parser.parse(source, None).expect("fixture tree");
21280        let root = tree.root_node();
21281        let call = |marker: &str| {
21282            let start = source.find(marker).expect("call marker");
21283            let mut node = root
21284                .descendant_for_byte_range(start, start + "helper".len())
21285                .expect("call name node");
21286            loop {
21287                if node.kind() == "call_expression" {
21288                    break node;
21289                }
21290                node = node.parent().expect("call expression ancestor");
21291            }
21292        };
21293        let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
21294        let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
21295        let keyword_call = call("helper(NULL, template); /* bound */");
21296        let keyword_arguments = keyword_call
21297            .child_by_field_name("arguments")
21298            .expect("keyword argument list");
21299        assert_eq!(
21300            recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
21301            1
21302        );
21303
21304        let nested_call = call("helper(NULL, template); /* nested bound */");
21305        let nested_arguments = nested_call
21306            .child_by_field_name("arguments")
21307            .expect("nested keyword argument list");
21308        assert_eq!(
21309            recovered_c_keyword_argument_count(&c_file, nested_call, nested_arguments, source),
21310            1
21311        );
21312
21313        let lambda_call = call("helper(NULL, template); }(); /* lambda scope */");
21314        let lambda_arguments = lambda_call
21315            .child_by_field_name("arguments")
21316            .expect("lambda keyword argument list");
21317        assert_eq!(
21318            recovered_c_keyword_argument_count(&c_file, lambda_call, lambda_arguments, source),
21319            0,
21320            "a lambda body must not borrow the enclosing collapsed C parameter"
21321        );
21322
21323        assert_eq!(
21324            recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
21325            0
21326        );
21327
21328        let unbound_call = call("helper(NULL, template); /* unbound */");
21329        let unbound_arguments = unbound_call
21330            .child_by_field_name("arguments")
21331            .expect("unbound argument list");
21332        assert_eq!(
21333            recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
21334            0
21335        );
21336    }
21337
21338    #[test]
21339    #[brokk_bifrost_test_macros::hardwon(
21340        "#4407: collapsed C keyword parameters need structural position proof"
21341    )]
21342    fn c_keyword_argument_recovery_rejects_non_parameter_keyword_positions() {
21343        let source = r#"static int helper(const char *left, wchar_t *right) { return 0; }
21344int type_keyword(template *value) {
21345    return helper(NULL, template); /* type */
21346}
21347int qualifier_keyword(const *template) {
21348    return helper(NULL, template); /* qualifier */
21349}
21350int annotation_keyword(wchar_t *value __attribute__((template))) {
21351    return helper(NULL, template); /* annotation */
21352}
21353int default_keyword(wchar_t *value = template) {
21354    return helper(NULL, template); /* default */
21355}
21356int nested_expression(wchar_t *value, (template)) {
21357    return helper(NULL, template); /* nested */
21358}
21359int malformed_head = (wchar_t *template) {
21360    return helper(NULL, template); /* malformed */
21361};
21362"#;
21363        let tree = parse_cpp(source);
21364        let root = tree.root_node();
21365        let call = |marker: &str| {
21366            let start = source.find(marker).expect("call marker");
21367            let mut node = root
21368                .descendant_for_byte_range(start, start + "helper".len())
21369                .expect("call name node");
21370            loop {
21371                if node.kind() == "call_expression" {
21372                    break Some(node);
21373                }
21374                let Some(parent) = node.parent() else {
21375                    break None;
21376                };
21377                node = parent;
21378            }
21379        };
21380        let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-near-miss.c");
21381        let markers = [
21382            ("helper(NULL, template); /* type */", true),
21383            ("helper(NULL, template); /* qualifier */", false),
21384            ("helper(NULL, template); /* annotation */", false),
21385            ("helper(NULL, template); /* default */", false),
21386            ("helper(NULL, template); /* nested */", false),
21387            ("helper(NULL, template); /* malformed */", false),
21388        ];
21389        let mut exercised = 0;
21390        for (marker, expect_call) in markers {
21391            // The macro-declaration grammar admits the misplaced keyword's
21392            // neighbors as `macro_modifier`s, which flattens these heads and
21393            // their bodies: where no `call_expression` survives, the recovery
21394            // has no invocation to fire on, which is itself the non-admission
21395            // proof. Pin the split so the near-miss matrix cannot silently
21396            // decay: only the `type` marker retains an invocation.
21397            let Some(keyword_call) = call(marker) else {
21398                assert!(
21399                    !expect_call,
21400                    "{marker} lost its call_expression; the near-miss matrix changed"
21401                );
21402                continue;
21403            };
21404            assert!(
21405                expect_call,
21406                "{marker} unexpectedly retains a call_expression; the near-miss matrix changed"
21407            );
21408            exercised += 1;
21409            let keyword_arguments = keyword_call
21410                .child_by_field_name("arguments")
21411                .expect("keyword argument list");
21412            assert_eq!(
21413                recovered_c_keyword_argument_count(
21414                    &c_file,
21415                    keyword_call,
21416                    keyword_arguments,
21417                    source,
21418                ),
21419                0,
21420                "keyword in {marker} must not prove a parameter binding"
21421            );
21422        }
21423        assert!(
21424            exercised > 0,
21425            "at least one near-miss marker must exercise the count itself"
21426        );
21427    }
21428
21429    #[test]
21430    #[hardwon("31982bd4d78a: C++ declaration recovery regressed for this structured syntax edge")]
21431    fn c_function_declarator_recovery_accepts_invocations_not_binders() {
21432        let source = r#"#define MAKE(type) type *value
21433MAKE(int *);
21434typedef struct Item Item;
21435struct CPUX86State { struct { int ZMM_L(int); } xmm_regs[8]; };
21436void gen_op_movl(void *s, int first, int second) { }
21437const char *strZ(const char *value) { return value; }
21438int body(void *s) {
21439    MAKE(int *);
21440    gen_op_movl(s, offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)),
21441                offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)));
21442    execvp(strZ(value), UNCONSTIFY(char **, args));
21443}
21444 #define DEV_CHECK_PRESENCE(TYPE, MEMBER, DEVTYPE, PROPERTY, VALUE) \
21445    if (!((TYPE)target)->MEMBER) { check(DEVTYPE, PROPERTY, VALUE); }
21446int recovered_deviation(struct Deviation *d, struct Target *target, void *ctx) {
21447    if (d->units) {
21448        switch (target->nodetype) {
21449        case 1:
21450        case 2:
21451            break;
21452        default:
21453            AMEND_WRONG_NODETYPE("deviation", "replace", "units");
21454        }
21455        DEV_CHECK_PRESENCE(struct Item *, units, "replacing", "units", d->units);
21456        lysdict_remove(ctx, ((struct Item *)target)->units);
21457        DUP_STRING_GOTO(ctx, d->units, ((struct Item *)target)->units, ret, cleanup);
21458    }
21459     return 0;
21460 }
21461STATIC EFI_STATUS Encode () { return 0; }
21462"#;
21463        let tree = parse_cpp(source);
21464        let top_macro_start = source.find("MAKE(int *);").expect("top macro");
21465        let top_macro = tree
21466            .root_node()
21467            .named_descendant_for_byte_range(top_macro_start, top_macro_start + 4)
21468            .expect("top macro node");
21469        let body_macro_start = source
21470            .match_indices("MAKE(int *);")
21471            .nth(1)
21472            .expect("body macro")
21473            .0;
21474        let body_macro = tree
21475            .root_node()
21476            .named_descendant_for_byte_range(body_macro_start, body_macro_start + 4)
21477            .expect("body macro node");
21478        let function_call_start = source
21479            .find("gen_op_movl(s, offsetof(CPUX86State")
21480            .expect("function call");
21481        let function_call = tree
21482            .root_node()
21483            .named_descendant_for_byte_range(function_call_start, function_call_start + 11)
21484            .expect("function call node");
21485        let strz_start = source.find("strZ(value)").expect("nested function call");
21486        let strz = tree
21487            .root_node()
21488            .named_descendant_for_byte_range(strz_start, strz_start + 4)
21489            .expect("nested function call node");
21490        let recovered_call_start = source.find("lysdict_remove(ctx").expect("recovered call");
21491        let recovered_call = tree
21492            .root_node()
21493            .named_descendant_for_byte_range(
21494                recovered_call_start,
21495                recovered_call_start + "lysdict_remove".len(),
21496            )
21497            .expect("recovered call node");
21498        let binder_start = source.find("Encode").expect("binder");
21499        let binder = tree
21500            .root_node()
21501            .named_descendant_for_byte_range(binder_start, binder_start + 6)
21502            .expect("binder node");
21503
21504        assert!(recovered_c_function_declarator_invocation(top_macro));
21505        assert!(recovered_c_function_declarator_invocation(body_macro));
21506        assert!(recovered_c_function_declarator_invocation(function_call));
21507        assert!(recovered_c_function_declarator_invocation(strz));
21508        assert!(recovered_c_function_declarator_invocation(recovered_call));
21509        assert!(!recovered_c_function_declarator_invocation(binder));
21510    }
21511
21512    #[test]
21513    #[hardwon("31982bd4d78a: C++ declaration recovery regressed for this structured syntax edge")]
21514    fn c_parenthesized_declarator_recovery_keeps_keyword_argument_and_rejects_siblings() {
21515        let source = r#"typedef int krb5_context;
21516int helper(int first, int second) { return first + second; }
21517static krb5_context ctx;
21518int main(int argc, char **argv) {
21519    int ccinitial;
21520    const char *collection_name, *typename;
21521    typename = helper(ctx, ccinitial);
21522    return 0;
21523}
21524"#;
21525        let tree = parse_cpp(source);
21526        let ctx = tree
21527            .root_node()
21528            .descendant_for_byte_range(
21529                source.find("ctx, ccinitial").expect("ctx argument"),
21530                source.find("ctx, ccinitial").expect("ctx argument") + 3,
21531            )
21532            .expect("ctx node");
21533        let ccinitial_start = source.find("ctx, ccinitial").expect("ctx argument") + 5;
21534        let ccinitial = tree
21535            .root_node()
21536            .descendant_for_byte_range(ccinitial_start, ccinitial_start + "ccinitial".len())
21537            .expect("sibling node");
21538        let typename = named_node_at(&tree, source, "typename = helper");
21539        let helper = named_node_at(&tree, source, "helper(ctx, ccinitial)");
21540
21541        // The macro-declaration grammar retains the argument beneath the
21542        // nested `ERROR` as a `type_descriptor`, so its type position is a
21543        // `type_identifier`; the recovery contract below is unchanged.
21544        assert_eq!(ctx.kind(), "type_identifier");
21545        assert!(recovered_c_parenthesized_declarator_reference(ctx));
21546        assert!(!recovered_c_parenthesized_declarator_reference(ccinitial));
21547        assert!(!recovered_c_parenthesized_declarator_reference(typename));
21548        assert!(!recovered_c_parenthesized_declarator_reference(helper));
21549    }
21550
21551    fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
21552        let mut parser = Parser::new();
21553        parser
21554            .set_language(&tree_sitter_cpp::LANGUAGE.into())
21555            .expect("C++ grammar");
21556        let tree = parser.parse(source, None).expect("C++ fixture tree");
21557        let mut stack = vec![tree.root_node()];
21558        while let Some(node) = stack.pop() {
21559            if node.kind() == "enum_specifier" {
21560                return flattened_macro_namespace_components(node, source);
21561            }
21562            let mut cursor = node.walk();
21563            let children = node.named_children(&mut cursor).collect::<Vec<_>>();
21564            stack.extend(children.into_iter().rev());
21565        }
21566        None
21567    }
21568
21569    #[test]
21570    #[hardwon("0e6df36dfa5d: C++ declaration recovery regressed for this structured syntax edge")]
21571    fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
21572        let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
21573namespace detail
21574{
21575enum class value_t { null };
21576}
21577NLOHMANN_JSON_NAMESPACE_END
21578NLOHMANN_JSON_NAMESPACE_BEGIN
21579namespace next
21580{
21581struct next_type {};
21582}
21583NLOHMANN_JSON_NAMESPACE_END
21584"#;
21585        assert_eq!(
21586            first_enum_flattened_namespace(complete),
21587            Some(vec!["detail".to_string()])
21588        );
21589
21590        let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
21591        assert_eq!(
21592            first_enum_flattened_namespace(&stale_end),
21593            Some(vec!["detail".to_string()]),
21594            "a stale end marker before the begin marker must not replace the intended namespace"
21595        );
21596
21597        let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
21598namespace detail
21599{
21600enum class value_t { null };
21601}
21602struct next_type {};
21603"#;
21604        assert_eq!(first_enum_flattened_namespace(incomplete), None);
21605    }
21606}
21607
21608/// Comparator laws for the total C++ lookup order introduced by #1876.
21609///
21610/// `sort_lookup_units` is the single tie-break the C++ resolver applies before
21611/// any "first wins" selection (template families in #1836, the visible
21612/// identifier index, the type-candidate lists). If its comparator is not a
21613/// total order over CodeUnit identity, some pair stays tied and the survivor
21614/// falls back to the order the units arrived in -- which is FxHash iteration
21615/// order over keys whose hash covers the absolute workspace root. That is the
21616/// exact mechanism behind #1836 and the #414 / #432 heisenbug, so the laws are
21617/// checked generatively rather than on one hand-picked list.
21618///
21619/// CodeUnit identity is `source`, `kind`, `fq`, `package_segment_count`,
21620/// `signature` and `synthetic` (see `impl PartialEq for CodeUnit`); a CodeUnit
21621/// carries no range, so declaration ranges are covered by the workspace-level
21622/// property in `tests/suite_analyzers/determinism_properties.rs` instead.
21623#[cfg(test)]
21624mod lookup_order_properties {
21625    use super::*;
21626    use proptest::prelude::*;
21627
21628    /// Segment spellings the C++ extractor and the shared renderer actually
21629    /// produce, including the `$`-joined nested spellings and non-ASCII
21630    /// identifiers that a byte-wise comparison has to keep apart.
21631    const ATOMS: [&str; 9] = ["a", "b", "A", "a$b", "a$", "$a", "ab", "naïve", "識別子"];
21632    const REL_PATHS: [&str; 3] = ["a.cpp", "b.cpp", "sub/a.cpp"];
21633    /// Two roots so the order is pinned across workspaces as well as inside
21634    /// one: the root path is precisely the byte string that used to leak into
21635    /// iteration order.
21636    const ROOT_NAMES: [&str; 2] = ["ws", "ws_much_longer_root_name"];
21637    const SIGNATURES: [Option<&str>; 3] = [None, Some("()"), Some("(int)")];
21638    const KINDS: [CodeUnitType; 6] = [
21639        CodeUnitType::Class,
21640        CodeUnitType::Function,
21641        CodeUnitType::Field,
21642        CodeUnitType::Module,
21643        CodeUnitType::Macro,
21644        CodeUnitType::FileScope,
21645    ];
21646
21647    /// Where one unit sits relative to another under the comparator that
21648    /// `sort_lookup_units` owns.
21649    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
21650    enum ProbedOrder {
21651        Before,
21652        Tied,
21653        After,
21654        /// Both directions reported "strictly first": the comparator is not
21655        /// dual, and no sort over it can be order-independent.
21656        Contradictory,
21657    }
21658
21659    impl ProbedOrder {
21660        fn mirror(self) -> Self {
21661            match self {
21662                ProbedOrder::Before => ProbedOrder::After,
21663                ProbedOrder::After => ProbedOrder::Before,
21664                other => other,
21665            }
21666        }
21667
21668        /// -1 / 0 / +1, so transitivity reads as the `<= 0` law.
21669        fn signum(self) -> i8 {
21670            match self {
21671                ProbedOrder::Before => -1,
21672                ProbedOrder::Tied => 0,
21673                ProbedOrder::After => 1,
21674                ProbedOrder::Contradictory => panic!("probed a non-dual comparator"),
21675            }
21676        }
21677    }
21678
21679    /// Read the comparator through its only caller.
21680    ///
21681    /// `sort_lookup_units` is a stable sort, so for a two-element slice the
21682    /// output says exactly whether the comparator put the second element
21683    /// strictly first. Sorting both arrangements of one pair therefore reports
21684    /// the comparator's verdict in both directions, including the contradictory
21685    /// case a single sort would hide.
21686    fn probe_order(left: &CodeUnit, right: &CodeUnit) -> ProbedOrder {
21687        if left == right {
21688            // A stable sort cannot distinguish two equal values, and `Equal` is
21689            // the only verdict a total order can give them.
21690            return ProbedOrder::Tied;
21691        }
21692        let mut forward = vec![left.clone(), right.clone()];
21693        sort_lookup_units(&mut forward);
21694        let mut backward = vec![right.clone(), left.clone()];
21695        sort_lookup_units(&mut backward);
21696        let left_first = backward[0] == *left;
21697        let right_first = forward[0] == *right;
21698        match (left_first, right_first) {
21699            (true, true) => ProbedOrder::Contradictory,
21700            (true, false) => ProbedOrder::Before,
21701            (false, true) => ProbedOrder::After,
21702            (false, false) => ProbedOrder::Tied,
21703        }
21704    }
21705
21706    fn code_unit_strategy() -> impl Strategy<Value = CodeUnit> {
21707        (
21708            0..ROOT_NAMES.len(),
21709            0..REL_PATHS.len(),
21710            0..KINDS.len(),
21711            prop::collection::vec((0..ATOMS.len(), 0..SegmentKind::ALL.len()), 1..=3),
21712            0..3usize,
21713            0..SIGNATURES.len(),
21714            any::<bool>(),
21715        )
21716            .prop_map(
21717                |(root, rel_path, kind, segments, package_prefix, signature, synthetic)| {
21718                    let source = ProjectFile::new(
21719                        std::env::temp_dir().join(ROOT_NAMES[root]),
21720                        REL_PATHS[rel_path],
21721                    );
21722                    let interner = segment_interner();
21723                    let mut fq = FqName::new();
21724                    for (atom, segment_kind) in &segments {
21725                        fq.push(interner.intern(ATOMS[*atom], SegmentKind::ALL[*segment_kind]));
21726                    }
21727                    // `from_fq` requires a non-empty declaration tail.
21728                    let package_segment_count = package_prefix % fq.len();
21729                    CodeUnit::from_fq(
21730                        source,
21731                        KINDS[kind],
21732                        fq,
21733                        package_segment_count,
21734                        SIGNATURES[signature].map(str::to_string),
21735                        synthetic,
21736                    )
21737                },
21738            )
21739    }
21740
21741    proptest! {
21742        #![proptest_config(ProptestConfig::with_cases(256))]
21743
21744        /// Reflexivity and duality: a unit ties with itself, and no pair is
21745        /// strictly first in both directions.
21746        #[test]
21747        #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
21748        fn lookup_order_is_reflexive_and_dual(
21749            left in code_unit_strategy(),
21750            right in code_unit_strategy(),
21751        ) {
21752            prop_assert_eq!(
21753                probe_order(&left, &left),
21754                ProbedOrder::Tied,
21755                "a unit must tie with itself: {:?}",
21756                left
21757            );
21758            let forward = probe_order(&left, &right);
21759            prop_assert_ne!(
21760                forward,
21761                ProbedOrder::Contradictory,
21762                "comparator put each of these strictly first: left={:?} right={:?}",
21763                left,
21764                right
21765            );
21766            prop_assert_eq!(
21767                probe_order(&right, &left),
21768                forward.mirror(),
21769                "compare(b, a) must reverse compare(a, b): left={:?} right={:?}",
21770                left,
21771                right
21772            );
21773        }
21774
21775        /// Transitivity: `a <= b` and `b <= c` imply `a <= c`.
21776        #[test]
21777        #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
21778        fn lookup_order_is_transitive(
21779            a in code_unit_strategy(),
21780            b in code_unit_strategy(),
21781            c in code_unit_strategy(),
21782        ) {
21783            let ab = probe_order(&a, &b);
21784            let bc = probe_order(&b, &c);
21785            let ac = probe_order(&a, &c);
21786            for (probed, pair) in [(ab, "a,b"), (bc, "b,c"), (ac, "a,c")] {
21787                prop_assert_ne!(
21788                    probed,
21789                    ProbedOrder::Contradictory,
21790                    "comparator is not dual over {}: a={:?} b={:?} c={:?}",
21791                    pair,
21792                    a,
21793                    b,
21794                    c
21795                );
21796            }
21797            if ab.signum() <= 0 && bc.signum() <= 0 {
21798                prop_assert!(
21799                    ac.signum() <= 0,
21800                    "transitivity broken: a<=b ({:?}) and b<=c ({:?}) but a?c is {:?}; \
21801                     a={:?} b={:?} c={:?}",
21802                    ab,
21803                    bc,
21804                    ac,
21805                    a,
21806                    b,
21807                    c
21808                );
21809            }
21810        }
21811
21812        /// The property #1876 exists for: only identical identities may tie.
21813        /// A tie between distinct units is the residual hash-order dependence.
21814        #[test]
21815        #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
21816        fn lookup_order_separates_distinct_identities(
21817            left in code_unit_strategy(),
21818            right in code_unit_strategy(),
21819        ) {
21820            if probe_order(&left, &right) == ProbedOrder::Tied {
21821                prop_assert_eq!(
21822                    &left,
21823                    &right,
21824                    "distinct identities tied, so their order is whatever order they \
21825                     arrived in: left_segments={:?} right_segments={:?}",
21826                    left.fq_segments_debug(),
21827                    right.fq_segments_debug()
21828                );
21829            }
21830        }
21831
21832        /// The consequence the resolver relies on: the sorted list is a
21833        /// function of the SET of units, not of the order they were pushed in.
21834        #[test]
21835        #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
21836        fn lookup_sort_is_permutation_invariant(
21837            units in prop::collection::vec(code_unit_strategy(), 1..=8),
21838        ) {
21839            let mut sorted = units.clone();
21840            sort_lookup_units(&mut sorted);
21841            for rotation in 0..units.len() {
21842                for reversed in [false, true] {
21843                    let mut permuted = units.clone();
21844                    permuted.rotate_left(rotation);
21845                    if reversed {
21846                        permuted.reverse();
21847                    }
21848                    sort_lookup_units(&mut permuted);
21849                    prop_assert_eq!(
21850                        &permuted,
21851                        &sorted,
21852                        "sorting a permutation gave a different list \
21853                         (rotation={}, reversed={}): input={:?}",
21854                        rotation,
21855                        reversed,
21856                        units
21857                    );
21858                }
21859            }
21860        }
21861    }
21862}
21863
21864#[cfg(test)]
21865mod resolver_domain_properties {
21866    use super::*;
21867    use proptest::prelude::*;
21868
21869    proptest! {
21870        // A missing free variable cannot be supplied by irrelevant names.
21871        // The expected fully bound tree is built directly from the inputs.
21872        #[test]
21873        fn template_substitution_respects_free_variables(
21874            first in 0u16..1000,
21875            second in 1000u16..2000,
21876            unrelated in prop::collection::vec(0u16..1000, 1..8),
21877            rename in "[A-Z][a-z]{0,6}",
21878        ) {
21879            let atom = |value: u16| CppTemplateTerm::Atom {
21880                kind: "number_literal".to_owned(), text: value.to_string(),
21881            };
21882            let term = CppTemplateTerm::Node {
21883                kind: "template_argument_list".to_owned(),
21884                children: vec![CppTemplateTerm::Parameter("T".to_owned()), CppTemplateTerm::Parameter(format!("U{rename}"))],
21885            };
21886            let mut bindings = HashMap::default();
21887            bindings.insert("T".to_owned(), atom(first));
21888            prop_assert_eq!(cpp_substitute_template_term(&term, &bindings), None);
21889            for (index, value) in unrelated.into_iter().enumerate() {
21890                bindings.insert(format!("unrelated{index}"), atom(value));
21891                prop_assert_eq!(cpp_substitute_template_term(&term, &bindings), None);
21892            }
21893            bindings.insert(format!("U{rename}"), atom(second));
21894            let expected = CppTemplateTerm::Node {
21895                kind: "template_argument_list".to_owned(), children: vec![atom(first), atom(second)],
21896            };
21897            prop_assert_eq!(cpp_substitute_template_term(&term, &bindings), Some(expected));
21898        }
21899
21900        // Combining redeclarations is symmetric. When their parameter lists
21901        // agree, the accepted argument counts are the union of both domains.
21902        #[test]
21903        fn callable_arity_merge_is_a_symmetric_domain_union(
21904            total in 0usize..20,
21905            left_required in 0usize..20,
21906            right_required in 0usize..20,
21907            left_variadic in any::<bool>(),
21908            right_variadic in any::<bool>(),
21909        ) {
21910            let left = CallableArity::new(left_required.min(total), total, left_variadic);
21911            let right = CallableArity::new(right_required.min(total), total, right_variadic);
21912            let merged = merge_compatible_callable_arities(left, right);
21913            prop_assert_eq!(merged, merge_compatible_callable_arities(right, left));
21914            if let Some(merged) = merged {
21915                for count in 0..=total + 2 {
21916                    prop_assert_eq!(merged.accepts(count), left.accepts(count) || right.accepts(count));
21917                }
21918            } else {
21919                prop_assert_ne!(left_variadic, right_variadic);
21920            }
21921        }
21922
21923        // Repeating the same include-path assumptions cannot discharge an
21924        // undecidable guard. This is the idempotence law of conjunction.
21925        #[test]
21926        fn guard_environment_conjunction_is_idempotent(names in prop::collection::vec("[A-Z]{1,5}", 1..8)) {
21927            let guards: HashSet<_> = names.into_iter().map(PreprocessorGuard::Defined).collect();
21928            let environment = PreprocessorGuardEnvironment { all: guards.clone(), partial: guards.clone() };
21929            let twice = environment.merged(&environment).unwrap();
21930            prop_assert_eq!(&twice.all, &guards);
21931            prop_assert_eq!(&twice.partial, &guards);
21932        }
21933    }
21934}
21935
21936#[cfg(test)]
21937mod resolver_lookup_key_properties {
21938    use super::*;
21939    use proptest::prelude::*;
21940
21941    proptest! {
21942        #![proptest_config(ProptestConfig::with_cases(32))]
21943
21944        // The declaration producer and the bounded FQN consumer must agree
21945        // on at least one exact index key. Canonical-name fallback would mask
21946        // this boundary mismatch in ordinary navigation.
21947        #[test]
21948        fn parsed_nested_types_round_trip_through_bounded_lookup_keys(
21949            namespaces in 0usize..3,
21950            owners in 1usize..5,
21951            suffix in "[a-z]{1,6}",
21952        ) {
21953            let mut source = String::new();
21954            let mut components = Vec::new();
21955            for index in 0..namespaces {
21956                let name = format!("ns{index}_{suffix}");
21957                source.push_str(&format!("namespace {name} {{ "));
21958                components.push(name);
21959            }
21960            for index in 0..owners {
21961                let name = format!("Owner{index}");
21962                source.push_str(&format!("struct {name} {{ "));
21963                components.push(name);
21964            }
21965            source.push_str("struct Leaf {}; ");
21966            for _ in 0..owners { source.push_str("}; "); }
21967            for _ in 0..namespaces { source.push_str("} "); }
21968            components.push("Leaf".to_owned());
21969            source.push_str(&format!("{}* value;", components.join("::")));
21970
21971            let tree = crate::diagnostics::parse_cpp_tree(&source);
21972            prop_assert!(!tree.root_node().has_error());
21973            let file = ProjectFile::new(std::env::temp_dir(), "lookup-keys.cpp");
21974            let parsed = crate::adapter::parse_cpp_file(&file, &source, &tree);
21975            let leaf = parsed.declarations().iter()
21976                .find(|unit| unit.is_class() && unit.identifier() == "Leaf")
21977                .expect("declaration producer indexed the nested type");
21978            let declaration = tree.root_node().named_child(tree.root_node().named_child_count() - 1).unwrap();
21979            prop_assert_eq!(declaration.kind(), "declaration");
21980            let type_node = declaration.child_by_field_name("type").unwrap();
21981            let parts = cpp_type_name_components(type_node, &source).unwrap();
21982            prop_assert_eq!(&parts, &components);
21983            let candidates = cpp_reference_fqn_candidates_from_parts(&parts, TargetKind::Type);
21984            let declaration_key = leaf.fq_name();
21985            prop_assert!(candidates.contains(&declaration_key), "declaration key {:?} absent from bounded query keys {:?}", declaration_key, candidates);
21986        }
21987    }
21988}